Skip to content

Commit

Permalink
breaking: project reference support (#2463)
Browse files Browse the repository at this point in the history
- 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)
  • Loading branch information
jasonlyu123 authored Aug 27, 2024
1 parent d6a2031 commit 8f5904a
Show file tree
Hide file tree
Showing 46 changed files with 1,181 additions and 492 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { dirname, join } from 'path';
import ts from 'typescript';
import { RelativePattern, TextDocumentContentChangeEvent } from 'vscode-languageserver';
import {
PublishDiagnosticsParams,
RelativePattern,
TextDocumentContentChangeEvent
} from 'vscode-languageserver';
import { Document, DocumentManager } from '../../lib/documents';
import { LSConfigManager } from '../../ls-config';
import {
Expand Down Expand Up @@ -37,6 +41,7 @@ interface LSAndTSDocResolverOptions {
tsconfigPath?: string;

onProjectReloaded?: () => void;
reportConfigError?: (diagnostic: PublishDiagnosticsParams) => void;
watch?: boolean;
tsSystem?: ts.System;
watchDirectory?: (patterns: RelativePattern[]) => void;
Expand All @@ -50,14 +55,10 @@ export class LSAndTSDocResolver {
private readonly configManager: LSConfigManager,
private readonly options?: LSAndTSDocResolverOptions
) {
const handleDocumentChange = (document: Document) => {
// This refreshes the document in the ts language service
this.getSnapshot(document);
};
docManager.on(
'documentChange',
debounceSameArg(
handleDocumentChange,
this.updateSnapshot.bind(this),
(newDoc, prevDoc) => newDoc.uri === prevDoc?.uri,
1000
)
Expand All @@ -68,7 +69,11 @@ export class LSAndTSDocResolver {
// where multiple files and their dependencies
// being loaded in a short period of times
docManager.on('documentOpen', (document) => {
handleDocumentChange(document);
if (document.openedByClient) {
this.getOrCreateSnapshot(document);
} else {
this.updateSnapshot(document);
}
docManager.lockDocument(document.uri);
});

Expand Down Expand Up @@ -121,7 +126,8 @@ export class LSAndTSDocResolver {
watchDirectory: this.options?.watchDirectory
? this.watchDirectory.bind(this)
: undefined,
nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern
nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern,
reportConfigError: this.options?.reportConfigError
};
}

Expand Down Expand Up @@ -151,18 +157,20 @@ export class LSAndTSDocResolver {
private lsDocumentContext: LanguageServiceDocumentContext;
private readonly watchedDirectories: FileSet;

async getLSForPath(path: string) {
return (await this.getTSService(path)).getService();
}

async getLSAndTSDoc(document: Document): Promise<{
tsDoc: SvelteDocumentSnapshot;
lang: ts.LanguageService;
userPreferences: ts.UserPreferences;
lsContainer: LanguageServiceContainer;
}> {
const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document);

return { tsDoc, lang: lsContainer.getService(), userPreferences };
return {
tsDoc,
lang: lsContainer.getService(),
userPreferences,
lsContainer
};
}

/**
Expand All @@ -181,7 +189,7 @@ export class LSAndTSDocResolver {

private async getLSAndTSDocWorker(document: Document) {
const lsContainer = await this.getTSService(document.getFilePath() || '');
const tsDoc = await this.getSnapshot(document);
const tsDoc = await this.getOrCreateSnapshot(document);
const userPreferences = this.getUserPreferences(tsDoc);

return { tsDoc, lsContainer, userPreferences };
Expand All @@ -192,13 +200,21 @@ export class LSAndTSDocResolver {
* the ts service it primarily belongs into.
* The update is mirrored in all other services, too.
*/
async getSnapshot(document: Document): Promise<SvelteDocumentSnapshot>;
async getSnapshot(pathOrDoc: string | Document): Promise<DocumentSnapshot>;
async getSnapshot(pathOrDoc: string | Document) {
async getOrCreateSnapshot(document: Document): Promise<SvelteDocumentSnapshot>;
async getOrCreateSnapshot(pathOrDoc: string | Document): Promise<DocumentSnapshot>;
async getOrCreateSnapshot(pathOrDoc: string | Document) {
const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || '';
const tsService = await this.getTSService(filePath);
return tsService.updateSnapshot(pathOrDoc);
}
private async updateSnapshot(document: Document) {
const filePath = document.getFilePath();
if (!filePath) {
return;
}
// ensure no new service is created
await this.updateExistingFile(filePath, (service) => service.updateSnapshot(document));
}

/**
* Updates snapshot path in all existing ts services and retrieves snapshot
Expand All @@ -217,7 +233,7 @@ export class LSAndTSDocResolver {
});
} else {
// This may not be a file but a directory, still try
await this.getSnapshot(newPath);
await this.getOrCreateSnapshot(newPath);
}
}

Expand Down Expand Up @@ -280,19 +296,11 @@ export class LSAndTSDocResolver {
});
}

/**
* @internal Public for tests only
*/
async getSnapshotManager(filePath: string): Promise<SnapshotManager> {
return (await this.getTSService(filePath)).snapshotManager;
}

async getTSService(filePath?: string): Promise<LanguageServiceContainer> {
if (this.options?.tsconfigPath) {
return getServiceForTsconfig(
this.options?.tsconfigPath,
dirname(this.options.tsconfigPath),
this.lsDocumentContext
return this.getTSServiceByConfigPath(
this.options.tsconfigPath,
dirname(this.options.tsconfigPath)
);
}
if (!filePath) {
Expand All @@ -301,6 +309,13 @@ export class LSAndTSDocResolver {
return getService(filePath, this.workspaceUris, this.lsDocumentContext);
}

async getTSServiceByConfigPath(
tsconfigPath: string,
workspacePath: string
): Promise<LanguageServiceContainer> {
return getServiceForTsconfig(tsconfigPath, workspacePath, this.lsDocumentContext);
}

private getUserPreferences(tsDoc: DocumentSnapshot): ts.UserPreferences {
const configLang =
tsDoc.scriptKind === ts.ScriptKind.TS || tsDoc.scriptKind === ts.ScriptKind.TSX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ export class SnapshotManager {
return Array.from(this.projectFileToOriginalCasing.values());
}

isProjectFile(fileName: string): boolean {
fileName = normalizePath(fileName);
return this.projectFileToOriginalCasing.has(this.getCanonicalFileName(fileName));
}

private logStatistics() {
const date = new Date();
// Don't use setInterval because that will keep tests running forever
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ export class TypeScriptPlugin
this.completionProvider,
configManager
);
this.updateImportsProvider = new UpdateImportsProviderImpl(this.lsAndTsDocResolver);
this.updateImportsProvider = new UpdateImportsProviderImpl(
this.lsAndTsDocResolver,
ts.sys.useCaseSensitiveFileNames
);
this.diagnosticsProvider = new DiagnosticsProviderImpl(
this.lsAndTsDocResolver,
configManager
Expand Down Expand Up @@ -383,7 +386,7 @@ export class TypeScriptPlugin
}

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

const defs = lang.getDefinitionAndBoundSpan(
tsDoc.filePath,
Expand All @@ -394,7 +397,7 @@ export class TypeScriptPlugin
return [];
}

const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);

const result = await Promise.all(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider {
position: Position,
cancellationToken?: CancellationToken
): Promise<CallHierarchyItem[] | null> {
const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);

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

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

const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);

const program = lang.getProgram();
Expand Down Expand Up @@ -251,16 +251,17 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider {
return null;
}

const lang = await this.lsAndTsDocResolver.getLSForPath(filePath);
const tsDoc = await this.lsAndTsDocResolver.getSnapshot(filePath);
const lsContainer = await this.lsAndTsDocResolver.getTSService(filePath);
const lang = lsContainer.getService();
const tsDoc = await this.lsAndTsDocResolver.getOrCreateSnapshot(filePath);

if (cancellationToken?.isCancellationRequested) {
return null;
}

const program = lang.getProgram();

const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);

const isComponentModulePosition =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
import { CompletionsProviderImpl } from './CompletionProvider';
import {
findClosestContainingNode,
findContainingNode,
FormatCodeBasis,
getFormatCodeBasis,
getNewScriptStartTag,
Expand All @@ -56,6 +55,7 @@ import {
} from './utils';
import { DiagnosticCode } from './DiagnosticsProvider';
import { createGetCanonicalFileName } from '../../../utils';
import { LanguageServiceContainer } from '../service';

/**
* TODO change this to protocol constant if it's part of the protocol
Expand Down Expand Up @@ -156,7 +156,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
return codeAction;
}

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

const isImportFix = codeAction.data.fixName === FIX_IMPORT_FIX_NAME;
const virtualDocInfo = isImportFix
? await this.createVirtualDocumentForCombinedImportCodeFix(
? this.createVirtualDocumentForCombinedImportCodeFix(
document,
getDiagnostics(),
tsDoc,
lsContainer,
lang
)
: undefined;
Expand Down Expand Up @@ -218,7 +219,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
await this.lsAndTsDocResolver.deleteSnapshot(virtualDocPath);
}

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

return {
virtualDoc,
Expand Down Expand Up @@ -553,7 +553,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
context: CodeActionContext,
cancellationToken: CancellationToken | undefined
) {
const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document);
const { lang, tsDoc, userPreferences, lsContainer } = await this.getLSAndTSDoc(document);

if (cancellationToken?.isCancellationRequested) {
return [];
Expand Down Expand Up @@ -613,7 +613,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
);
}

const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);

const codeActionsPromises = codeFixes.map(async (fix) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
isPartOfImportStatement
} from './utils';
import { isInTag as svelteIsInTag } from '../svelte-ast-utils';
import { LanguageServiceContainer } from '../service';

export interface CompletionResolveInfo
extends Pick<ts.CompletionEntry, 'data' | 'name' | 'source'>,
Expand Down Expand Up @@ -170,7 +171,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
return null;
}

const { lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
const { lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
if (cancellationToken?.isCancellationRequested) {
return null;
}
Expand All @@ -192,7 +193,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
const tagCompletions =
componentInfo || eventAndSlotLetCompletions.length > 0
? []
: await this.getCustomElementCompletions(lang, document, tsDoc, position);
: this.getCustomElementCompletions(lang, lsContainer, document, tsDoc, position);

const formatSettings = await this.configManager.getFormatCodeSettingsForFile(
document,
Expand Down Expand Up @@ -474,12 +475,13 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
];
}

private async getCustomElementCompletions(
private getCustomElementCompletions(
lang: ts.LanguageService,
lsContainer: LanguageServiceContainer,
document: Document,
tsDoc: SvelteDocumentSnapshot,
position: Position
): Promise<CompletionItem[] | undefined> {
): CompletionItem[] | undefined {
const offset = document.offsetAt(position);
const tag = getNodeIfIsInHTMLStartTag(document.html, offset);

Expand All @@ -499,9 +501,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionRe
return;
}

const typingsNamespace = (
await this.lsAndTsDocResolver.getTSService(tsDoc.filePath)
)?.getTsConfigSvelteOptions().namespace;
const typingsNamespace = lsContainer.getTsConfigSvelteOptions().namespace;

const typingsNamespaceSymbol = this.findTypingsNamespaceSymbol(
typingsNamespace,
Expand Down
Loading

0 comments on commit 8f5904a

Please sign in to comment.