svelte-language-server
Version:
A language server for Svelte
284 lines • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDiagnostics = getDiagnostics;
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const logger_1 = require("../../../logger");
const utils_1 = require("../../../utils");
const SvelteDocument_1 = require("../SvelteDocument");
/**
* Returns diagnostics from the svelte compiler.
* Also tries to return errors at correct position if transpilation/preprocessing fails.
*/
async function getDiagnostics(document, svelteDoc, settings, cancellationToken) {
const config = await svelteDoc.config;
if (config?.loadConfigError) {
return getConfigLoadErrorDiagnostics(config.loadConfigError);
}
if (cancellationToken?.isCancellationRequested) {
return [];
}
try {
return await tryGetDiagnostics(document, svelteDoc, settings, cancellationToken);
}
catch (error) {
return getPreprocessErrorDiagnostics(document, error);
}
}
/**
* Try to transpile and compile the svelte file and return diagnostics.
*/
async function tryGetDiagnostics(document, svelteDoc, settings, cancellationToken) {
const transpiled = await svelteDoc.getTranspiled();
if (cancellationToken?.isCancellationRequested) {
return [];
}
try {
const res = await svelteDoc.getCompiled();
if (cancellationToken?.isCancellationRequested) {
return [];
}
let ignoreScriptWarnings = false;
let ignoreStyleWarnings = false;
let ignoreTemplateWarnings = false;
if (!document.config?.preprocess || !!document.config.isFallbackConfig) {
ignoreTemplateWarnings = !!document.getLanguageAttribute('template');
ignoreStyleWarnings = !!document.getLanguageAttribute('style');
const scriptAttr = document.getLanguageAttribute('script');
ignoreScriptWarnings = !!scriptAttr && scriptAttr !== 'ts';
}
return (res.warnings || [])
.filter((warning) => settings[warning.code] !== 'ignore')
.map((warning) => {
const start = warning.start || { line: 1, column: 0 };
const end = warning.end || start;
return {
range: vscode_languageserver_1.Range.create(start.line - 1, start.column, end.line - 1, end.column),
message: warning.message,
severity: settings[warning.code] === 'error'
? vscode_languageserver_1.DiagnosticSeverity.Error
: vscode_languageserver_1.DiagnosticSeverity.Warning,
source: 'svelte',
code: warning.code
};
})
.map((diag) => (0, documents_1.mapObjWithRangeToOriginal)(transpiled, diag))
.map((diag) => adjustMappings(diag, document))
.filter((diag) => isNoFalsePositive(diag, document, ignoreScriptWarnings, ignoreStyleWarnings, ignoreTemplateWarnings));
}
catch (err) {
return createParserErrorDiagnostic(err, document)
.map((diag) => (0, documents_1.mapObjWithRangeToOriginal)(transpiled, diag))
.map((diag) => adjustMappings(diag, document));
}
}
/**
* Try to infer a nice diagnostic error message from the compilation error.
*/
function createParserErrorDiagnostic(error, document) {
const start = error.start || { line: 1, column: 0 };
const end = error.end || start;
const diagnostic = {
range: vscode_languageserver_1.Range.create(start.line - 1, start.column, end.line - 1, end.column),
message: error.message,
severity: vscode_languageserver_1.DiagnosticSeverity.Error,
source: 'svelte',
code: error.code
};
if (diagnostic.message.includes('expected')) {
const isInStyle = (0, documents_1.isInTag)(diagnostic.range.start, document.styleInfo);
const isInScript = (0, documents_1.isInTag)(diagnostic.range.start, document.scriptInfo || document.moduleScriptInfo);
if ((!document.config?.preprocess || document.config.isFallbackConfig) &&
document.hasLanguageAttribute()) {
logger_1.Logger.error(`Parsing ${document.getFilePath()} failed. No preprocess config found but lang tag exists. Skip showing error because they likely use other preprocessors.`);
return [];
}
if (isInStyle || isInScript) {
diagnostic.message +=
'\n\nIf you expect this syntax to work, here are some suggestions: ';
if (isInScript) {
diagnostic.message +=
'\nIf you use typescript with `svelte-preprocess`, did you add `lang="ts"` to your `script` tag? ';
}
else {
diagnostic.message +=
'\nIf you use less/SCSS with `svelte-preprocess`, did you add `lang="scss"`/`lang="less"` to your `style` tag? ' +
scssNodeRuntimeHint;
}
diagnostic.message +=
'\nDid you setup a `svelte.config.js`? ' +
'\nSee https://github.com/sveltejs/language-tools/tree/master/docs#using-with-preprocessors for more info.';
}
}
return [diagnostic];
}
/**
* Try to infer a nice diagnostic error message from the transpilation error.
*/
function getPreprocessErrorDiagnostics(document, error) {
logger_1.Logger.error('Preprocessing failed');
logger_1.Logger.error(error);
if (document.styleInfo && error.__source === SvelteDocument_1.TranspileErrorSource.Style) {
return getStyleErrorDiagnostics(error, document);
}
if ((document.scriptInfo || document.moduleScriptInfo) &&
error.__source === SvelteDocument_1.TranspileErrorSource.Script) {
return getScriptErrorDiagnostics(error, document);
}
return getOtherErrorDiagnostics(error);
}
function getConfigLoadErrorDiagnostics(error) {
return [
{
message: 'Error in svelte.config.js\n\n' + error,
range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(0, 5)),
severity: vscode_languageserver_1.DiagnosticSeverity.Error,
source: 'svelte'
}
];
}
/**
* Try to infer a nice diagnostic error message from the transpilation error.
*/
function getStyleErrorDiagnostics(error, document) {
// Error could be from another file that was mixed into the Svelte file as part of preprocessing.
// Some preprocessors set the file property from which we can infer that
const isErrorFromOtherFile = typeof error?.file === 'string' &&
(0, utils_1.getLastPartOfPath)(error.file) !== (0, utils_1.getLastPartOfPath)(document.getFilePath() || '');
return [
{
message: getStyleErrorMessage(),
range: getStyleErrorRange(),
severity: vscode_languageserver_1.DiagnosticSeverity.Error,
source: 'svelte(style)'
}
];
function getStyleErrorMessage() {
if (isSveltePreprocessCannotFindModulesError(error)) {
const hint = error.message.includes('node-sass') ? scssNodeRuntimeHint : '';
return getErrorMessage(error.message, 'style', hint);
}
const msg = error.formatted /* sass error messages have this */ ||
error.message ||
'Style error. Transpilation failed.';
return isErrorFromOtherFile ? 'Error in referenced file\n\n' + msg : msg;
}
function getStyleErrorRange() {
const lineOffset = document.styleInfo?.startPos.line || 0;
const position = !isErrorFromOtherFile &&
// Some preprocessors like sass or less return error objects with these attributes.
// Use it to display message at better position.
typeof error?.column === 'number' &&
typeof error?.line === 'number'
? vscode_languageserver_1.Position.create(lineOffset + error.line - 1, error.column)
: document.styleInfo?.startPos || vscode_languageserver_1.Position.create(0, 0);
return vscode_languageserver_1.Range.create(position, position);
}
}
/**
* Try to infer a nice diagnostic error message from the transpilation error.
*/
function getScriptErrorDiagnostics(error, document) {
return [
{
message: getScriptErrorMessage(),
range: getScriptErrorRange(),
severity: vscode_languageserver_1.DiagnosticSeverity.Error,
source: 'svelte(script)'
}
];
function getScriptErrorMessage() {
if (isSveltePreprocessCannotFindModulesError(error)) {
return getErrorMessage(error.message, 'script');
}
return error.message || 'Script error. Transpilation failed.';
}
function getScriptErrorRange() {
const position = document.scriptInfo?.startPos ||
document.moduleScriptInfo?.startPos ||
vscode_languageserver_1.Position.create(0, 0);
return vscode_languageserver_1.Range.create(position, position);
}
}
/**
* Try to infer a nice diagnostic error message from the transpilation error.
*/
function getOtherErrorDiagnostics(error) {
return [
{
message: getOtherErrorMessage(),
range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(0, 5)),
severity: vscode_languageserver_1.DiagnosticSeverity.Warning,
source: 'svelte'
}
];
function getOtherErrorMessage() {
if (isSveltePreprocessCannotFindModulesError(error)) {
return getErrorMessage(error.message, 'it');
}
return error.message || 'Error. Transpilation failed.';
}
}
/**
* Preprocessing could fail if packages cannot be resolved.
* A warning about a broken svelte.configs.js/preprocessor setup should be added then.
*/
function isSveltePreprocessCannotFindModulesError(error) {
return error instanceof Error && error.message.startsWith('Cannot find any of modules');
}
function getErrorMessage(error, source, hint = '') {
return (error +
'\n\nThe file cannot be parsed because ' +
source +
" requires a preprocessor that doesn't seem to be setup or failed during setup. " +
'Did you setup a `svelte.config.js`? ' +
hint +
'\n\nSee https://github.com/sveltejs/language-tools/tree/master/docs#using-with-preprocessors for more info.');
}
function isNoFalsePositive(diag, doc, ignoreScriptWarnings, ignoreStyleWarnings, ignoreTemplateWarnings) {
if ((ignoreTemplateWarnings || ignoreScriptWarnings) &&
(typeof diag.code !== 'string' || !diag.code.startsWith('a11y'))) {
return false;
}
if (ignoreStyleWarnings &&
(diag.code === 'css-unused-selector' || diag.code === 'css_unused_selector')) {
return false;
}
if (diag.code !== 'unused-export-let' && diag.code !== 'export_let_unused') {
return true;
}
// TypeScript transpiles `export enum A` and `export namespace A` to `export var A`,
// which the compiler will warn about.
// Silence this edge case. We extract the property from the message and don't use the position
// because that position could be wrong when source mapping trips up.
const unusedExportName = diag.message.substring(diag.message.indexOf("'") + 1, diag.message.lastIndexOf("'"));
const hasExportedEnumWithThatName = new RegExp(`\\bexport\\s+?(enum|namespace)\\s+?${unusedExportName}\\b`).test(doc.getText());
return !hasExportedEnumWithThatName;
}
/**
* Some mappings might be invalid. Try to catch these cases here.
*/
function adjustMappings(diag, doc) {
if (diag.range.start.character < 0) {
diag.range.start.character = 0;
}
if (diag.range.end.character < 0) {
diag.range.end.character = 0;
}
if (diag.range.start.line < 0) {
diag.range.start = { line: 0, character: 0 };
}
if (diag.range.end.line < 0) {
diag.range.end = { line: 0, character: 0 };
}
diag.range = (0, utils_1.moveRangeStartToEndIfNecessary)(diag.range);
if ((diag.code === 'css-unused-selector' || diag.code === 'css_unused_selector') &&
doc.styleInfo &&
!(0, documents_1.isInTag)(diag.range.start, doc.styleInfo)) {
diag.range.start = doc.styleInfo.startPos;
diag.range.end = diag.range.start;
}
return diag;
}
const scssNodeRuntimeHint = 'If you use SCSS, it may be necessary to add the path to your NODE runtime to the setting `svelte.language-server.runtime`, or use `sass` instead of `node-sass`. ';
//# sourceMappingURL=getDiagnostics.js.map