Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
470 changes: 340 additions & 130 deletions packages/language-server/src/plugins/typescript/DocumentSnapshot.ts

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ async function createLanguageService(
);

// Load all configs within the tsconfig scope and the one above so that they are all loaded
// by the time they need to be accessed synchronously by DocumentSnapshots to determine
// the default language.
// by the time they need to be accessed synchronously by DocumentSnapshots.
await configLoader.loadConfigs(workspacePath);

const svelteModuleLoader = createSvelteModuleLoader(getSnapshot, compilerOptions, tsSystem);
Expand Down
134 changes: 84 additions & 50 deletions packages/language-server/src/svelte-check.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { basename, isAbsolute } from 'path';
import { isAbsolute } from 'path';
import ts from 'typescript';
import { Diagnostic, Position, Range } from 'vscode-languageserver';
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
import { Document, DocumentManager } from './lib/documents';
import { FileSystemProvider } from './plugins/css/FileSystemProvider';
import { Logger } from './logger';
import { LSConfigManager } from './ls-config';
import {
Expand All @@ -13,11 +12,12 @@ import {
SveltePlugin,
TypeScriptPlugin
} from './plugins';
import { FileSystemProvider } from './plugins/css/FileSystemProvider';
import { createLanguageServices } from './plugins/css/service';
import { JSOrTSDocumentSnapshot } from './plugins/typescript/DocumentSnapshot';
import { isInGeneratedCode } from './plugins/typescript/features/utils';
import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils';
import { pathToUrl, urlToPath } from './utils';
import { isInGeneratedCode } from './plugins/typescript/features/utils';
import { kitPageFiles } from './plugins/typescript/DocumentSnapshot';

export type SvelteCheckDiagnosticSource = 'js' | 'css' | 'svelte';

Expand Down Expand Up @@ -200,58 +200,92 @@ export class SvelteCheck {
(options.skipDefaultLibCheck && file.hasNoDefaultLib) ||
// ignore JS files in node_modules
/\/node_modules\/.+\.(c|m)?js$/.test(file.fileName);
const isKitFile = kitPageFiles.has(basename(file.fileName));
const snapshot = lsContainer.snapshotManager.get(file.fileName) as
| JSOrTSDocumentSnapshot
| undefined;
const isKitFile = snapshot?.kitFile ?? false;
const diagnostics: Diagnostic[] = [];
const map = (diagnostic: ts.Diagnostic, range?: Range) => ({
range:
range ??
convertRange(
{ positionAt: file.getLineAndCharacterOfPosition.bind(file) },
diagnostic
),
severity: mapSeverity(diagnostic.category),
source: diagnostic.source,
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
code: diagnostic.code,
tags: getDiagnosticTag(diagnostic)
});

if (!skipDiagnosticsForFile) {
const originalDiagnostics = [
...lang.getSyntacticDiagnostics(file.fileName),
...lang.getSuggestionDiagnostics(file.fileName),
...lang.getSemanticDiagnostics(file.fileName)
];

const diagnostics = skipDiagnosticsForFile
? []
: [
...lang.getSyntacticDiagnostics(file.fileName),
...lang.getSuggestionDiagnostics(file.fileName),
...lang.getSemanticDiagnostics(file.fileName)
]
.filter((diagnostic) => {
if (!isKitFile) {
return true;
}
for (let diagnostic of originalDiagnostics) {
if (!diagnostic.start || !diagnostic.length || !isKitFile) {
diagnostics.push(map(diagnostic));
continue;
}

if (
diagnostic.start === undefined ||
diagnostic.length === undefined
) {
return true;
}
let range: Range | undefined = undefined;
const inGenerated = 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;
}
}

const text = lang
.getProgram()
?.getSourceFile(file.fileName)
?.getFullText();
return (
!text ||
!isInGeneratedCode(
text,
diagnostic.start,
diagnostic.start + diagnostic.length
)
);
})
.map<Diagnostic>((diagnostic) => ({
range: convertRange(
{ positionAt: file.getLineAndCharacterOfPosition.bind(file) },
diagnostic
),
severity: mapSeverity(diagnostic.category),
source: diagnostic.source,
message: ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n'
),
code: diagnostic.code,
tags: getDiagnosticTag(diagnostic)
}));
diagnostics.push(map(diagnostic, range));
}
}

return {
filePath: file.fileName,
text: file.text,
text: snapshot?.originalText ?? file.text,
diagnostics
};
}
Expand Down
31 changes: 9 additions & 22 deletions packages/svelte-check/src/writers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,38 +83,25 @@ export class HumanFriendlyWriter implements Writer {
private getCodeLine(diagnostic: Diagnostic, text: string) {
const startOffset = offsetAt(diagnostic.range.start, text);
const endOffset = offsetAt(diagnostic.range.end, text);
const codePrev = this.removeGeneratedCode(
text.substring(
offsetAt({ line: diagnostic.range.start.line, character: 0 }, text),
startOffset
)
const codePrev = text.substring(
offsetAt({ line: diagnostic.range.start.line, character: 0 }, text),
startOffset
);
const codeHighlight = pc.magenta(text.substring(startOffset, endOffset));
const codePost = this.removeGeneratedCode(
text.substring(
endOffset,
offsetAt(
{ line: diagnostic.range.end.line, character: Number.MAX_SAFE_INTEGER },
text
)
)
const codePost = text.substring(
endOffset,
offsetAt({ line: diagnostic.range.end.line, character: Number.MAX_SAFE_INTEGER }, text)
);
return codePrev + codeHighlight + codePost;
}

private getLine(line: number, text: string): string {
return this.removeGeneratedCode(
text.substring(
offsetAt({ line, character: 0 }, text),
offsetAt({ line, character: Number.MAX_SAFE_INTEGER }, text)
)
return text.substring(
offsetAt({ line, character: 0 }, text),
offsetAt({ line, character: Number.MAX_SAFE_INTEGER }, text)
);
}

private removeGeneratedCode(text: string): string {
return text.replace(/\/\*Ωignore_startΩ\*\/.+\/\*Ωignore_endΩ\*\//g, '');
}

completion(
_f: number,
errorCount: number,
Expand Down
12 changes: 2 additions & 10 deletions packages/typescript-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SvelteSnapshotManager } from './svelte-snapshots';
import type ts from 'typescript/lib/tsserverlibrary';
import { ConfigManager, Configuration } from './config-manager';
import { ProjectSvelteFilesManager } from './project-svelte-files';
import { getConfigPathForProject } from './utils';
import { getConfigPathForProject, hasNodeModule } from './utils';

function init(modules: { typescript: typeof ts }): ts.server.PluginModule {
const configManager = new ConfigManager();
Expand Down Expand Up @@ -185,15 +185,7 @@ function init(modules: { typescript: typeof ts }): ts.server.PluginModule {

function isSvelteProject(compilerOptions: ts.CompilerOptions) {
// Add more checks like "no Svelte file found" or "no config file found"?
try {
const isSvelteProject =
typeof compilerOptions.configFilePath !== 'string' ||
require.resolve('svelte', { paths: [compilerOptions.configFilePath] });
return isSvelteProject;
} catch (e) {
// If require.resolve fails, we end up here
return false;
}
return hasNodeModule(compilerOptions, 'svelte');
}

function onConfigurationChanged(config: Configuration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { basename, dirname } from 'path';
import type ts from 'typescript/lib/tsserverlibrary';
import { Logger } from '../logger';
import { findNodeAtPosition, isSvelteFilePath, isTopLevelExport, replaceDeep } from '../utils';
import { getVirtualLS, isKitExportAllowedIn, kitExports } from './sveltekit';
import { getVirtualLS, isKitRouteExportAllowedIn, kitExports } from './sveltekit';

type _ts = typeof ts;

Expand Down Expand Up @@ -52,7 +52,7 @@ export function decorateCompletions(
if (node && isTopLevelExport(ts, node, source)) {
return {
entries: Object.entries(kitExports)
.filter(([, value]) => isKitExportAllowedIn(basename(fileName), value))
.filter(([, value]) => isKitRouteExportAllowedIn(basename(fileName), value))
.map(([key, value]) => ({
kind: ts.ScriptElementKind.constElement,
name: key,
Expand Down
16 changes: 12 additions & 4 deletions packages/typescript-plugin/src/language-service/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import path from 'path';
import type ts from 'typescript/lib/tsserverlibrary';
import { Logger } from '../logger';
import { findExports, findIdentifier, isSvelteFilePath } from '../utils';
import { getVirtualLS, isKitExportAllowedIn, kitExports } from './sveltekit';
import { getVirtualLS, isKitRouteExportAllowedIn, kitExports } from './sveltekit';

type _ts = typeof ts;

Expand Down Expand Up @@ -139,6 +140,13 @@ function getKitDiagnostics<
` (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
diagnostic = {
...diagnostic,
// adjust length so it doesn't spill over to the next line
length: 1
};
} else {
continue;
}
Expand All @@ -156,11 +164,11 @@ function getKitDiagnostics<
// We're in a Svelte file - check top level exports
// We're using the original file to have the correct position without mapping
const source = info.languageService.getProgram()?.getSourceFile(fileName);
const basename = fileName.split('/').pop() || '';
const basename = path.basename(fileName);
const validExports = Object.keys(kitExports).filter((key) =>
isKitExportAllowedIn(basename, kitExports[key])
isKitRouteExportAllowedIn(basename, kitExports[key])
);
if (source) {
if (source && basename.startsWith('+')) {
const exports = findExports(ts, source, /* irrelevant */ false);
for (const exportName of exports.keys()) {
if (!validExports.includes(exportName) && !exportName.startsWith('_')) {
Expand Down
Loading