Skip to content

Commit 72a8554

Browse files
authored
(feat) tsconfig feature for svelte-check (#1014)
Makes it possible to pass a path to a tsconfig or jsconfig file. The path can be relative to the workspace path or absolute. Doing this means that only files matched by the files/include/exclude pattern of the config file are diagnosed. It also means that errors from TypeScript and JavaScript files are reported. #390
1 parent 134e035 commit 72a8554

File tree

12 files changed

+255
-117
lines changed

12 files changed

+255
-117
lines changed

packages/language-server/src/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './css/CSSPlugin';
22
export * from './typescript/TypeScriptPlugin';
3+
export * from './typescript/LSAndTSDocResolver';
34
export * from './svelte/SveltePlugin';
45
export * from './html/HTMLPlugin';
56
export * from './PluginHost';

packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@ import { LSConfigManager } from '../../ls-config';
44
import { debounceSameArg, pathToUrl } from '../../utils';
55
import { DocumentSnapshot, SvelteDocumentSnapshot } from './DocumentSnapshot';
66
import {
7-
getLanguageServiceForDocument,
8-
getLanguageServiceForPath,
97
getService,
8+
getServiceForTsconfig,
9+
hasServiceForFile,
1010
LanguageServiceContainer,
1111
LanguageServiceDocumentContext
1212
} from './service';
1313
import { SnapshotManager } from './SnapshotManager';
1414

1515
export class LSAndTSDocResolver {
16+
/**
17+
*
18+
* @param docManager
19+
* @param workspaceUris
20+
* @param configManager
21+
* @param tsconfigPath This should only be set via svelte-check. Makes sure all documents are resolved to that tsconfig. Has to be absolute.
22+
*/
1623
constructor(
1724
private readonly docManager: DocumentManager,
1825
private readonly workspaceUris: string[],
1926
private readonly configManager: LSConfigManager,
20-
private readonly transformOnTemplateError = true
27+
private readonly tsconfigPath?: string
2128
) {
2229
const handleDocumentChange = (document: Document) => {
2330
// This refreshes the document in the ts language service
@@ -55,24 +62,20 @@ export class LSAndTSDocResolver {
5562
private get lsDocumentContext(): LanguageServiceDocumentContext {
5663
return {
5764
createDocument: this.createDocument,
58-
transformOnTemplateError: this.transformOnTemplateError
65+
transformOnTemplateError: !this.tsconfigPath
5966
};
6067
}
6168

6269
async getLSForPath(path: string) {
63-
return getLanguageServiceForPath(path, this.workspaceUris, this.lsDocumentContext);
70+
return (await this.getTSService(path)).getService();
6471
}
6572

6673
async getLSAndTSDoc(document: Document): Promise<{
6774
tsDoc: SvelteDocumentSnapshot;
6875
lang: ts.LanguageService;
6976
userPreferences: ts.UserPreferences;
7077
}> {
71-
const lang = await getLanguageServiceForDocument(
72-
document,
73-
this.workspaceUris,
74-
this.lsDocumentContext
75-
);
78+
const lang = await this.getLSForPath(document.getFilePath() || '');
7679
const tsDoc = await this.getSnapshot(document);
7780
const userPreferences = this.getUserPreferences(tsDoc.scriptKind);
7881

@@ -93,6 +96,11 @@ export class LSAndTSDocResolver {
9396
}
9497

9598
async deleteSnapshot(filePath: string) {
99+
if (!hasServiceForFile(filePath, this.workspaceUris)) {
100+
// Don't initialize a service for a file that should be deleted
101+
return;
102+
}
103+
96104
(await this.getTSService(filePath)).deleteSnapshot(filePath);
97105
this.docManager.releaseDocument(pathToUrl(filePath));
98106
}
@@ -101,7 +109,13 @@ export class LSAndTSDocResolver {
101109
return (await this.getTSService(filePath)).snapshotManager;
102110
}
103111

104-
private getTSService(filePath: string): Promise<LanguageServiceContainer> {
112+
async getTSService(filePath?: string): Promise<LanguageServiceContainer> {
113+
if (this.tsconfigPath) {
114+
return getServiceForTsconfig(this.tsconfigPath, this.lsDocumentContext);
115+
}
116+
if (!filePath) {
117+
throw new Error('Cannot call getTSService without filePath and without tsconfigPath');
118+
}
105119
return getService(filePath, this.workspaceUris, this.lsDocumentContext);
106120
}
107121

packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
CodeAction,
44
CodeActionContext,
55
CompletionContext,
6+
CompletionList,
67
DefinitionLink,
78
Diagnostic,
89
FileChangeType,
@@ -12,21 +13,15 @@ import {
1213
Position,
1314
Range,
1415
ReferenceContext,
15-
SymbolInformation,
16-
WorkspaceEdit,
17-
CompletionList,
1816
SelectionRange,
17+
SemanticTokens,
1918
SignatureHelp,
2019
SignatureHelpContext,
21-
SemanticTokens,
22-
TextDocumentContentChangeEvent
20+
SymbolInformation,
21+
TextDocumentContentChangeEvent,
22+
WorkspaceEdit
2323
} from 'vscode-languageserver';
24-
import {
25-
Document,
26-
DocumentManager,
27-
mapSymbolInformationToOriginal,
28-
getTextInRange
29-
} from '../../lib/documents';
24+
import { Document, getTextInRange, mapSymbolInformationToOriginal } from '../../lib/documents';
3025
import { LSConfigManager, LSTypescriptConfig } from '../../ls-config';
3126
import { isNotNullOrUndefined, pathToUrl } from '../../utils';
3227
import {
@@ -41,12 +36,12 @@ import {
4136
FindReferencesProvider,
4237
HoverProvider,
4338
OnWatchFileChanges,
39+
OnWatchFileChangesPara,
4440
RenameProvider,
4541
SelectionRangeProvider,
42+
SemanticTokensProvider,
4643
SignatureHelpProvider,
4744
UpdateImportsProvider,
48-
OnWatchFileChangesPara,
49-
SemanticTokensProvider,
5045
UpdateTsOrJsFile
5146
} from '../interfaces';
5247
import { CodeActionsProviderImpl } from './features/CodeActionsProvider';
@@ -55,18 +50,18 @@ import {
5550
CompletionsProviderImpl
5651
} from './features/CompletionProvider';
5752
import { DiagnosticsProviderImpl } from './features/DiagnosticsProvider';
53+
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
54+
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
5855
import { HoverProviderImpl } from './features/HoverProvider';
5956
import { RenameProviderImpl } from './features/RenameProvider';
60-
import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
61-
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
62-
import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils';
63-
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
64-
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
6557
import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider';
66-
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
67-
import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager';
6858
import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider';
59+
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
60+
import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
6961
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';
62+
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
63+
import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager';
64+
import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils';
7065

7166
export class TypeScriptPlugin
7267
implements
@@ -98,19 +93,9 @@ export class TypeScriptPlugin
9893
private readonly signatureHelpProvider: SignatureHelpProviderImpl;
9994
private readonly semanticTokensProvider: SemanticTokensProviderImpl;
10095

101-
constructor(
102-
docManager: DocumentManager,
103-
configManager: LSConfigManager,
104-
workspaceUris: string[],
105-
isEditor = true
106-
) {
96+
constructor(configManager: LSConfigManager, lsAndTsDocResolver: LSAndTSDocResolver) {
10797
this.configManager = configManager;
108-
this.lsAndTsDocResolver = new LSAndTSDocResolver(
109-
docManager,
110-
workspaceUris,
111-
configManager,
112-
/**transformOnTemplateError */ isEditor
113-
);
98+
this.lsAndTsDocResolver = lsAndTsDocResolver;
11499
this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver);
115100
this.codeActionsProvider = new CodeActionsProviderImpl(
116101
this.lsAndTsDocResolver,

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

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import ts from 'typescript';
2-
import { Diagnostic, DiagnosticSeverity, DiagnosticTag } from 'vscode-languageserver';
2+
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
33
import {
44
Document,
55
mapObjWithRangeToOriginal,
@@ -8,7 +8,7 @@ import {
88
} from '../../../lib/documents';
99
import { DiagnosticsProvider } from '../../interfaces';
1010
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
11-
import { convertRange, mapSeverity } from '../utils';
11+
import { convertRange, getDiagnosticTag, mapSeverity } from '../utils';
1212
import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
1313
import { isInGeneratedCode } from './utils';
1414

@@ -53,7 +53,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
5353
source: isTypescript ? 'ts' : 'js',
5454
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
5555
code: diagnostic.code,
56-
tags: this.getDiagnosticTag(diagnostic)
56+
tags: getDiagnosticTag(diagnostic)
5757
}))
5858
.map((diagnostic) => mapObjWithRangeToOriginal(fragment, diagnostic))
5959
.filter(hasNoNegativeLines)
@@ -62,17 +62,6 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
6262
.map(swapRangeStartEndIfNecessary);
6363
}
6464

65-
private getDiagnosticTag(diagnostic: ts.Diagnostic) {
66-
const tags: DiagnosticTag[] = [];
67-
if (diagnostic.reportsUnnecessary) {
68-
tags.push(DiagnosticTag.Unnecessary);
69-
}
70-
if (diagnostic.reportsDeprecated) {
71-
tags.push(DiagnosticTag.Deprecated);
72-
}
73-
return tags;
74-
}
75-
7665
private async getLSAndTSDoc(document: Document) {
7766
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
7867
}

packages/language-server/src/plugins/typescript/service.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export interface LanguageServiceContainer {
1616
getService(): ts.LanguageService;
1717
updateSnapshot(documentOrFilePath: Document | string): DocumentSnapshot;
1818
deleteSnapshot(filePath: string): void;
19+
/**
20+
* Careful, don't call often, or it will hurt performance.
21+
* Only works for TS versions that have ScriptKind.Deferred
22+
*/
23+
fileBelongsToProject(filePath: string): boolean;
1924
}
2025

2126
const services = new Map<string, Promise<LanguageServiceContainer>>();
@@ -25,29 +30,28 @@ export interface LanguageServiceDocumentContext {
2530
createDocument: (fileName: string, content: string) => Document;
2631
}
2732

28-
export async function getLanguageServiceForPath(
33+
export async function getService(
2934
path: string,
3035
workspaceUris: string[],
3136
docContext: LanguageServiceDocumentContext
32-
): Promise<ts.LanguageService> {
33-
return (await getService(path, workspaceUris, docContext)).getService();
37+
): Promise<LanguageServiceContainer> {
38+
const tsconfigPath = findTsConfigPath(path, workspaceUris);
39+
return getServiceForTsconfig(tsconfigPath, docContext);
3440
}
3541

36-
export async function getLanguageServiceForDocument(
37-
document: Document,
38-
workspaceUris: string[],
39-
docContext: LanguageServiceDocumentContext
40-
): Promise<ts.LanguageService> {
41-
return getLanguageServiceForPath(document.getFilePath() || '', workspaceUris, docContext);
42+
export function hasServiceForFile(path: string, workspaceUris: string[]): boolean {
43+
const tsconfigPath = findTsConfigPath(path, workspaceUris);
44+
return services.has(tsconfigPath);
4245
}
4346

44-
export async function getService(
45-
path: string,
46-
workspaceUris: string[],
47+
/**
48+
* @param tsconfigPath has to be absolute
49+
* @param docContext
50+
*/
51+
export async function getServiceForTsconfig(
52+
tsconfigPath: string,
4753
docContext: LanguageServiceDocumentContext
48-
) {
49-
const tsconfigPath = findTsConfigPath(path, workspaceUris);
50-
54+
): Promise<LanguageServiceContainer> {
5155
let service: LanguageServiceContainer;
5256
if (services.has(tsconfigPath)) {
5357
service = await services.get(tsconfigPath)!;
@@ -127,6 +131,7 @@ async function createLanguageService(
127131
getService: () => languageService,
128132
updateSnapshot,
129133
deleteSnapshot,
134+
fileBelongsToProject,
130135
snapshotManager
131136
};
132137

@@ -192,6 +197,10 @@ async function createLanguageService(
192197
return doc;
193198
}
194199

200+
function fileBelongsToProject(filePath: string): boolean {
201+
return snapshotManager.has(filePath) || getParsedConfig().fileNames.includes(filePath);
202+
}
203+
195204
function getParsedConfig() {
196205
const forcedCompilerOptions: ts.CompilerOptions = {
197206
allowNonTsExtensions: true,

packages/language-server/src/plugins/typescript/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ts from 'typescript';
33
import {
44
CompletionItemKind,
55
DiagnosticSeverity,
6+
DiagnosticTag,
67
Position,
78
Range,
89
SymbolKind
@@ -301,3 +302,14 @@ export function convertToTextSpan(range: Range, fragment: SnapshotFragment): ts.
301302
export function isInScript(position: Position, fragment: SvelteSnapshotFragment | Document) {
302303
return isInTag(position, fragment.scriptInfo) || isInTag(position, fragment.moduleScriptInfo);
303304
}
305+
306+
export function getDiagnosticTag(diagnostic: ts.Diagnostic): DiagnosticTag[] {
307+
const tags: DiagnosticTag[] = [];
308+
if (diagnostic.reportsUnnecessary) {
309+
tags.push(DiagnosticTag.Unnecessary);
310+
}
311+
if (diagnostic.reportsDeprecated) {
312+
tags.push(DiagnosticTag.Deprecated);
313+
}
314+
return tags;
315+
}

packages/language-server/src/server.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import {
3131
PluginHost,
3232
SveltePlugin,
3333
TypeScriptPlugin,
34-
OnWatchFileChangesPara
34+
OnWatchFileChangesPara,
35+
LSAndTSDocResolver
3536
} from './plugins';
3637
import { isNotNullOrUndefined, urlToPath } from './utils';
3738
import { FallbackWatcher } from './lib/FallbackWatcher';
@@ -132,7 +133,12 @@ export function startServer(options?: LSOptions) {
132133
pluginHost.register((sveltePlugin = new SveltePlugin(configManager)));
133134
pluginHost.register(new HTMLPlugin(docManager, configManager));
134135
pluginHost.register(new CSSPlugin(docManager, configManager));
135-
pluginHost.register(new TypeScriptPlugin(docManager, configManager, workspaceUris));
136+
pluginHost.register(
137+
new TypeScriptPlugin(
138+
configManager,
139+
new LSAndTSDocResolver(docManager, workspaceUris, configManager)
140+
)
141+
);
136142

137143
const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit;
138144

0 commit comments

Comments
 (0)