Skip to content

Commit 0b8af82

Browse files
authored
feat: svelte-check --incremental / --tsgo (#2932)
Adds two new flags: - `--incremental`: Opts into TypeScript's incremental build cache, which speeds up subsequent runs. Saved within `.svelte-kit` or if not available within `.svelte-check`. This might result in slightly different type check outcomes, and certain patterns are not supported. Specifically, anything that is not in the root dir of your tsconfig.json and is a Svelte file will not be properly loaded and type-checked. - `--tsgo`: Use TypeScript's Go implementation. Needs to have `@typescript/native-preview` installed. Subject to the same limitations as `--incremental` Part of #2733 closes #2131 Huge kudos to https://github.com/astralhpi/svelte-fast-check and https://svelte-check-rs.vercel.app/ , both of which heavily inspired this PR
1 parent e9f58d2 commit 0b8af82

File tree

41 files changed

+1973
-218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1973
-218
lines changed

.changeset/clean-clocks-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte-check': minor
3+
---
4+
5+
feat: provide `--incremental` and `--tsgo` flags

.changeset/silver-trees-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte2tsx': patch
3+
---
4+
5+
chore: add option to output pure jsdoc-based JS files

.changeset/wild-crabs-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte-language-server': patch
3+
---
4+
5+
chore: provide utils for svelte-check
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
export * from './server';
22
export { offsetAt } from './lib/documents';
3-
export { SvelteCheck, SvelteCheckOptions, SvelteCheckDiagnosticSource } from './svelte-check';
3+
export {
4+
mapSvelteCheckDiagnostics,
5+
SvelteCheck,
6+
SvelteCheckDiagnosticSource,
7+
SvelteCheckOptions
8+
} from './svelte-check';

packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ts from 'typescript';
1+
import ts, { flattenDiagnosticMessageText } from 'typescript';
22
import { CancellationToken, Diagnostic, DiagnosticSeverity, Range } from 'vscode-languageserver';
33
import {
44
Document,
@@ -101,8 +101,25 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
101101
diagnostics.push(...checker.call(lang, tsDoc.filePath));
102102
}
103103

104+
return mapAndFilterDiagnostics(diagnostics, document, tsDoc, isTypescript, lang);
105+
}
106+
107+
private async getLSAndTSDoc(document: Document) {
108+
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
109+
}
110+
}
111+
112+
export function mapAndFilterDiagnostics(
113+
diagnostics: ts.Diagnostic[],
114+
document: Document,
115+
tsDoc: SvelteDocumentSnapshot,
116+
isTypescript: boolean,
117+
lang?: ts.LanguageService
118+
): Diagnostic[] {
119+
const notGenerated = isNotGenerated(tsDoc.getFullText());
120+
121+
if (lang) {
104122
const additionalStoreDiagnostics: ts.Diagnostic[] = [];
105-
const notGenerated = isNotGenerated(tsDoc.getFullText());
106123
for (const diagnostic of diagnostics) {
107124
if (
108125
(diagnostic.code === DiagnosticCode.NO_OVERLOAD_MATCHES_CALL ||
@@ -132,46 +149,44 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
132149
}
133150
}
134151
diagnostics.push(...additionalStoreDiagnostics);
152+
}
135153

136-
diagnostics = diagnostics
137-
.filter(notGenerated)
138-
.filter(not(isUnusedReactiveStatementLabel))
139-
.filter((diagnostics) => !expectedTransitionThirdArgument(diagnostics, tsDoc, lang));
154+
diagnostics = diagnostics
155+
.filter(notGenerated)
156+
.filter(not(isUnusedReactiveStatementLabel))
157+
.filter((diagnostic) => !expectedTransitionThirdArgument(diagnostic, tsDoc, lang));
140158

159+
if (lang) {
141160
diagnostics = resolveNoopsInReactiveStatements(lang, diagnostics);
161+
}
142162

143-
const mapRange = rangeMapper(tsDoc, document, lang);
144-
const noFalsePositive = isNoFalsePositive(document, tsDoc);
145-
const converted: Diagnostic[] = [];
146-
147-
for (const tsDiag of diagnostics) {
148-
let diagnostic: Diagnostic = {
149-
range: convertRange(tsDoc, tsDiag),
150-
severity: mapSeverity(tsDiag.category),
151-
source: isTypescript ? 'ts' : 'js',
152-
message: ts.flattenDiagnosticMessageText(tsDiag.messageText, '\n'),
153-
code: tsDiag.code,
154-
tags: getDiagnosticTag(tsDiag)
155-
};
156-
diagnostic = mapRange(diagnostic);
157-
158-
moveBindingErrorMessage(tsDiag, tsDoc, diagnostic, document);
163+
const mapRange = rangeMapper(tsDoc, document, lang);
164+
const noFalsePositive = isNoFalsePositive(document, tsDoc);
165+
const converted: Diagnostic[] = [];
166+
167+
for (const tsDiag of diagnostics) {
168+
let diagnostic: Diagnostic = {
169+
range: convertRange(tsDoc, tsDiag),
170+
severity: mapSeverity(tsDiag.category),
171+
source: isTypescript ? 'ts' : 'js',
172+
message: ts.flattenDiagnosticMessageText(tsDiag.messageText, '\n'),
173+
code: tsDiag.code,
174+
tags: getDiagnosticTag(tsDiag)
175+
};
176+
diagnostic = mapRange(diagnostic);
159177

160-
if (!hasNoNegativeLines(diagnostic) || !noFalsePositive(diagnostic)) {
161-
continue;
162-
}
178+
moveBindingErrorMessage(tsDiag, tsDoc, diagnostic, document);
163179

164-
diagnostic = adjustIfNecessary(diagnostic, tsDoc.isSvelte5Plus);
165-
diagnostic = swapDiagRangeStartEndIfNecessary(diagnostic);
166-
converted.push(diagnostic);
180+
if (!hasNoNegativeLines(diagnostic) || !noFalsePositive(diagnostic)) {
181+
continue;
167182
}
168183

169-
return converted;
184+
diagnostic = adjustIfNecessary(diagnostic, tsDoc.isSvelte5Plus);
185+
diagnostic = swapDiagRangeStartEndIfNecessary(diagnostic);
186+
converted.push(diagnostic);
170187
}
171188

172-
private async getLSAndTSDoc(document: Document) {
173-
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
174-
}
189+
return converted;
175190
}
176191

177192
function moveBindingErrorMessage(
@@ -231,17 +246,21 @@ function moveBindingErrorMessage(
231246
function rangeMapper(
232247
snapshot: SvelteDocumentSnapshot,
233248
document: Document,
234-
lang: ts.LanguageService
249+
lang?: ts.LanguageService
235250
): (value: Diagnostic) => Diagnostic {
236-
const get$$PropsDefWithCache = memoize(() => get$$PropsDef(lang, snapshot));
251+
const get$$PropsDefWithCache = memoize(() =>
252+
lang ? get$$PropsDef(lang, snapshot) : undefined
253+
);
237254
const get$$PropsAliasInfoWithCache = memoize(() =>
238-
get$$PropsAliasForInfo(get$$PropsDefWithCache, lang, document)
255+
lang ? get$$PropsAliasForInfo(get$$PropsDefWithCache, lang, document) : undefined
239256
);
240257

241258
return (diagnostic) => {
242259
let range = mapRangeToOriginal(snapshot, diagnostic.range);
243260

244-
if (range.start.line < 0) {
261+
// When lang is unavailable (incremental CLI path), we can't remap negative-line
262+
// diagnostics. This only affects deprecated $$Props syntax, so it's acceptable.
263+
if (range.start.line < 0 && lang) {
245264
range =
246265
movePropsErrorRangeBackIfNecessary(
247266
diagnostic,
@@ -605,7 +624,7 @@ function movePropsErrorRangeBackIfNecessary(
605624
function expectedTransitionThirdArgument(
606625
diagnostic: ts.Diagnostic,
607626
tsDoc: SvelteDocumentSnapshot,
608-
lang: ts.LanguageService
627+
lang?: ts.LanguageService
609628
) {
610629
if (
611630
diagnostic.code !== DiagnosticCode.EXPECTED_N_ARGUMENTS ||
@@ -631,6 +650,13 @@ function expectedTransitionThirdArgument(
631650
ts.isCallExpression
632651
);
633652

653+
if (!lang) {
654+
// Without a language service we can't resolve the signature to check parameter count.
655+
// Fall back to checking the message text for " 3" (i.e. "Expected 3 arguments").
656+
// This is only for the __sveltets_2_ensureTransition wrapper and unlikely to false-positive.
657+
return flattenDiagnosticMessageText(diagnostic.messageText, '\n').includes(' 3');
658+
}
659+
634660
const signature =
635661
callExpression && lang.getProgram()?.getTypeChecker().getResolvedSignature(callExpression);
636662

packages/language-server/src/svelte-check.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,35 @@ import {
1414
} from './plugins';
1515
import { FileSystemProvider } from './lib/FileSystemProvider';
1616
import { createLanguageServices } from './plugins/css/service';
17-
import { JSOrTSDocumentSnapshot } from './plugins/typescript/DocumentSnapshot';
17+
import {
18+
DocumentSnapshot,
19+
JSOrTSDocumentSnapshot,
20+
SvelteDocumentSnapshot,
21+
SvelteSnapshotOptions
22+
} from './plugins/typescript/DocumentSnapshot';
1823
import { isInGeneratedCode } from './plugins/typescript/features/utils';
24+
import { mapAndFilterDiagnostics } from './plugins/typescript/features/DiagnosticsProvider';
1925
import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils';
2026
import { pathToUrl, urlToPath } from './utils';
2127
import { groupBy } from 'lodash';
2228

29+
export function mapSvelteCheckDiagnostics(
30+
sourcePath: string,
31+
sourceText: string,
32+
isTsFile: boolean,
33+
tsDiagnostics: ts.Diagnostic[]
34+
): Diagnostic[] {
35+
const document = new Document(pathToUrl(sourcePath), sourceText);
36+
const snapshot = DocumentSnapshot.fromDocument(document, {
37+
parse: document.compiler?.parse,
38+
version: document.compiler?.VERSION,
39+
transformOnTemplateError: false,
40+
typingsNamespace: 'svelteHTML'
41+
} satisfies SvelteSnapshotOptions) as SvelteDocumentSnapshot;
42+
43+
return mapAndFilterDiagnostics(tsDiagnostics, document, snapshot, isTsFile, undefined);
44+
}
45+
2346
export type SvelteCheckDiagnosticSource = 'js' | 'css' | 'svelte';
2447

2548
export interface SvelteCheckOptions {

packages/svelte-check/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
dist/
22
.vscode/
33
node_modules/
4+
.svelte-check/

0 commit comments

Comments
 (0)