svelte-language-server
Version:
A language server for Svelte
140 lines • 6.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getQuickfixActions = getQuickfixActions;
exports.isIgnorableSvelteDiagnostic = isIgnorableSvelteDiagnostic;
const estree_walker_1 = require("estree-walker");
const os_1 = require("os");
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../../lib/documents");
const utils_1 = require("../../../../utils");
/**
* Get applicable quick fixes.
*/
async function getQuickfixActions(svelteDoc, svelteDiagnostics) {
const textDocument = vscode_languageserver_1.OptionalVersionedTextDocumentIdentifier.create((0, utils_1.pathToUrl)(svelteDoc.getFilePath()), null);
const { ast } = await svelteDoc.getCompiled();
const transpiled = await svelteDoc.getTranspiled();
const content = transpiled.getText();
const lineOffsets = (0, documents_1.getLineOffsets)(content);
const codeActions = [];
for (const diagnostic of svelteDiagnostics) {
codeActions.push(...(await createQuickfixActions(textDocument, transpiled, content, lineOffsets, ast, diagnostic)));
}
return codeActions;
}
async function createQuickfixActions(textDocument, transpiled, content, lineOffsets, ast, diagnostic) {
const { range: { start, end } } = diagnostic;
const generatedStart = transpiled.getGeneratedPosition(start);
const generatedEnd = transpiled.getGeneratedPosition(end);
const diagnosticStartOffset = (0, documents_1.offsetAt)(generatedStart, content, lineOffsets);
const diagnosticEndOffset = (0, documents_1.offsetAt)(generatedEnd, content, lineOffsets);
const offsetRange = {
pos: diagnosticStartOffset,
end: diagnosticEndOffset
};
const { html, instance, module } = ast;
const tree = [html, instance, module].find((part) => {
return (part?.start != null &&
offsetRange.pos >= part.start &&
part?.end != null &&
offsetRange.pos <= part.end &&
part?.end != null &&
offsetRange.end <= part.end &&
part?.start != null &&
offsetRange.end >= part.start);
});
const node = findTagForRange(tree, offsetRange, tree === html);
const codeActions = [];
if (diagnostic.code == 'security-anchor-rel-noreferrer') {
codeActions.push(createSvelteAnchorMissingAttributeQuickfixAction(textDocument, transpiled, content, lineOffsets, node));
}
codeActions.push(createSvelteIgnoreQuickfixAction(textDocument, transpiled, content, lineOffsets, node, diagnostic, tree === html));
return codeActions;
}
function createSvelteAnchorMissingAttributeQuickfixAction(textDocument, transpiled, content, lineOffsets, node) {
// Assert non-null because the node target attribute is required for 'security-anchor-rel-noreferrer'
const targetAttribute = node.attributes.find((i) => i.name == 'target');
const relAttribute = node.attributes.find((i) => i.name == 'rel');
const codeActionTextEdit = relAttribute
? vscode_languageserver_1.TextEdit.insert((0, documents_1.positionAt)(relAttribute.end - 1, content, lineOffsets), ' noreferrer')
: vscode_languageserver_1.TextEdit.insert((0, documents_1.positionAt)(targetAttribute.end, content, lineOffsets), ' rel="noreferrer"');
return vscode_languageserver_1.CodeAction.create('(svelte) Add missing attribute rel="noreferrer"', {
documentChanges: [
vscode_languageserver_1.TextDocumentEdit.create(textDocument, [
(0, documents_1.mapObjWithRangeToOriginal)(transpiled, codeActionTextEdit)
])
]
}, vscode_languageserver_1.CodeActionKind.QuickFix);
}
function createSvelteIgnoreQuickfixAction(textDocument, transpiled, content, lineOffsets, node, diagnostic, isHtml) {
return vscode_languageserver_1.CodeAction.create(getCodeActionTitle(diagnostic), {
documentChanges: [
vscode_languageserver_1.TextDocumentEdit.create(textDocument, [
getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic, isHtml)
])
]
}, vscode_languageserver_1.CodeActionKind.QuickFix);
}
function getCodeActionTitle(diagnostic) {
// make it distinguishable with eslint's code action
return `(svelte) Disable ${diagnostic.code} for this line`;
}
/**
* Whether or not the given diagnostic can be ignored via a
* <!-- svelte-ignore <code> -->
*/
function isIgnorableSvelteDiagnostic(diagnostic) {
const { source, severity, code } = diagnostic;
return (code &&
!nonIgnorableWarnings.includes(code) &&
source === 'svelte' &&
severity !== vscode_languageserver_1.DiagnosticSeverity.Error);
}
const nonIgnorableWarnings = [
'missing-custom-element-compile-options',
'unused-export-let',
'css-unused-selector'
];
function getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic, isHtml) {
const { code } = diagnostic;
const nodeStartPosition = (0, documents_1.positionAt)(node.start, content, lineOffsets);
const nodeLineStart = (0, documents_1.offsetAt)({
line: nodeStartPosition.line,
character: 0
}, content, lineOffsets);
const afterStartLineStart = content.slice(nodeLineStart);
const indent = (0, utils_1.getIndent)(afterStartLineStart);
// TODO: Make all code action's new line consistent
let ignore = `${indent}// svelte-ignore ${code}${os_1.EOL}${indent}`;
if (isHtml) {
ignore = `${indent}<!-- svelte-ignore ${code} -->${os_1.EOL}`;
}
const position = vscode_languageserver_1.Position.create(nodeStartPosition.line, 0);
return (0, documents_1.mapObjWithRangeToOriginal)(transpiled, vscode_languageserver_1.TextEdit.insert(position, ignore));
}
const elementOrComponent = ['Component', 'Element', 'InlineComponent'];
function findTagForRange(ast, range, isHtml) {
let nearest = ast;
(0, estree_walker_1.walk)(ast, {
enter(node, parent) {
if (isHtml) {
const { type } = node;
const isBlock = 'block' in node || node.type.toLowerCase().includes('block');
const isFragment = type === 'Fragment';
const keepLooking = isFragment || elementOrComponent.includes(type) || isBlock;
if (!keepLooking) {
this.skip();
return;
}
}
if (within(node, range) && parent === nearest) {
nearest = node;
}
}
});
return nearest;
}
function within(node, range) {
return node.end >= range.end && node.start <= range.pos;
}
//# sourceMappingURL=getQuickfixes.js.map