Skip to content

Commit 72df051

Browse files
Merge branch 'sveltejs:master' into master
2 parents 448b11f + 2142753 commit 72df051

File tree

46 files changed

+2056
-223
lines changed

Some content is hidden

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

46 files changed

+2056
-223
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/fix-unix-sockets.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte-check': patch
3+
---
4+
5+
fix: ignore Unix domain sockets in file watcher to prevent crashes

.changeset/good-steaks-judge.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+
feat: add links to diagnostic error codes via codeDescription

.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/svelte/features/getDiagnostics.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
CancellationToken,
3+
CodeDescription,
34
Diagnostic,
45
DiagnosticSeverity,
56
Position,
@@ -77,7 +78,7 @@ async function tryGetDiagnostics(
7778
.map((warning) => {
7879
const start = warning.start || { line: 1, column: 0 };
7980
const end = warning.end || start;
80-
return {
81+
const result: Diagnostic = {
8182
range: Range.create(start.line - 1, start.column, end.line - 1, end.column),
8283
message: warning.message,
8384
severity:
@@ -87,6 +88,14 @@ async function tryGetDiagnostics(
8788
source: 'svelte',
8889
code: warning.code
8990
};
91+
const codeDescription = getCodeDescription(
92+
warning.code,
93+
'https://svelte.dev/docs/svelte/compiler-warnings#'
94+
);
95+
if (codeDescription) {
96+
result.codeDescription = codeDescription;
97+
}
98+
return result;
9099
})
91100
.map((diag) => mapObjWithRangeToOriginal(transpiled, diag))
92101
.map((diag) => adjustMappings(diag, document))
@@ -154,9 +163,34 @@ function createParserErrorDiagnostic(error: any, document: Document) {
154163
}
155164
}
156165

166+
const codeDescription = getCodeDescription(
167+
error.code,
168+
'https://svelte.dev/docs/svelte/compiler-errors#'
169+
);
170+
if (codeDescription) {
171+
diagnostic.codeDescription = codeDescription;
172+
}
173+
157174
return [diagnostic];
158175
}
159176

177+
function getCodeDescription(code: unknown, url: string): CodeDescription | undefined {
178+
if (typeof code !== 'string') {
179+
return undefined;
180+
}
181+
182+
const firstChar = code.charCodeAt(0);
183+
if (isLowerLetter(firstChar) && (code.includes('-') || code.includes('_'))) {
184+
return {
185+
href: url + code.replace(/-/g, '_')
186+
};
187+
}
188+
}
189+
190+
function isLowerLetter(charCode: number) {
191+
return charCode >= 97 && charCode <= 122;
192+
}
193+
160194
/**
161195
* Try to infer a nice diagnostic error message from the transpilation error.
162196
*/

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/language-server/test/plugins/svelte/SveltePlugin.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ describe('Svelte Plugin', () => {
4545
isSvelte5Plus ? 'a11y_missing_attribute' : 'a11y-missing-attribute',
4646
'svelte'
4747
);
48+
diagnostic.codeDescription = {
49+
href: 'https://svelte.dev/docs/svelte/compiler-warnings#a11y_missing_attribute'
50+
};
4851

4952
assert.deepStrictEqual(diagnostics, [diagnostic]);
5053
});
@@ -63,6 +66,13 @@ describe('Svelte Plugin', () => {
6366
'svelte'
6467
);
6568

69+
diagnostic.codeDescription = {
70+
href: isSvelte5Plus
71+
? 'https://svelte.dev/docs/svelte/compiler-errors#bind_invalid_name'
72+
: // doesn't actually exist but at least points to the page
73+
'https://svelte.dev/docs/svelte/compiler-errors#binding_undeclared'
74+
};
75+
6676
assert.deepStrictEqual(diagnostics, [diagnostic]);
6777
});
6878

0 commit comments

Comments
 (0)