@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>
198 lines • 9.72 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiquidVariableRenameProvider = void 0;
const documents_1 = require("../../documents");
const liquid_html_parser_1 = require("@shopify/liquid-html-parser");
const vscode_languageserver_1 = require("vscode-languageserver");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const theme_check_common_1 = require("@shopify/theme-check-common");
const uri_1 = require("../../utils/uri");
class LiquidVariableRenameProvider {
constructor(connection, clientCapabilities, documentManager, findThemeRootURI) {
this.connection = connection;
this.clientCapabilities = clientCapabilities;
this.documentManager = documentManager;
this.findThemeRootURI = findThemeRootURI;
}
async prepare(node, ancestors, params) {
const document = this.documentManager.get(params.textDocument.uri);
const textDocument = document === null || document === void 0 ? void 0 : document.textDocument;
if (!textDocument || !node || !ancestors)
return null;
if (!supportedTags(node, ancestors))
return null;
const oldName = variableName(node);
const offsetOfVariableNameEnd = node.position.start + oldName.length;
// The cursor could be past the end of the variable name
if (textDocument.offsetAt(params.position) > offsetOfVariableNameEnd)
return null;
return {
range: vscode_languageserver_1.Range.create(textDocument.positionAt(node.position.start), textDocument.positionAt(offsetOfVariableNameEnd)),
placeholder: oldName,
};
}
async rename(node, ancestors, params) {
const document = this.documentManager.get(params.textDocument.uri);
const rootUri = await this.findThemeRootURI(params.textDocument.uri);
const textDocument = document === null || document === void 0 ? void 0 : document.textDocument;
if (!textDocument || !node || !ancestors)
return null;
if (document.ast instanceof Error)
return null;
if (!supportedTags(node, ancestors))
return null;
const oldName = variableName(node);
const scope = variableNameBlockScope(oldName, ancestors);
const replaceRange = textReplaceRange(oldName, textDocument, scope);
let liquidDocParamUpdated = false;
const ranges = (0, theme_check_common_1.visit)(document.ast, {
VariableLookup: replaceRange,
AssignMarkup: replaceRange,
ForMarkup: replaceRange,
TextNode: (node, ancestors) => {
var _a;
if (((_a = ancestors.at(-1)) === null || _a === void 0 ? void 0 : _a.type) !== liquid_html_parser_1.NodeTypes.LiquidDocParamNode)
return;
liquidDocParamUpdated = true;
return replaceRange(node, ancestors);
},
});
if (this.clientCapabilities.hasApplyEditSupport && liquidDocParamUpdated) {
const themeFiles = this.documentManager.theme(rootUri, true);
const liquidSourceCodes = themeFiles.filter(documents_1.isLiquidSourceCode);
const name = (0, uri_1.snippetName)(params.textDocument.uri);
updateRenderTags(this.connection, liquidSourceCodes, name, oldName, params.newName);
}
const textDocumentEdit = vscode_languageserver_protocol_1.TextDocumentEdit.create({ uri: textDocument.uri, version: textDocument.version }, ranges.map((range) => vscode_languageserver_protocol_1.TextEdit.replace(range, params.newName)));
return {
documentChanges: [textDocumentEdit],
};
}
}
exports.LiquidVariableRenameProvider = LiquidVariableRenameProvider;
function supportedTags(node, ancestors) {
return (node.type === liquid_html_parser_1.NodeTypes.AssignMarkup ||
node.type === liquid_html_parser_1.NodeTypes.VariableLookup ||
node.type === liquid_html_parser_1.NodeTypes.ForMarkup ||
isLiquidDocParamNameNode(node, ancestors));
}
function isLiquidDocParamNameNode(node, ancestors) {
const parentNode = ancestors.at(-1);
return (!!parentNode &&
parentNode.type === liquid_html_parser_1.NodeTypes.LiquidDocParamNode &&
parentNode.paramName === node &&
node.type === liquid_html_parser_1.NodeTypes.TextNode);
}
function variableName(node) {
var _a, _b;
switch (node.type) {
case liquid_html_parser_1.NodeTypes.VariableLookup:
case liquid_html_parser_1.NodeTypes.AssignMarkup:
return (_a = node.name) !== null && _a !== void 0 ? _a : '';
case liquid_html_parser_1.NodeTypes.ForMarkup:
return (_b = node.variableName) !== null && _b !== void 0 ? _b : '';
case liquid_html_parser_1.NodeTypes.TextNode:
return node.value;
default:
return '';
}
}
/*
* Find the scope where the variable name is used. Looks at defined in `tablerow` and `for` tags.
*/
function variableNameBlockScope(variableName, ancestors) {
let scopedAncestor;
for (let i = ancestors.length - 1; i >= 0; i--) {
const ancestor = ancestors[i];
if (ancestor.type === liquid_html_parser_1.NodeTypes.LiquidTag &&
(ancestor.name === liquid_html_parser_1.NamedTags.tablerow || ancestor.name === liquid_html_parser_1.NamedTags.for) &&
typeof ancestor.markup !== 'string' &&
ancestor.markup.variableName === variableName) {
scopedAncestor = ancestor;
break;
}
}
if (!scopedAncestor || !scopedAncestor.blockEndPosition)
return;
return {
start: scopedAncestor.blockStartPosition.start,
end: scopedAncestor.blockEndPosition.end,
};
}
function textReplaceRange(oldName, textDocument, selectedVariableScope) {
return (node, ancestors) => {
if (variableName(node) !== oldName)
return;
const ancestorScope = variableNameBlockScope(oldName, ancestors);
if ((ancestorScope === null || ancestorScope === void 0 ? void 0 : ancestorScope.start) !== (selectedVariableScope === null || selectedVariableScope === void 0 ? void 0 : selectedVariableScope.start) ||
(ancestorScope === null || ancestorScope === void 0 ? void 0 : ancestorScope.end) !== (selectedVariableScope === null || selectedVariableScope === void 0 ? void 0 : selectedVariableScope.end)) {
return;
}
return vscode_languageserver_1.Range.create(textDocument.positionAt(node.position.start), textDocument.positionAt(node.position.start + oldName.length));
};
}
async function updateRenderTags(connection, liquidSourceCodes, snippetName, oldParamName, newParamName) {
var _a;
const editLabel = `Rename snippet parameter '${oldParamName}' to '${newParamName}'`;
const annotationId = 'renameSnippetParameter';
const workspaceEdit = {
documentChanges: [],
changeAnnotations: {
[annotationId]: {
label: editLabel,
needsConfirmation: false,
},
},
};
for (const sourceCode of liquidSourceCodes) {
if (sourceCode.ast instanceof Error)
continue;
const textDocument = sourceCode.textDocument;
const edits = (0, theme_check_common_1.visit)(sourceCode.ast, {
RenderMarkup(node) {
var _a;
if (node.snippet.type !== liquid_html_parser_1.NodeTypes.String || node.snippet.value !== snippetName) {
return;
}
const renamedNameParamNode = node.args.find((arg) => arg.name === oldParamName);
if (renamedNameParamNode) {
return {
newText: `${newParamName}: `,
range: vscode_languageserver_1.Range.create(textDocument.positionAt(renamedNameParamNode.position.start), textDocument.positionAt(renamedNameParamNode.value.position.start)),
};
}
if (((_a = node.alias) === null || _a === void 0 ? void 0 : _a.value) === oldParamName && node.variable) {
// `as variable` is not captured in our liquid parser yet,
// so we have to check it manually and replace it
const aliasMatch = /as\s+([^\s,]+)/g;
const match = aliasMatch.exec(node.source.slice(node.position.start, node.position.end));
if (!match)
return;
return {
newText: `as ${newParamName}`,
range: vscode_languageserver_1.Range.create(textDocument.positionAt(node.position.start + match.index), textDocument.positionAt(node.position.start + match.index + match[0].length)),
};
}
},
});
if (edits.length === 0)
continue;
workspaceEdit.documentChanges.push({
textDocument: {
uri: textDocument.uri,
version: (_a = sourceCode.version) !== null && _a !== void 0 ? _a : null /* null means file from disk in this API */,
},
annotationId,
edits,
});
}
if (workspaceEdit.documentChanges.length === 0) {
console.error('Nothing to do!');
return;
}
await connection.sendRequest(vscode_languageserver_protocol_1.ApplyWorkspaceEditRequest.type, {
label: editLabel,
edit: workspaceEdit,
});
}
//# sourceMappingURL=LiquidVariableRenameProvider.js.map
;