Skip to content

Commit

Permalink
refactor(language-core): resolved language ID by LangaugePlugin (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Apr 30, 2024
1 parent 9327aae commit e3a3318
Show file tree
Hide file tree
Showing 14 changed files with 91 additions and 69 deletions.
8 changes: 1 addition & 7 deletions packages/kit/lib/createChecker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CodeActionTriggerKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, FileChangeType, LanguagePlugin, NotificationHandler, LanguageServicePlugin, ServiceEnvironment, createLanguageService, mergeWorkspaceEdits, resolveCommonLanguageId, TypeScriptProjectHost } from '@volar/language-service';
import { CodeActionTriggerKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, FileChangeType, LanguagePlugin, NotificationHandler, LanguageServicePlugin, ServiceEnvironment, createLanguageService, mergeWorkspaceEdits, TypeScriptProjectHost } from '@volar/language-service';
import * as path from 'typesafe-path/posix';
import * as ts from 'typescript';
import { TextDocument } from 'vscode-languageserver-textdocument';
Expand All @@ -10,7 +10,6 @@ export function createTypeScriptChecker(
languagePlugins: LanguagePlugin[],
languageServicePlugins: LanguageServicePlugin[],
tsconfig: string,
getLanguageId = resolveCommonLanguageId,
) {
const tsconfigPath = asPosix(tsconfig);
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, env => {
Expand All @@ -30,7 +29,6 @@ export function createTypeScriptChecker(
parsed.fileNames = parsed.fileNames.map(asPosix);
return parsed;
},
getLanguageId,
);
});
}
Expand All @@ -39,7 +37,6 @@ export function createTypeScriptInferredChecker(
languagePlugins: LanguagePlugin[],
languageServicePlugins: LanguageServicePlugin[],
getScriptFileNames: () => string[],
getLanguageId = resolveCommonLanguageId,
compilerOptions = defaultCompilerOptions
) {
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, env => {
Expand All @@ -50,7 +47,6 @@ export function createTypeScriptInferredChecker(
options: compilerOptions,
fileNames: getScriptFileNames().map(asPosix),
}),
getLanguageId,
);
});
}
Expand Down Expand Up @@ -203,7 +199,6 @@ function createTypeScriptProjectHost(
configFileName: string | undefined,
env: ServiceEnvironment,
createParsedCommandLine: () => Pick<ts.ParsedCommandLine, 'options' | 'fileNames'>,
getLanguageId: (fileName: string) => string,
) {

let scriptSnapshotsCache: Map<string, ts.IScriptSnapshot | undefined> = new Map();
Expand Down Expand Up @@ -240,7 +235,6 @@ function createTypeScriptProjectHost(
}
return scriptSnapshotsCache.get(fileName);
},
getLanguageId,
fileNameToScriptId: env.typescript!.fileNameToUri,
scriptIdToFileName: env.typescript!.uriToFileName,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/lib/createFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function createFormatter(
async function format(content: string, languageId: string, options: FormattingOptions): Promise<string> {

const snapshot = ts.ScriptSnapshot.fromString(content);
language.scripts.set(fakeUri, languageId, snapshot);
language.scripts.set(fakeUri, snapshot, languageId);

const document = service.context.documents.get(fakeUri, languageId, snapshot);
const edits = await service.format(fakeUri, options, undefined, undefined);
Expand Down
53 changes: 27 additions & 26 deletions packages/language-core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,44 @@ export function createLanguage(plugins: LanguagePlugin[], caseSensitive: boolean
sync(id);
return sourceScripts.get(id);
},
set(id, languageId, snapshot, _plugins = plugins) {
set(id, snapshot, languageId, _plugins = plugins) {
if (!languageId) {
for (const plugin of plugins) {
languageId = plugin.getLanguageId?.(id);
if (languageId) {
break;
}
}
}
if (!languageId) {
return;
}
if (sourceScripts.has(id)) {
const sourceScript = sourceScripts.get(id)!;
if (sourceScript.languageId !== languageId) {
// languageId changed
this.delete(id);
return this.set(id, languageId, snapshot);
return this.set(id, snapshot, languageId);
}
else if (sourceScript.snapshot !== snapshot) {
// snapshot updated
sourceScript.snapshot = snapshot;
if (sourceScript.generated) {
sourceScript.generated.root = sourceScript.generated.languagePlugin.updateVirtualCode(id, sourceScript.generated.root, snapshot);
sourceScript.generated.embeddedCodes.clear();
for (const code of forEachEmbeddedCode(sourceScript.generated.root)) {
virtualCodeToSourceFileMap.set(code, sourceScript);
sourceScript.generated.embeddedCodes.set(code.id, code);
const newVirtualCode = sourceScript.generated.languagePlugin.updateVirtualCode?.(id, sourceScript.generated.root, snapshot);
if (newVirtualCode) {
sourceScript.generated.root = newVirtualCode;
sourceScript.generated.embeddedCodes.clear();
for (const code of forEachEmbeddedCode(sourceScript.generated.root)) {
virtualCodeToSourceFileMap.set(code, sourceScript);
sourceScript.generated.embeddedCodes.set(code.id, code);
}
return sourceScript;
}
else {
this.delete(id);
return;
}
}
return sourceScript;
}
else {
// not changed
Expand All @@ -55,7 +73,7 @@ export function createLanguage(plugins: LanguagePlugin[], caseSensitive: boolean
const sourceScript: SourceScript = { id, languageId, snapshot };
sourceScripts.set(id, sourceScript);
for (const languagePlugin of _plugins) {
const virtualCode = languagePlugin.createVirtualCode(id, languageId, snapshot);
const virtualCode = languagePlugin.createVirtualCode?.(id, languageId, snapshot);
if (virtualCode) {
sourceScript.generated = {
root: virtualCode,
Expand Down Expand Up @@ -167,20 +185,3 @@ export function* forEachEmbeddedCode(virtualCode: VirtualCode): Generator<Virtua
}
}
}

export function resolveCommonLanguageId(fileNameOrUri: string) {
const ext = fileNameOrUri.split('.').pop()!;
switch (ext) {
case 'js': return 'javascript';
case 'cjs': return 'javascript';
case 'mjs': return 'javascript';
case 'ts': return 'typescript';
case 'cts': return 'typescript';
case 'mts': return 'typescript';
case 'jsx': return 'javascriptreact';
case 'tsx': return 'typescriptreact';
case 'pug': return 'jade';
case 'md': return 'markdown';
}
return ext;
}
8 changes: 4 additions & 4 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface Language {
plugins: LanguagePlugin[];
scripts: {
get(id: string): SourceScript | undefined;
set(id: string, languageId: string, snapshot: ts.IScriptSnapshot, plugins?: LanguagePlugin[]): SourceScript;
set(id: string, snapshot: ts.IScriptSnapshot, languageId?: string, plugins?: LanguagePlugin[]): SourceScript | undefined;
delete(id: string): void;
};
maps: {
Expand Down Expand Up @@ -86,8 +86,9 @@ export interface ExtraServiceScript extends ServiceScript {
}

export interface LanguagePlugin<T extends VirtualCode = VirtualCode> {
createVirtualCode(scriptId: string, languageId: string, snapshot: ts.IScriptSnapshot): T | undefined;
updateVirtualCode(scriptId: string, virtualCode: T, newSnapshot: ts.IScriptSnapshot): T;
getLanguageId(scriptId: string): string | undefined;
createVirtualCode?(scriptId: string, languageId: string, snapshot: ts.IScriptSnapshot): T | undefined;
updateVirtualCode?(scriptId: string, virtualCode: T, newSnapshot: ts.IScriptSnapshot): T | undefined;
disposeVirtualCode?(scriptId: string, virtualCode: T): void;
typescript?: {
/**
Expand Down Expand Up @@ -120,7 +121,6 @@ export interface TypeScriptProjectHost extends ts.System, Pick<
| 'getScriptSnapshot'
> {
configFileName: string | undefined;
getLanguageId(scriptId: string): string;
getSystemVersion?(): number;
syncSystem?(): Promise<number>;
scriptIdToFileName(scriptId: string): string;
Expand Down
6 changes: 3 additions & 3 deletions packages/language-server/lib/project/simpleProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export async function createSimpleServerProject(
function getLanguageService() {
if (!languageService) {
const language = createLanguage(languagePlugins, false, uri => {
const script = server.documents.get(uri);
if (script) {
language.scripts.set(uri, script.languageId, script.getSnapshot());
const document = server.documents.get(uri);
if (document) {
language.scripts.set(uri, document.getSnapshot(), document.uri);
}
else {
language.scripts.delete(uri);
Expand Down
14 changes: 9 additions & 5 deletions packages/language-server/lib/project/typescriptProject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LanguagePlugin, LanguageService, ProviderResult, ServiceEnvironment, TypeScriptProjectHost, createLanguageService, resolveCommonLanguageId } from '@volar/language-service';
import { LanguagePlugin, LanguageService, ProviderResult, ServiceEnvironment, TypeScriptProjectHost, createLanguageService } from '@volar/language-service';
import { createSys, createTypeScriptLanguage } from '@volar/typescript';
import * as path from 'path-browserify';
import type * as ts from 'typescript';
Expand Down Expand Up @@ -63,9 +63,6 @@ export async function createTypeScriptServerProject(
getProjectReferences() {
return parsedCommandLine.projectReferences;
},
getLanguageId(uri) {
return server.documents.get(uri)?.languageId ?? resolveCommonLanguageId(uri);
},
fileNameToScriptId: serviceEnv.typescript!.fileNameToUri,
scriptIdToFileName: serviceEnv.typescript!.uriToFileName,
};
Expand Down Expand Up @@ -112,7 +109,14 @@ export async function createTypeScriptServerProject(
if (!languageService) {
const language = createTypeScriptLanguage(
ts,
languagePlugins,
[
{
getLanguageId(uri) {
return server.documents.get(uri)?.languageId;
},
},
...languagePlugins,
],
host,
);
languageService = createLanguageService(
Expand Down
6 changes: 3 additions & 3 deletions packages/language-server/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import * as l10n from '@vscode/l10n';
import { configure as configureHttpRequests } from 'request-light';
import * as vscode from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { registerEditorFeatures } from './register/registerEditorFeatures.js';
import { registerLanguageFeatures } from './register/registerLanguageFeatures.js';
import { getServerCapabilities } from './serverCapabilities.js';
import type { VolarInitializeParams, ServerProjectProvider } from './types.js';
import type { ServerProjectProvider, VolarInitializeParams } from './types.js';
import { fileNameToUri } from './uri.js';
import { createUriMap } from './utils/uriMap.js';
import { registerEditorFeatures } from './register/registerEditorFeatures.js';
import { registerLanguageFeatures } from './register/registerLanguageFeatures.js';

export * from '@volar/snapshot-document';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function register(context: ServiceContext) {
const originalDocument = document;

let tempSourceSnapshot = sourceScript.snapshot;
let tempVirtualFile = context.language.scripts.set(sourceScript.id + '.tmp', sourceScript.languageId, sourceScript.snapshot, [sourceScript.generated.languagePlugin]).generated?.root;
let tempVirtualFile = context.language.scripts.set(sourceScript.id + '.tmp', sourceScript.snapshot, sourceScript.languageId, [sourceScript.generated.languagePlugin])?.generated?.root;
if (!tempVirtualFile) {
return;
}
Expand Down Expand Up @@ -145,7 +145,7 @@ export function register(context: ServiceContext) {
const newText = TextDocument.applyEdits(document, edits);
document = TextDocument.create(document.uri, document.languageId, document.version + 1, newText);
tempSourceSnapshot = stringToSnapshot(newText);
tempVirtualFile = context.language.scripts.set(sourceScript.id + '.tmp', sourceScript.languageId, tempSourceSnapshot, [sourceScript.generated.languagePlugin]).generated?.root;
tempVirtualFile = context.language.scripts.set(sourceScript.id + '.tmp', tempSourceSnapshot, sourceScript.languageId, [sourceScript.generated.languagePlugin])?.generated?.root;
if (!tempVirtualFile) {
break;
}
Expand Down
8 changes: 1 addition & 7 deletions packages/monaco/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
LanguageServicePlugin,
createLanguageService as _createLanguageService,
createLanguage,
resolveCommonLanguageId,
type LanguageService,
type ServiceEnvironment,
TypeScriptProjectHost,
Expand All @@ -22,14 +21,12 @@ export function createSimpleWorkerService<T = {}>({
languagePlugins = [],
servicePlugins = [],
extraApis = {} as T,
getLanguageId = resolveCommonLanguageId,
}: {
env: ServiceEnvironment;
workerContext: monaco.worker.IWorkerContext<any>;
languagePlugins?: LanguagePlugin[];
servicePlugins?: LanguageServicePlugin[];
extraApis?: T;
getLanguageId?: (uri: string) => string;
}) {
const snapshots = new Map<monaco.worker.IMirrorModel, readonly [number, ts.IScriptSnapshot]>();
const language = createLanguage(
Expand All @@ -49,7 +46,7 @@ export function createSimpleWorkerService<T = {}>({
getChangeRange: () => undefined,
};
snapshots.set(model, [model.version, snapshot]);
language.scripts.set(uri, getLanguageId(uri), snapshot);
language.scripts.set(uri, snapshot);
}
else {
language.scripts.delete(uri);
Expand All @@ -68,7 +65,6 @@ export function createTypeScriptWorkerService<T = {}>({
languagePlugins = [],
servicePlugins = [],
extraApis = {} as T,
getLanguageId = resolveCommonLanguageId,
}: {
typescript: typeof import('typescript'),
compilerOptions: ts.CompilerOptions,
Expand All @@ -77,7 +73,6 @@ export function createTypeScriptWorkerService<T = {}>({
languagePlugins?: LanguagePlugin[];
servicePlugins?: LanguageServicePlugin[];
extraApis?: T;
getLanguageId?: (uri: string) => string;
}) {

let projectVersion = 0;
Expand Down Expand Up @@ -134,7 +129,6 @@ export function createTypeScriptWorkerService<T = {}>({
getCompilationSettings() {
return compilerOptions;
},
getLanguageId: id => getLanguageId(id),
fileNameToScriptId: env.typescript!.fileNameToUri,
scriptIdToFileName: env.typescript!.uriToFileName,
};
Expand Down
17 changes: 17 additions & 0 deletions packages/typescript/lib/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { LanguagePlugin } from "@volar/language-core";

export const fileLanguageIdProviderPlugin: LanguagePlugin = {
getLanguageId(scriptId) {
const ext = scriptId.split('.').pop()!;
switch (ext) {
case 'js': return 'javascript';
case 'cjs': return 'javascript';
case 'mjs': return 'javascript';
case 'ts': return 'typescript';
case 'cts': return 'typescript';
case 'mts': return 'typescript';
case 'jsx': return 'javascriptreact';
case 'tsx': return 'typescriptreact';
}
},
};
9 changes: 6 additions & 3 deletions packages/typescript/lib/node/proxyCreateProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Language, LanguagePlugin, createLanguage } from '@volar/language-core';
import type * as ts from 'typescript';
import { createResolveModuleName } from '../resolveModuleName';
import { decorateProgram } from './decorateProgram';
import { fileLanguageIdProviderPlugin } from '../common';

const arrayEqual = (a: readonly any[], b: readonly any[]) => {
if (a.length !== b.length) {
Expand Down Expand Up @@ -32,7 +33,6 @@ export function proxyCreateProgram(
ts: typeof import('typescript'),
original: typeof ts['createProgram'],
getLanguagePlugins: (ts: typeof import('typescript'), options: ts.CreateProgramOptions) => LanguagePlugin[],
getLanguageId: (fileName: string) => string,
) {
const sourceFileSnapshots = new Map<string, [ts.SourceFile | undefined, ts.IScriptSnapshot | undefined]>();
const parsedSourceFiles = new WeakMap<ts.SourceFile, ts.SourceFile>();
Expand All @@ -59,7 +59,10 @@ export function proxyCreateProgram(
lastOptions = options;
languagePlugins = getLanguagePlugins(ts, options);
language = createLanguage(
languagePlugins,
[
...languagePlugins,
fileLanguageIdProviderPlugin,
],
ts.sys.useCaseSensitiveFileNames,
fileName => {
if (!sourceFileSnapshots.has(fileName)) {
Expand All @@ -83,7 +86,7 @@ export function proxyCreateProgram(
}
const snapshot = sourceFileSnapshots.get(fileName)?.[1];
if (snapshot) {
language!.scripts.set(fileName, getLanguageId(fileName), snapshot);
language!.scripts.set(fileName, snapshot);
}
else {
language!.scripts.delete(fileName);
Expand Down
8 changes: 6 additions & 2 deletions packages/typescript/lib/protocol/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type * as ts from 'typescript';
import { forEachEmbeddedCode } from '@volar/language-core';
import * as path from 'path-browserify';
import { createResolveModuleName } from '../resolveModuleName';
import { fileLanguageIdProviderPlugin } from '../common';

const scriptVersions = new Map<string, { lastVersion: number; map: WeakMap<ts.IScriptSnapshot, number>; }>();
const fsFileSnapshots = new Map<string, [number | undefined, ts.IScriptSnapshot | undefined]>();
Expand All @@ -14,7 +15,10 @@ export function createTypeScriptLanguage(
): Language {

const language = createLanguage(
languagePlugins,
[
...languagePlugins,
fileLanguageIdProviderPlugin,
],
projectHost.useCaseSensitiveFileNames,
scriptId => {
const fileName = projectHost.scriptIdToFileName(scriptId);
Expand All @@ -39,7 +43,7 @@ export function createTypeScriptLanguage(
}

if (snapshot) {
language.scripts.set(scriptId, projectHost.getLanguageId(scriptId), snapshot);
language.scripts.set(scriptId, snapshot);
}
else {
language.scripts.delete(scriptId);
Expand Down

0 comments on commit e3a3318

Please sign in to comment.