@shopify/theme-language-server-common
Version:
<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>
126 lines • 6.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JSONLanguageService = void 0;
const liquid_html_parser_1 = require("@shopify/liquid-html-parser");
const theme_check_common_1 = require("@shopify/theme-check-common");
const vscode_json_languageservice_1 = require("vscode-json-languageservice");
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const JSONContributions_1 = require("./JSONContributions");
class JSONLanguageService {
constructor(documentManager, jsonValidationSet, getDefaultSchemaTranslations, getModeForURI, getThemeBlockNames, getThemeBlockSchema) {
this.documentManager = documentManager;
this.jsonValidationSet = jsonValidationSet;
this.getDefaultSchemaTranslations = getDefaultSchemaTranslations;
this.getModeForURI = getModeForURI;
this.getThemeBlockNames = getThemeBlockNames;
this.getThemeBlockSchema = getThemeBlockSchema;
this.isValidSchema = async (uri, jsonString) => {
const mode = await this.getModeForURI(uri);
const service = this.services[mode];
if (!service)
return false;
return (0, theme_check_common_1.isValid)(service, uri, jsonString);
};
this.services = Object.fromEntries(theme_check_common_1.Modes.map((mode) => [mode, null]));
this.schemas = {};
}
async setup(clientCapabilities) {
await Promise.all(theme_check_common_1.Modes.map(async (mode) => {
const schemas = await this.jsonValidationSet.schemas(mode);
for (const schema of schemas) {
this.schemas[schema.uri] = schema;
}
if (!schemas.length)
return;
const service = (0, vscode_json_languageservice_1.getLanguageService)({
clientCapabilities,
// Map URIs to schemas without making network requests. Removes the
// network dependency.
schemaRequestService: this.getSchemaForURI.bind(this),
// This is how we make sure that our "$ref": "./inputSettings.json" in
// our JSON schemas resolve correctly.
workspaceContext: {
resolveRelativePath: (relativePath, resource) => {
const url = new URL(relativePath, resource);
return url.toString();
},
},
contributions: [
new JSONContributions_1.JSONContributions(this.documentManager, this.getDefaultSchemaTranslations, this.getThemeBlockNames, this.getThemeBlockSchema),
],
});
service.configure({
// This is what we use to map file names to JSON schemas. Without
// this, we'd need folks to use the `$schema` field in their JSON
// blobs. That ain't fun nor is going to happen.
schemas: schemas.map((schemaDefinition) => ({
uri: schemaDefinition.uri,
fileMatch: schemaDefinition.fileMatch,
})),
});
this.services[mode] = service;
}));
}
async completions(params) {
const mode = await this.getModeForURI(params.textDocument.uri);
const service = this.services[mode];
if (!service)
return null;
const documents = this.getDocuments(params, service);
if (!documents)
return null;
const [jsonTextDocument, jsonDocument] = documents;
return service.doComplete(jsonTextDocument, params.position, jsonDocument);
}
async hover(params) {
const mode = await this.getModeForURI(params.textDocument.uri);
const service = this.services[mode];
if (!service)
return null;
const documents = this.getDocuments(params, service);
if (!documents)
return null;
const [jsonTextDocument, jsonDocument] = documents;
return service.doHover(jsonTextDocument, params.position, jsonDocument);
}
getDocuments(params, service) {
const document = this.documentManager.get(params.textDocument.uri);
if (!document)
return null;
switch (document.type) {
case theme_check_common_1.SourceCodeType.JSON: {
const jsonTextDocument = document.textDocument;
const jsonDocument = service.parseJSONDocument(jsonTextDocument);
return [jsonTextDocument, jsonDocument];
}
case theme_check_common_1.SourceCodeType.LiquidHtml: {
if (document.ast instanceof Error)
return null;
const textDocument = document.textDocument;
const offset = textDocument.offsetAt(params.position);
const [_, ancestors] = (0, theme_check_common_1.findCurrentNode)(document.ast, offset);
const schema = ancestors.find((node) => node.type === liquid_html_parser_1.NodeTypes.LiquidRawTag && node.name === 'schema');
if (!schema)
return null;
const schemaLineNumber = textDocument.positionAt(schema.blockStartPosition.end).line;
// Hacking away "same line numbers" here by prefixing the file with newlines
// This way params.position will be at the same line number in this fake jsonTextDocument
// Which means that the completions will be at the same line number in the Liquid document
const jsonString = Array(schemaLineNumber).fill('\n').join('') +
schema.source.slice(schema.blockStartPosition.end, schema.blockEndPosition.start);
const jsonTextDocument = vscode_languageserver_textdocument_1.TextDocument.create(textDocument.uri, 'json', textDocument.version, jsonString);
const jsonDocument = service.parseJSONDocument(jsonTextDocument);
return [jsonTextDocument, jsonDocument];
}
}
}
async getSchemaForURI(uri) {
var _a;
const schema = (_a = this.schemas[uri]) === null || _a === void 0 ? void 0 : _a.schema;
if (!schema)
return `Could not get schema for '${uri}'`;
return schema;
}
}
exports.JSONLanguageService = JSONLanguageService;
//# sourceMappingURL=JSONLanguageService.js.map