Skip to content

Commit 8f5904a

Browse files
authored
breaking: project reference support (#2463)
- closes #2148 - fixes #1234 - due to the change of how files are resolved (breaking change) - fixes #1976 - less options are now forced (breaking change) - fixes #2154 (breaking change)
1 parent d6a2031 commit 8f5904a

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

+1181
-492
lines changed

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

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { dirname, join } from 'path';
22
import ts from 'typescript';
3-
import { RelativePattern, TextDocumentContentChangeEvent } from 'vscode-languageserver';
3+
import {
4+
PublishDiagnosticsParams,
5+
RelativePattern,
6+
TextDocumentContentChangeEvent
7+
} from 'vscode-languageserver';
48
import { Document, DocumentManager } from '../../lib/documents';
59
import { LSConfigManager } from '../../ls-config';
610
import {
@@ -37,6 +41,7 @@ interface LSAndTSDocResolverOptions {
3741
tsconfigPath?: string;
3842

3943
onProjectReloaded?: () => void;
44+
reportConfigError?: (diagnostic: PublishDiagnosticsParams) => void;
4045
watch?: boolean;
4146
tsSystem?: ts.System;
4247
watchDirectory?: (patterns: RelativePattern[]) => void;
@@ -50,14 +55,10 @@ export class LSAndTSDocResolver {
5055
private readonly configManager: LSConfigManager,
5156
private readonly options?: LSAndTSDocResolverOptions
5257
) {
53-
const handleDocumentChange = (document: Document) => {
54-
// This refreshes the document in the ts language service
55-
this.getSnapshot(document);
56-
};
5758
docManager.on(
5859
'documentChange',
5960
debounceSameArg(
60-
handleDocumentChange,
61+
this.updateSnapshot.bind(this),
6162
(newDoc, prevDoc) => newDoc.uri === prevDoc?.uri,
6263
1000
6364
)
@@ -68,7 +69,11 @@ export class LSAndTSDocResolver {
6869
// where multiple files and their dependencies
6970
// being loaded in a short period of times
7071
docManager.on('documentOpen', (document) => {
71-
handleDocumentChange(document);
72+
if (document.openedByClient) {
73+
this.getOrCreateSnapshot(document);
74+
} else {
75+
this.updateSnapshot(document);
76+
}
7277
docManager.lockDocument(document.uri);
7378
});
7479

@@ -121,7 +126,8 @@ export class LSAndTSDocResolver {
121126
watchDirectory: this.options?.watchDirectory
122127
? this.watchDirectory.bind(this)
123128
: undefined,
124-
nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern
129+
nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern,
130+
reportConfigError: this.options?.reportConfigError
125131
};
126132
}
127133

@@ -151,18 +157,20 @@ export class LSAndTSDocResolver {
151157
private lsDocumentContext: LanguageServiceDocumentContext;
152158
private readonly watchedDirectories: FileSet;
153159

154-
async getLSForPath(path: string) {
155-
return (await this.getTSService(path)).getService();
156-
}
157-
158160
async getLSAndTSDoc(document: Document): Promise<{
159161
tsDoc: SvelteDocumentSnapshot;
160162
lang: ts.LanguageService;
161163
userPreferences: ts.UserPreferences;
164+
lsContainer: LanguageServiceContainer;
162165
}> {
163166
const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document);
164167

165-
return { tsDoc, lang: lsContainer.getService(), userPreferences };
168+
return {
169+
tsDoc,
170+
lang: lsContainer.getService(),
171+
userPreferences,
172+
lsContainer
173+
};
166174
}
167175

168176
/**
@@ -181,7 +189,7 @@ export class LSAndTSDocResolver {
181189

182190
private async getLSAndTSDocWorker(document: Document) {
183191
const lsContainer = await this.getTSService(document.getFilePath() || '');
184-
const tsDoc = await this.getSnapshot(document);
192+
const tsDoc = await this.getOrCreateSnapshot(document);
185193
const userPreferences = this.getUserPreferences(tsDoc);
186194

187195
return { tsDoc, lsContainer, userPreferences };
@@ -192,13 +200,21 @@ export class LSAndTSDocResolver {
192200
* the ts service it primarily belongs into.
193201
* The update is mirrored in all other services, too.
194202
*/
195-
async getSnapshot(document: Document): Promise<SvelteDocumentSnapshot>;
196-
async getSnapshot(pathOrDoc: string | Document): Promise<DocumentSnapshot>;
197-
async getSnapshot(pathOrDoc: string | Document) {
203+
async getOrCreateSnapshot(document: Document): Promise<SvelteDocumentSnapshot>;
204+
async getOrCreateSnapshot(pathOrDoc: string | Document): Promise<DocumentSnapshot>;
205+
async getOrCreateSnapshot(pathOrDoc: string | Document) {
198206
const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || '';
199207
const tsService = await this.getTSService(filePath);
200208
return tsService.updateSnapshot(pathOrDoc);
201209
}
210+
private async updateSnapshot(document: Document) {
211+
const filePath = document.getFilePath();
212+
if (!filePath) {
213+
return;
214+
}
215+
// ensure no new service is created
216+
await this.updateExistingFile(filePath, (service) => service.updateSnapshot(document));
217+
}
202218

203219
/**
204220
* Updates snapshot path in all existing ts services and retrieves snapshot
@@ -217,7 +233,7 @@ export class LSAndTSDocResolver {
217233
});
218234
} else {
219235
// This may not be a file but a directory, still try
220-
await this.getSnapshot(newPath);
236+
await this.getOrCreateSnapshot(newPath);
221237
}
222238
}
223239

@@ -280,19 +296,11 @@ export class LSAndTSDocResolver {
280296
});
281297
}
282298

283-
/**
284-
* @internal Public for tests only
285-
*/
286-
async getSnapshotManager(filePath: string): Promise<SnapshotManager> {
287-
return (await this.getTSService(filePath)).snapshotManager;
288-
}
289-
290299
async getTSService(filePath?: string): Promise<LanguageServiceContainer> {
291300
if (this.options?.tsconfigPath) {
292-
return getServiceForTsconfig(
293-
this.options?.tsconfigPath,
294-
dirname(this.options.tsconfigPath),
295-
this.lsDocumentContext
301+
return this.getTSServiceByConfigPath(
302+
this.options.tsconfigPath,
303+
dirname(this.options.tsconfigPath)
296304
);
297305
}
298306
if (!filePath) {
@@ -301,6 +309,13 @@ export class LSAndTSDocResolver {
301309
return getService(filePath, this.workspaceUris, this.lsDocumentContext);
302310
}
303311

312+
async getTSServiceByConfigPath(
313+
tsconfigPath: string,
314+
workspacePath: string
315+
): Promise<LanguageServiceContainer> {
316+
return getServiceForTsconfig(tsconfigPath, workspacePath, this.lsDocumentContext);
317+
}
318+
304319
private getUserPreferences(tsDoc: DocumentSnapshot): ts.UserPreferences {
305320
const configLang =
306321
tsDoc.scriptKind === ts.ScriptKind.TS || tsDoc.scriptKind === ts.ScriptKind.TSX

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ export class SnapshotManager {
262262
return Array.from(this.projectFileToOriginalCasing.values());
263263
}
264264

265+
isProjectFile(fileName: string): boolean {
266+
fileName = normalizePath(fileName);
267+
return this.projectFileToOriginalCasing.has(this.getCanonicalFileName(fileName));
268+
}
269+
265270
private logStatistics() {
266271
const date = new Date();
267272
// Don't use setInterval because that will keep tests running forever

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ export class TypeScriptPlugin
168168
this.completionProvider,
169169
configManager
170170
);
171-
this.updateImportsProvider = new UpdateImportsProviderImpl(this.lsAndTsDocResolver);
171+
this.updateImportsProvider = new UpdateImportsProviderImpl(
172+
this.lsAndTsDocResolver,
173+
ts.sys.useCaseSensitiveFileNames
174+
);
172175
this.diagnosticsProvider = new DiagnosticsProviderImpl(
173176
this.lsAndTsDocResolver,
174177
configManager
@@ -383,7 +386,7 @@ export class TypeScriptPlugin
383386
}
384387

385388
async getDefinitions(document: Document, position: Position): Promise<DefinitionLink[]> {
386-
const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
389+
const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
387390

388391
const defs = lang.getDefinitionAndBoundSpan(
389392
tsDoc.filePath,
@@ -394,7 +397,7 @@ export class TypeScriptPlugin
394397
return [];
395398
}
396399

397-
const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
400+
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
398401
snapshots.set(tsDoc.filePath, tsDoc);
399402

400403
const result = await Promise.all(

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider {
4141
position: Position,
4242
cancellationToken?: CancellationToken
4343
): Promise<CallHierarchyItem[] | null> {
44-
const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
44+
const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
4545

4646
if (cancellationToken?.isCancellationRequested) {
4747
return null;
@@ -52,7 +52,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider {
5252

5353
const itemsArray = Array.isArray(items) ? items : items ? [items] : [];
5454

55-
const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
55+
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
5656
snapshots.set(tsDoc.filePath, tsDoc);
5757

5858
const program = lang.getProgram();
@@ -251,16 +251,17 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider {
251251
return null;
252252
}
253253

254-
const lang = await this.lsAndTsDocResolver.getLSForPath(filePath);
255-
const tsDoc = await this.lsAndTsDocResolver.getSnapshot(filePath);
254+
const lsContainer = await this.lsAndTsDocResolver.getTSService(filePath);
255+
const lang = lsContainer.getService();
256+
const tsDoc = await this.lsAndTsDocResolver.getOrCreateSnapshot(filePath);
256257

257258
if (cancellationToken?.isCancellationRequested) {
258259
return null;
259260
}
260261

261262
const program = lang.getProgram();
262263

263-
const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
264+
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
264265
snapshots.set(tsDoc.filePath, tsDoc);
265266

266267
const isComponentModulePosition =

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import {
4646
import { CompletionsProviderImpl } from './CompletionProvider';
4747
import {
4848
findClosestContainingNode,
49-
findContainingNode,
5049
FormatCodeBasis,
5150
getFormatCodeBasis,
5251
getNewScriptStartTag,
@@ -56,6 +55,7 @@ import {
5655
} from './utils';
5756
import { DiagnosticCode } from './DiagnosticsProvider';
5857
import { createGetCanonicalFileName } from '../../../utils';
58+
import { LanguageServiceContainer } from '../service';
5959

6060
/**
6161
* TODO change this to protocol constant if it's part of the protocol
@@ -156,7 +156,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
156156
return codeAction;
157157
}
158158

159-
const { lang, tsDoc, userPreferences } =
159+
const { lang, tsDoc, userPreferences, lsContainer } =
160160
await this.lsAndTsDocResolver.getLSAndTSDoc(document);
161161
if (cancellationToken?.isCancellationRequested) {
162162
return codeAction;
@@ -180,10 +180,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
180180

181181
const isImportFix = codeAction.data.fixName === FIX_IMPORT_FIX_NAME;
182182
const virtualDocInfo = isImportFix
183-
? await this.createVirtualDocumentForCombinedImportCodeFix(
183+
? this.createVirtualDocumentForCombinedImportCodeFix(
184184
document,
185185
getDiagnostics(),
186186
tsDoc,
187+
lsContainer,
187188
lang
188189
)
189190
: undefined;
@@ -218,7 +219,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
218219
await this.lsAndTsDocResolver.deleteSnapshot(virtualDocPath);
219220
}
220221

221-
const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
222+
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
222223
const fixActions: ts.CodeFixAction[] = [
223224
{
224225
fixName: codeAction.data.fixName,
@@ -259,10 +260,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
259260
* Do not use this in regular code action
260261
* This'll cause TypeScript to rebuild and invalidate caches every time. It'll be slow
261262
*/
262-
private async createVirtualDocumentForCombinedImportCodeFix(
263+
private createVirtualDocumentForCombinedImportCodeFix(
263264
document: Document,
264265
diagnostics: Diagnostic[],
265266
tsDoc: DocumentSnapshot,
267+
lsContainer: LanguageServiceContainer,
266268
lang: ts.LanguageService
267269
) {
268270
const virtualUri = document.uri + '.__virtual__.svelte';
@@ -314,10 +316,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
314316
const virtualDoc = new Document(virtualUri, newText);
315317
virtualDoc.openedByClient = true;
316318
// let typescript know about the virtual document
317-
// getLSAndTSDoc instead of getSnapshot so that project dirty state is correctly tracked by us
318-
// otherwise, sometime the applied code fix might not be picked up by the language service
319-
// because we think the project is still dirty and doesn't update the project version
320-
await this.lsAndTsDocResolver.getLSAndTSDoc(virtualDoc);
319+
lsContainer.openVirtualDocument(virtualDoc);
320+
lsContainer.getService();
321321

322322
return {
323323
virtualDoc,
@@ -553,7 +553,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
553553
context: CodeActionContext,
554554
cancellationToken: CancellationToken | undefined
555555
) {
556-
const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document);
556+
const { lang, tsDoc, userPreferences, lsContainer } = await this.getLSAndTSDoc(document);
557557

558558
if (cancellationToken?.isCancellationRequested) {
559559
return [];
@@ -613,7 +613,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
613613
);
614614
}
615615

616-
const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
616+
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
617617
snapshots.set(tsDoc.filePath, tsDoc);
618618

619619
const codeActionsPromises = codeFixes.map(async (fix) => {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
isPartOfImportStatement
5050
} from './utils';
5151
import { isInTag as svelteIsInTag } from '../svelte-ast-utils';
52+
import { LanguageServiceContainer } from '../service';
5253

5354
export interface CompletionResolveInfo
5455
extends Pick<ts.CompletionEntry, 'data' | 'name' | 'source'>,
@@ -170,7 +171,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
170171
return null;
171172
}
172173

173-
const { lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
174+
const { lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
174175
if (cancellationToken?.isCancellationRequested) {
175176
return null;
176177
}
@@ -192,7 +193,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
192193
const tagCompletions =
193194
componentInfo || eventAndSlotLetCompletions.length > 0
194195
? []
195-
: await this.getCustomElementCompletions(lang, document, tsDoc, position);
196+
: this.getCustomElementCompletions(lang, lsContainer, document, tsDoc, position);
196197

197198
const formatSettings = await this.configManager.getFormatCodeSettingsForFile(
198199
document,
@@ -474,12 +475,13 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
474475
];
475476
}
476477

477-
private async getCustomElementCompletions(
478+
private getCustomElementCompletions(
478479
lang: ts.LanguageService,
480+
lsContainer: LanguageServiceContainer,
479481
document: Document,
480482
tsDoc: SvelteDocumentSnapshot,
481483
position: Position
482-
): Promise<CompletionItem[] | undefined> {
484+
): CompletionItem[] | undefined {
483485
const offset = document.offsetAt(position);
484486
const tag = getNodeIfIsInHTMLStartTag(document.html, offset);
485487

@@ -499,9 +501,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
499501
return;
500502
}
501503

502-
const typingsNamespace = (
503-
await this.lsAndTsDocResolver.getTSService(tsDoc.filePath)
504-
)?.getTsConfigSvelteOptions().namespace;
504+
const typingsNamespace = lsContainer.getTsConfigSvelteOptions().namespace;
505505

506506
const typingsNamespaceSymbol = this.findTypingsNamespaceSymbol(
507507
typingsNamespace,

0 commit comments

Comments
 (0)