Skip to content

Commit

Permalink
refactor(language-service): move project context from Language to l…
Browse files Browse the repository at this point in the history
…anguage service option (#217)
  • Loading branch information
johnsoncodehk authored Jun 29, 2024
1 parent 45b6625 commit 18b92a0
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 110 deletions.
50 changes: 26 additions & 24 deletions packages/kit/lib/createChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'typesafe-path/posix';
import * as ts from 'typescript';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { createServiceEnvironment } from './createServiceEnvironment';
import { asPosix, defaultCompilerOptions, fileNameToUri, uriToFileName } from './utils';
import { asPosix, defaultCompilerOptions, asUri, asFileName } from './utils';
import { URI } from 'vscode-uri';
import { TypeScriptProjectHost, createLanguageServiceHost, resolveFileLanguageId } from '@volar/typescript';

Expand Down Expand Up @@ -82,7 +82,7 @@ function createTypeScriptCheckerWorker(
uri => {
// fs files
const cache = fsFileSnapshots.get(uri);
const fileName = uriToFileName(uri);
const fileName = asFileName(uri);
const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf();
if (!cache || cache[0] !== modifiedTime) {
if (ts.sys.fileExists(fileName)) {
Expand All @@ -104,23 +104,25 @@ function createTypeScriptCheckerWorker(
}
);
const projectHost = getProjectHost(env);
language.typescript = {
configFileName,
sys: ts.sys,
asFileName: uriToFileName,
asScriptId: fileNameToUri,
...createLanguageServiceHost(
ts,
ts.sys,
language,
fileNameToUri,
projectHost
),
};
const languageService = createLanguageService(
language,
languageServicePlugins,
env
env,
{
typescript: {
configFileName,
sys: ts.sys,
asFileName,
asUri,
...createLanguageServiceHost(
ts,
ts.sys,
language,
asUri,
projectHost
),
},
}
);

return {
Expand Down Expand Up @@ -154,19 +156,19 @@ function createTypeScriptCheckerWorker(
function fileEvent(fileName: string, type: FileChangeType) {
fileName = asPosix(fileName);
for (const cb of didChangeWatchedFilesCallbacks) {
cb({ changes: [{ uri: fileNameToUri(fileName).toString(), type }] });
cb({ changes: [{ uri: asUri(fileName).toString(), type }] });
}
}

function check(fileName: string) {
fileName = asPosix(fileName);
const uri = fileNameToUri(fileName);
const uri = asUri(fileName);
return languageService.getDiagnostics(uri);
}

async function fixErrors(fileName: string, diagnostics: Diagnostic[], only: string[] | undefined, writeFile: (fileName: string, newText: string) => Promise<void>) {
fileName = asPosix(fileName);
const uri = fileNameToUri(fileName);
const uri = asUri(fileName);
const sourceScript = languageService.context.language.scripts.get(uri);
if (sourceScript) {
const document = languageService.context.documents.get(uri, sourceScript.languageId, sourceScript.snapshot);
Expand All @@ -188,7 +190,7 @@ function createTypeScriptCheckerWorker(
if (editFile) {
const editDocument = languageService.context.documents.get(parsedUri, editFile.languageId, editFile.snapshot);
const newString = TextDocument.applyEdits(editDocument, edits);
await writeFile(uriToFileName(parsedUri), newString);
await writeFile(asFileName(parsedUri), newString);
}
}
}
Expand All @@ -199,7 +201,7 @@ function createTypeScriptCheckerWorker(
if (editFile) {
const editDocument = languageService.context.documents.get(changeUri, editFile.languageId, editFile.snapshot);
const newString = TextDocument.applyEdits(editDocument, change.edits);
await writeFile(uriToFileName(changeUri), newString);
await writeFile(asFileName(changeUri), newString);
}
}
// TODO: CreateFile | RenameFile | DeleteFile
Expand All @@ -219,7 +221,7 @@ function createTypeScriptCheckerWorker(

function formatErrors(fileName: string, diagnostics: Diagnostic[], rootPath: string) {
fileName = asPosix(fileName);
const uri = fileNameToUri(fileName);
const uri = asUri(fileName);
const sourceScript = languageService.context.language.scripts.get(uri)!;
const document = languageService.context.documents.get(uri, sourceScript.languageId, sourceScript.snapshot);
const errors: ts.Diagnostic[] = diagnostics.map<ts.Diagnostic>(diagnostic => ({
Expand Down Expand Up @@ -250,7 +252,7 @@ function createTypeScriptProjectHost(

const host: TypeScriptProjectHost = {
getCurrentDirectory: () => env.workspaceFolders.length
? uriToFileName(env.workspaceFolders[0])
? asFileName(env.workspaceFolders[0])
: process.cwd(),
getCompilationSettings: () => {
return parsedCommandLine.options;
Expand Down Expand Up @@ -280,7 +282,7 @@ function createTypeScriptProjectHost(
env.onDidChangeWatchedFiles?.(({ changes }) => {
for (const change of changes) {
const changeUri = URI.parse(change.uri);
const fileName = uriToFileName(changeUri);
const fileName = asFileName(changeUri);
if (change.type === 2 satisfies typeof FileChangeType.Changed) {
if (scriptSnapshotsCache.has(fileName)) {
projectVersion++;
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/lib/createFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export function createFormatter(
const languageService = createLanguageService(
language,
services,
env
env,
{}
);

return {
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export function asPosix(path: string) {
return path.replace(/\\/g, '/') as path.PosixPath;
}

export const uriToFileName = (uri: URI) => uri.fsPath.replace(/\\/g, '/');
export const asFileName = (uri: URI) => uri.fsPath.replace(/\\/g, '/');

export const fileNameToUri = (fileName: string) => URI.file(fileName);
export const asUri = (fileName: string) => URI.file(fileName);
3 changes: 2 additions & 1 deletion packages/language-server/lib/project/simpleProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export function createSimpleProject(languagePlugins: LanguagePlugin<URI>[]): Lan
languageService = createLanguageService(
language,
server.languageServicePlugins,
createLanguageServiceEnvironment(server, [...server.workspaceFolders.keys()])
createLanguageServiceEnvironment(server, [...server.workspaceFolders.keys()]),
{}
);
},
getLanguageService() {
Expand Down
30 changes: 16 additions & 14 deletions packages/language-server/lib/project/typescriptProjectLs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,24 +134,26 @@ export async function createTypeScriptLS(
}
}
);
language.typescript = {
configFileName: typeof tsconfig === 'string' ? tsconfig : undefined,
sys,
asScriptId: asUri,
asFileName: asFileName,
...createLanguageServiceHost(
ts,
sys,
language,
asUri,
projectHost
),
};
setup(language);
const languageService = createLanguageService(
language,
server.languageServicePlugins,
serviceEnv
serviceEnv,
{
typescript: {
configFileName: typeof tsconfig === 'string' ? tsconfig : undefined,
sys,
asUri,
asFileName,
...createLanguageServiceHost(
ts,
sys,
language,
asUri,
projectHost
),
}
}
);

return {
Expand Down
19 changes: 11 additions & 8 deletions packages/language-server/lib/register/registerEditorFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ export function registerEditorFeatures(server: LanguageServer) {
server.connection.onRequest(GetMatchTsConfigRequest.type, async params => {
const uri = URI.parse(params.uri);
const languageService = (await server.project.getLanguageService(uri));
if (languageService.context.language.typescript?.configFileName) {
const { configFileName, asScriptId } = languageService.context.language.typescript;
return { uri: asScriptId(configFileName).toString() };
const tsProject = languageService.context.project.typescript;
if (tsProject?.configFileName) {
const { configFileName, asUri } = tsProject;
return { uri: asUri(configFileName).toString() };
}
});
server.connection.onRequest(GetVirtualFileRequest.type, async document => {
Expand Down Expand Up @@ -107,10 +108,11 @@ export function registerEditorFeatures(server: LanguageServer) {
const fs = _require('fs');
const uri = URI.parse(params.uri);
const languageService = (await server.project.getLanguageService(uri));
const tsProject = languageService.context.project.typescript;

if (languageService.context.language.typescript) {
if (tsProject) {

const { languageServiceHost } = languageService.context.language.typescript;
const { languageServiceHost } = tsProject;

for (const fileName of languageServiceHost.getScriptFileNames()) {
if (!fs.existsSync(fileName)) {
Expand All @@ -121,7 +123,7 @@ export function registerEditorFeatures(server: LanguageServer) {
}
}
else {
const uri = languageService.context.language.typescript.asScriptId(fileName);
const uri = tsProject.asUri(fileName);
const sourceScript = languageService.context.language.scripts.get(uri);
if (sourceScript?.generated) {
const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
Expand Down Expand Up @@ -150,8 +152,9 @@ export function registerEditorFeatures(server: LanguageServer) {
for (const languageService of await server.project.getExistingLanguageServices()) {
const tsLanguageService: ts.LanguageService | undefined = languageService.context.inject<any>('typescript/languageService');
const program = tsLanguageService?.getProgram();
if (program && languageService.context.language.typescript) {
const { languageServiceHost, configFileName } = languageService.context.language.typescript;
const tsProject = languageService.context.project.typescript;
if (program && tsProject) {
const { languageServiceHost, configFileName } = tsProject;
const projectName = configFileName ?? (languageServiceHost.getCurrentDirectory() + '(inferred)');
const sourceFiles = program.getSourceFiles() ?? [];
for (const sourceFile of sourceFiles) {
Expand Down
6 changes: 4 additions & 2 deletions packages/language-service/lib/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import * as completionResolve from './features/resolveCompletionItem';
import * as documentLinkResolve from './features/resolveDocumentLink';
import * as inlayHintResolve from './features/resolveInlayHint';
import * as workspaceSymbolResolve from './features/resolveWorkspaceSymbol';
import type { LanguageServiceContext, LanguageServiceEnvironment, LanguageServicePlugin } from './types';
import type { LanguageServiceContext, LanguageServiceEnvironment, LanguageServicePlugin, ProjectContext } from './types';
import { NoneCancellationToken } from './utils/cancellation';
import { UriMap, createUriMap } from './utils/uriMap';

Expand All @@ -48,12 +48,14 @@ export const embeddedContentScheme = 'volar-embedded-content';
export function createLanguageService(
language: Language<URI>,
plugins: LanguageServicePlugin[],
env: LanguageServiceEnvironment
env: LanguageServiceEnvironment,
project: ProjectContext
) {
const documentVersions = createUriMap<number>();
const snapshot2Doc = new WeakMap<ts.IScriptSnapshot, UriMap<TextDocument>>();
const context: LanguageServiceContext = {
language,
project,
getLanguageService: () => langaugeService,
documents: {
get(uri, languageId, snapshot) {
Expand Down
5 changes: 4 additions & 1 deletion packages/language-service/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@ export interface LanguageServiceCommand<T extends any[]> {
is(value: vscode.Command): boolean;
}

export interface ProjectContext { }

export interface LanguageServiceContext {
language: Language<URI>;
project: ProjectContext;
getLanguageService(): LanguageService;
env: LanguageServiceEnvironment;
inject<Provide, K extends keyof Provide = keyof Provide>(
inject<Provide = any, K extends keyof Provide = keyof Provide>(
key: K,
...args: Provide[K] extends (...args: any) => any ? Parameters<Provide[K]> : never
): ReturnType<Provide[K] extends (...args: any) => any ? Provide[K] : never> | undefined;
Expand Down
Loading

0 comments on commit 18b92a0

Please sign in to comment.