Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/language-server/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './css/CSSPlugin';
export * from './typescript/TypeScriptPlugin';
export * from './typescript/LSAndTSDocResolver';
export * from './svelte/SveltePlugin';
export * from './html/HTMLPlugin';
export * from './PluginHost';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,27 @@ import { LSConfigManager } from '../../ls-config';
import { debounceSameArg, pathToUrl } from '../../utils';
import { DocumentSnapshot, SvelteDocumentSnapshot } from './DocumentSnapshot';
import {
getLanguageServiceForDocument,
getLanguageServiceForPath,
getService,
getServiceForTsconfig,
hasServiceForFile,
LanguageServiceContainer,
LanguageServiceDocumentContext
} from './service';
import { SnapshotManager } from './SnapshotManager';

export class LSAndTSDocResolver {
/**
*
* @param docManager
* @param workspaceUris
* @param configManager
* @param tsconfigPath This should only be set via svelte-check. Makes sure all documents are resolved to that tsconfig. Has to be absolute.
*/
constructor(
private readonly docManager: DocumentManager,
private readonly workspaceUris: string[],
private readonly configManager: LSConfigManager,
private readonly transformOnTemplateError = true
private readonly tsconfigPath?: string
) {
const handleDocumentChange = (document: Document) => {
// This refreshes the document in the ts language service
Expand Down Expand Up @@ -55,24 +62,20 @@ export class LSAndTSDocResolver {
private get lsDocumentContext(): LanguageServiceDocumentContext {
return {
createDocument: this.createDocument,
transformOnTemplateError: this.transformOnTemplateError
transformOnTemplateError: !this.tsconfigPath
};
}

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

async getLSAndTSDoc(document: Document): Promise<{
tsDoc: SvelteDocumentSnapshot;
lang: ts.LanguageService;
userPreferences: ts.UserPreferences;
}> {
const lang = await getLanguageServiceForDocument(
document,
this.workspaceUris,
this.lsDocumentContext
);
const lang = await this.getLSForPath(document.getFilePath() || '');
const tsDoc = await this.getSnapshot(document);
const userPreferences = this.getUserPreferences(tsDoc.scriptKind);

Expand All @@ -93,6 +96,11 @@ export class LSAndTSDocResolver {
}

async deleteSnapshot(filePath: string) {
if (!hasServiceForFile(filePath, this.workspaceUris)) {
// Don't initialize a service for a file that should be deleted
return;
}

(await this.getTSService(filePath)).deleteSnapshot(filePath);
this.docManager.releaseDocument(pathToUrl(filePath));
}
Expand All @@ -101,7 +109,13 @@ export class LSAndTSDocResolver {
return (await this.getTSService(filePath)).snapshotManager;
}

private getTSService(filePath: string): Promise<LanguageServiceContainer> {
async getTSService(filePath?: string): Promise<LanguageServiceContainer> {
if (this.tsconfigPath) {
return getServiceForTsconfig(this.tsconfigPath, this.lsDocumentContext);
}
if (!filePath) {
throw new Error('Cannot call getTSService without filePath and without tsconfigPath');
}
return getService(filePath, this.workspaceUris, this.lsDocumentContext);
}

Expand Down
49 changes: 17 additions & 32 deletions packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CodeAction,
CodeActionContext,
CompletionContext,
CompletionList,
DefinitionLink,
Diagnostic,
FileChangeType,
Expand All @@ -12,21 +13,15 @@ import {
Position,
Range,
ReferenceContext,
SymbolInformation,
WorkspaceEdit,
CompletionList,
SelectionRange,
SemanticTokens,
SignatureHelp,
SignatureHelpContext,
SemanticTokens,
TextDocumentContentChangeEvent
SymbolInformation,
TextDocumentContentChangeEvent,
WorkspaceEdit
} from 'vscode-languageserver';
import {
Document,
DocumentManager,
mapSymbolInformationToOriginal,
getTextInRange
} from '../../lib/documents';
import { Document, getTextInRange, mapSymbolInformationToOriginal } from '../../lib/documents';
import { LSConfigManager, LSTypescriptConfig } from '../../ls-config';
import { isNotNullOrUndefined, pathToUrl } from '../../utils';
import {
Expand All @@ -41,12 +36,12 @@ import {
FindReferencesProvider,
HoverProvider,
OnWatchFileChanges,
OnWatchFileChangesPara,
RenameProvider,
SelectionRangeProvider,
SemanticTokensProvider,
SignatureHelpProvider,
UpdateImportsProvider,
OnWatchFileChangesPara,
SemanticTokensProvider,
UpdateTsOrJsFile
} from '../interfaces';
import { CodeActionsProviderImpl } from './features/CodeActionsProvider';
Expand All @@ -55,18 +50,18 @@ import {
CompletionsProviderImpl
} from './features/CompletionProvider';
import { DiagnosticsProviderImpl } from './features/DiagnosticsProvider';
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
import { HoverProviderImpl } from './features/HoverProvider';
import { RenameProviderImpl } from './features/RenameProvider';
import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils';
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider';
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager';
import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider';
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';
import { LSAndTSDocResolver } from './LSAndTSDocResolver';
import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager';
import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils';

export class TypeScriptPlugin
implements
Expand Down Expand Up @@ -98,19 +93,9 @@ export class TypeScriptPlugin
private readonly signatureHelpProvider: SignatureHelpProviderImpl;
private readonly semanticTokensProvider: SemanticTokensProviderImpl;

constructor(
docManager: DocumentManager,
configManager: LSConfigManager,
workspaceUris: string[],
isEditor = true
) {
constructor(configManager: LSConfigManager, lsAndTsDocResolver: LSAndTSDocResolver) {
this.configManager = configManager;
this.lsAndTsDocResolver = new LSAndTSDocResolver(
docManager,
workspaceUris,
configManager,
/**transformOnTemplateError */ isEditor
);
this.lsAndTsDocResolver = lsAndTsDocResolver;
this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver);
this.codeActionsProvider = new CodeActionsProviderImpl(
this.lsAndTsDocResolver,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import { Diagnostic, DiagnosticSeverity, DiagnosticTag } from 'vscode-languageserver';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import {
Document,
mapObjWithRangeToOriginal,
Expand All @@ -8,7 +8,7 @@ import {
} from '../../../lib/documents';
import { DiagnosticsProvider } from '../../interfaces';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertRange, mapSeverity } from '../utils';
import { convertRange, getDiagnosticTag, mapSeverity } from '../utils';
import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
import { isInGeneratedCode } from './utils';

Expand Down Expand Up @@ -53,7 +53,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
source: isTypescript ? 'ts' : 'js',
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
code: diagnostic.code,
tags: this.getDiagnosticTag(diagnostic)
tags: getDiagnosticTag(diagnostic)
}))
.map((diagnostic) => mapObjWithRangeToOriginal(fragment, diagnostic))
.filter(hasNoNegativeLines)
Expand All @@ -62,17 +62,6 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
.map(swapRangeStartEndIfNecessary);
}

private getDiagnosticTag(diagnostic: ts.Diagnostic) {
const tags: DiagnosticTag[] = [];
if (diagnostic.reportsUnnecessary) {
tags.push(DiagnosticTag.Unnecessary);
}
if (diagnostic.reportsDeprecated) {
tags.push(DiagnosticTag.Deprecated);
}
return tags;
}

private async getLSAndTSDoc(document: Document) {
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
}
Expand Down
39 changes: 24 additions & 15 deletions packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export interface LanguageServiceContainer {
getService(): ts.LanguageService;
updateSnapshot(documentOrFilePath: Document | string): DocumentSnapshot;
deleteSnapshot(filePath: string): void;
/**
* Careful, don't call often, or it will hurt performance.
* Only works for TS versions that have ScriptKind.Deferred
*/
fileBelongsToProject(filePath: string): boolean;
}

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

export async function getLanguageServiceForPath(
export async function getService(
path: string,
workspaceUris: string[],
docContext: LanguageServiceDocumentContext
): Promise<ts.LanguageService> {
return (await getService(path, workspaceUris, docContext)).getService();
): Promise<LanguageServiceContainer> {
const tsconfigPath = findTsConfigPath(path, workspaceUris);
return getServiceForTsconfig(tsconfigPath, docContext);
}

export async function getLanguageServiceForDocument(
document: Document,
workspaceUris: string[],
docContext: LanguageServiceDocumentContext
): Promise<ts.LanguageService> {
return getLanguageServiceForPath(document.getFilePath() || '', workspaceUris, docContext);
export function hasServiceForFile(path: string, workspaceUris: string[]): boolean {
const tsconfigPath = findTsConfigPath(path, workspaceUris);
return services.has(tsconfigPath);
}

export async function getService(
path: string,
workspaceUris: string[],
/**
* @param tsconfigPath has to be absolute
* @param docContext
*/
export async function getServiceForTsconfig(
tsconfigPath: string,
docContext: LanguageServiceDocumentContext
) {
const tsconfigPath = findTsConfigPath(path, workspaceUris);

): Promise<LanguageServiceContainer> {
let service: LanguageServiceContainer;
if (services.has(tsconfigPath)) {
service = await services.get(tsconfigPath)!;
Expand Down Expand Up @@ -127,6 +131,7 @@ async function createLanguageService(
getService: () => languageService,
updateSnapshot,
deleteSnapshot,
fileBelongsToProject,
snapshotManager
};

Expand Down Expand Up @@ -192,6 +197,10 @@ async function createLanguageService(
return doc;
}

function fileBelongsToProject(filePath: string): boolean {
return snapshotManager.has(filePath) || getParsedConfig().fileNames.includes(filePath);
}

function getParsedConfig() {
const forcedCompilerOptions: ts.CompilerOptions = {
allowNonTsExtensions: true,
Expand Down
12 changes: 12 additions & 0 deletions packages/language-server/src/plugins/typescript/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ts from 'typescript';
import {
CompletionItemKind,
DiagnosticSeverity,
DiagnosticTag,
Position,
Range,
SymbolKind
Expand Down Expand Up @@ -301,3 +302,14 @@ export function convertToTextSpan(range: Range, fragment: SnapshotFragment): ts.
export function isInScript(position: Position, fragment: SvelteSnapshotFragment | Document) {
return isInTag(position, fragment.scriptInfo) || isInTag(position, fragment.moduleScriptInfo);
}

export function getDiagnosticTag(diagnostic: ts.Diagnostic): DiagnosticTag[] {
const tags: DiagnosticTag[] = [];
if (diagnostic.reportsUnnecessary) {
tags.push(DiagnosticTag.Unnecessary);
}
if (diagnostic.reportsDeprecated) {
tags.push(DiagnosticTag.Deprecated);
}
return tags;
}
10 changes: 8 additions & 2 deletions packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
PluginHost,
SveltePlugin,
TypeScriptPlugin,
OnWatchFileChangesPara
OnWatchFileChangesPara,
LSAndTSDocResolver
} from './plugins';
import { isNotNullOrUndefined, urlToPath } from './utils';
import { FallbackWatcher } from './lib/FallbackWatcher';
Expand Down Expand Up @@ -132,7 +133,12 @@ export function startServer(options?: LSOptions) {
pluginHost.register((sveltePlugin = new SveltePlugin(configManager)));
pluginHost.register(new HTMLPlugin(docManager, configManager));
pluginHost.register(new CSSPlugin(docManager, configManager));
pluginHost.register(new TypeScriptPlugin(docManager, configManager, workspaceUris));
pluginHost.register(
new TypeScriptPlugin(
configManager,
new LSAndTSDocResolver(docManager, workspaceUris, configManager)
)
);

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

Expand Down
Loading