svelte-language-server
Version:
A language server for Svelte
288 lines • 13.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SvelteCheck = void 0;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("./lib/documents");
const logger_1 = require("./logger");
const ls_config_1 = require("./ls-config");
const plugins_1 = require("./plugins");
const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider");
const service_1 = require("./plugins/css/service");
const utils_1 = require("./plugins/typescript/features/utils");
const utils_2 = require("./plugins/typescript/utils");
const utils_3 = require("./utils");
const lodash_1 = require("lodash");
/**
* Small wrapper around PluginHost's Diagnostic Capabilities
* for svelte-check, without the overhead of the lsp.
*/
class SvelteCheck {
constructor(workspacePath, options = {}) {
this.options = options;
this.docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text));
this.configManager = new ls_config_1.LSConfigManager();
this.pluginHost = new plugins_1.PluginHost(this.docManager);
logger_1.Logger.setLogErrorsOnly(true);
this.initialize(workspacePath, options);
}
async initialize(workspacePath, options) {
if (options.tsconfig && !(0, path_1.isAbsolute)(options.tsconfig)) {
throw new Error('tsconfigPath needs to be absolute, got ' + options.tsconfig);
}
this.configManager.update({
svelte: {
compilerWarnings: options.compilerWarnings
}
});
// No HTMLPlugin, it does not provide diagnostics
if (shouldRegister('svelte')) {
this.pluginHost.register(new plugins_1.SveltePlugin(this.configManager));
}
if (shouldRegister('css')) {
const services = (0, service_1.createLanguageServices)({
fileSystemProvider: new FileSystemProvider_1.FileSystemProvider()
});
const workspaceFolders = [
{
name: '',
uri: (0, utils_3.pathToUrl)(workspacePath)
}
];
this.pluginHost.register(new plugins_1.CSSPlugin(this.docManager, this.configManager, workspaceFolders, services));
}
if (shouldRegister('js') || options.tsconfig) {
const workspaceUris = [(0, utils_3.pathToUrl)(workspacePath)];
this.lsAndTSDocResolver = new plugins_1.LSAndTSDocResolver(this.docManager, workspaceUris, this.configManager, {
tsconfigPath: options.tsconfig,
isSvelteCheck: true,
onProjectReloaded: options.onProjectReload,
watch: options.watch,
onFileSnapshotCreated: options.onFileSnapshotCreated
});
this.pluginHost.register(new plugins_1.TypeScriptPlugin(this.configManager, this.lsAndTSDocResolver, workspaceUris, this.docManager));
}
function shouldRegister(source) {
return !options.diagnosticSources || options.diagnosticSources.includes(source);
}
}
/**
* Creates/updates given document
*
* @param doc Text and Uri of the document
* @param isNew Whether or not this is the creation of the document
*/
async upsertDocument(doc, isNew) {
const filePath = (0, utils_3.urlToPath)(doc.uri) || '';
if (this.options.tsconfig) {
const lsContainer = await this.getLSContainer(this.options.tsconfig);
if (!lsContainer.fileBelongsToProject(filePath, isNew)) {
return;
}
}
if (doc.uri.endsWith('.ts') ||
doc.uri.endsWith('.js') ||
doc.uri.endsWith('.tsx') ||
doc.uri.endsWith('.jsx') ||
doc.uri.endsWith('.mjs') ||
doc.uri.endsWith('.cjs') ||
doc.uri.endsWith('.mts') ||
doc.uri.endsWith('.cts')) {
this.pluginHost.updateTsOrJsFile(filePath, [
{
range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(Number.MAX_VALUE, Number.MAX_VALUE)),
text: doc.text
}
]);
}
else {
this.docManager.openClientDocument({
text: doc.text,
uri: doc.uri
});
}
}
/**
* Removes/closes document
*
* @param uri Uri of the document
*/
async removeDocument(uri) {
if (!this.docManager.get(uri)) {
return;
}
this.docManager.closeDocument(uri);
this.docManager.releaseDocument(uri);
if (this.options.tsconfig) {
const lsContainer = await this.getLSContainer(this.options.tsconfig);
lsContainer.deleteSnapshot((0, utils_3.urlToPath)(uri) || '');
}
}
/**
* Gets the diagnostics for all currently open files.
*/
async getDiagnostics() {
if (this.options.tsconfig) {
return this.getDiagnosticsForTsconfig(this.options.tsconfig);
}
return await Promise.all(this.docManager.getAllOpenedByClient().map(async (doc) => {
const uri = doc[1].uri;
return await this.getDiagnosticsForFile(uri);
}));
}
async getDiagnosticsForTsconfig(tsconfigPath) {
const lsContainer = await this.getLSContainer(tsconfigPath);
const map = (diagnostic, range) => {
const file = diagnostic.file;
range ??= file
? (0, utils_2.convertRange)({ positionAt: file.getLineAndCharacterOfPosition.bind(file) }, diagnostic)
: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
return {
range: range,
severity: (0, utils_2.mapSeverity)(diagnostic.category),
source: diagnostic.source,
message: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
code: diagnostic.code,
tags: (0, utils_2.getDiagnosticTag)(diagnostic)
};
};
if (lsContainer.configErrors.some((error) => error.category === typescript_1.default.DiagnosticCategory.Error)) {
return reportConfigError();
}
const lang = lsContainer.getService();
if (lsContainer.configErrors.some((error) => error.category === typescript_1.default.DiagnosticCategory.Error)) {
return reportConfigError();
}
const files = lang.getProgram()?.getSourceFiles() || [];
const options = lang.getProgram()?.getCompilerOptions() || {};
const diagnostics = await Promise.all(files.map((file) => {
const uri = (0, utils_3.pathToUrl)(file.fileName);
const doc = this.docManager.get(uri);
if (doc) {
this.docManager.markAsOpenedInClient(uri);
return this.getDiagnosticsForFile(uri);
}
else {
// This check is done inside TS mostly, too, but for some diagnostics like suggestions it
// doesn't apply to all code paths. That's why we do it here, too.
const skipDiagnosticsForFile = (options.skipLibCheck && file.isDeclarationFile) ||
(options.skipDefaultLibCheck && file.hasNoDefaultLib) ||
lsContainer.isShimFiles(file.fileName) ||
// ignore JS files in node_modules
/\/node_modules\/.+\.(c|m)?js$/.test(file.fileName);
const snapshot = lsContainer.snapshotManager.get(file.fileName);
const isKitFile = snapshot?.kitFile ?? false;
const diagnostics = [];
if (!skipDiagnosticsForFile) {
const diagnosticSources = [
'getSyntacticDiagnostics',
'getSuggestionDiagnostics',
'getSemanticDiagnostics'
];
for (const diagnosticSource of diagnosticSources) {
for (let diagnostic of lang[diagnosticSource](file.fileName)) {
if (!diagnostic.start || !diagnostic.length || !isKitFile) {
diagnostics.push(map(diagnostic));
continue;
}
let range = undefined;
const inGenerated = (0, utils_1.isInGeneratedCode)(file.text, diagnostic.start, diagnostic.start + diagnostic.length);
if (inGenerated && snapshot) {
const pos = snapshot.getOriginalPosition(snapshot.positionAt(diagnostic.start));
range = {
start: pos,
end: {
line: pos.line,
// adjust length so it doesn't spill over to the next line
character: pos.character + 1
}
};
// If not one of the specific error messages then filter out
if (diagnostic.code === 2307) {
diagnostic = {
...diagnostic,
messageText: typeof diagnostic.messageText === 'string' &&
diagnostic.messageText.includes('./$types')
? diagnostic.messageText +
` (this likely means that SvelteKit's type generation didn't run yet - try running it by executing 'npm run dev' or 'npm run build')`
: diagnostic.messageText
};
}
else if (diagnostic.code === 2694) {
diagnostic = {
...diagnostic,
messageText: typeof diagnostic.messageText === 'string' &&
diagnostic.messageText.includes('/$types')
? diagnostic.messageText +
` (this likely means that SvelteKit's generated types are out of date - try rerunning it by executing 'npm run dev' or 'npm run build')`
: diagnostic.messageText
};
}
else if (diagnostic.code !==
2355 /* A function whose declared type is neither 'void' nor 'any' must return a value */) {
continue;
}
}
diagnostics.push(map(diagnostic, range));
}
}
}
return {
filePath: file.fileName,
text: snapshot?.originalText ?? file.text,
diagnostics
};
}
}));
if (lsContainer.configErrors.length) {
diagnostics.push(...reportConfigError());
}
return diagnostics;
function reportConfigError() {
const grouped = (0, lodash_1.groupBy)(lsContainer.configErrors, (error) => error.file?.fileName ?? tsconfigPath);
return Object.entries(grouped).map(([filePath, errors]) => ({
filePath,
text: '',
diagnostics: errors.map((diagnostic) => map(diagnostic))
}));
}
}
async getDiagnosticsForFile(uri) {
const diagnostics = await this.pluginHost.getDiagnostics({ uri });
return {
filePath: (0, utils_3.urlToPath)(uri) || '',
text: this.docManager.get(uri)?.getText() || '',
diagnostics
};
}
getLSContainer(tsconfigPath) {
if (!this.lsAndTSDocResolver) {
throw new Error('Cannot run with tsconfig path without LS/TSdoc resolver');
}
return this.lsAndTSDocResolver.getTSService(tsconfigPath);
}
/**
* Gets the watch directories based on the tsconfig include patterns.
* Returns null if no tsconfig is specified.
*/
async getWatchDirectories() {
if (!this.options.tsconfig) {
return null;
}
const lsContainer = await this.getLSContainer(this.options.tsconfig);
const projectConfig = lsContainer.getProjectConfig();
if (!projectConfig.wildcardDirectories) {
return null;
}
return Object.entries(projectConfig.wildcardDirectories).map(([dir, flags]) => ({
path: dir,
recursive: !!(flags & typescript_1.default.WatchDirectoryFlags.Recursive)
}));
}
}
exports.SvelteCheck = SvelteCheck;
//# sourceMappingURL=svelte-check.js.map