Skip to content

Commit

Permalink
refactor(language-service): service plugins do not depend on project …
Browse files Browse the repository at this point in the history
…context
  • Loading branch information
johnsoncodehk committed Dec 21, 2023
1 parent a3b942a commit 02d0ef4
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 37 deletions.
9 changes: 5 additions & 4 deletions packages/language-server/src/nodeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,18 @@ connection.onInitialize(params => {
watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions],
getServerCapabilitiesSetup() {
const ts = getTsLib();
const services = vue.resolveServices(ts, {}, {});
const services = vue.resolveServices({}, ts, env => envToVueOptions.get(env)!);
return {
servicePlugins: Object.values(services),
};
},
async getProjectSetup(serviceEnv, projectContext) {
const ts = getTsLib();
const [commandLine, vueOptions] = await parseCommandLine();
envToVueOptions.set(serviceEnv, vue.resolveVueCompilerOptions(vueOptions));
const services = vue.resolveServices(ts, {}, vueOptions);
const languages = vue.resolveLanguages(ts, {}, commandLine?.options ?? {}, vueOptions, options.codegenStack);
const resolvedVueOptions = vue.resolveVueCompilerOptions(vueOptions);
envToVueOptions.set(serviceEnv, resolvedVueOptions);
const services = vue.resolveServices({}, ts, env => envToVueOptions.get(env)!);
const languages = vue.resolveLanguages({}, ts, commandLine?.options ?? {}, resolvedVueOptions, options.codegenStack);

return {
languagePlugins: Object.values(languages),
Expand Down
26 changes: 12 additions & 14 deletions packages/language-service/src/languageService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ServicePlugin } from '@volar/language-service';
import { LanguagePlugin, VueFile, createLanguages, hyphenateTag, resolveVueCompilerOptions, scriptRanges } from '@vue/language-core';
import { ServiceEnvironment, ServicePlugin } from '@volar/language-service';
import { LanguagePlugin, VueFile, createLanguages, hyphenateTag, scriptRanges } from '@vue/language-core';
import { capitalize } from '@vue/shared';
import type * as ts from 'typescript/lib/tsserverlibrary';
import type { Data } from 'volar-service-typescript/lib/features/completions/basic';
Expand Down Expand Up @@ -37,14 +37,13 @@ export interface Settings {
}

export function resolveLanguages(
languages: Record<string, LanguagePlugin>,
ts: typeof import('typescript/lib/tsserverlibrary'),
languages: Record<string, LanguagePlugin> = {},
compilerOptions: ts.CompilerOptions = {},
_vueCompilerOptions: Partial<VueCompilerOptions> = {},
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
codegenStack: boolean = false,
): Record<string, LanguagePlugin> {

const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
const vueLanguageModules = createLanguages(ts, compilerOptions, vueCompilerOptions, codegenStack);

return {
Expand All @@ -57,12 +56,11 @@ export function resolveLanguages(
}

export function resolveServices(
services: Record<string, ServicePlugin>,
ts: typeof import('typescript/lib/tsserverlibrary'),
services: Record<string, ServicePlugin> = {},
_vueCompilerOptions: Partial<VueCompilerOptions> = {},
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
) {

const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions);
const tsService: ServicePlugin = services?.typescript ?? createTsService(ts);

services ??= {};
Expand Down Expand Up @@ -103,7 +101,7 @@ export function resolveServices(
}

// fix #2458
casing ??= await getNameCasing(ts, context, sourceFile.fileName, vueCompilerOptions);
casing ??= await getNameCasing(ts, context, sourceFile.fileName, getVueOptions(context.env));

if (casing.tag === TagNameCasing.Kebab) {
for (const item of result.items) {
Expand All @@ -129,7 +127,7 @@ export function resolveServices(
patchAdditionalTextEdits(itemData.uri, item.additionalTextEdits);
}

for (const ext of vueCompilerOptions.extensions) {
for (const ext of getVueOptions(context.env).extensions) {
const suffix = capitalize(ext.substring('.'.length)); // .vue -> Vue
if (
itemData?.uri
Expand All @@ -156,7 +154,7 @@ export function resolveServices(
item.textEdit.newText = newName;
const [_, sourceFile] = context.language.files.getVirtualFile(context.env.uriToFileName(itemData.uri));
if (sourceFile) {
const casing = await getNameCasing(ts, context, sourceFile.fileName, vueCompilerOptions);
const casing = await getNameCasing(ts, context, sourceFile.fileName, getVueOptions(context.env));
if (casing.tag === TagNameCasing.Kebab) {
item.textEdit.newText = hyphenateTag(item.textEdit.newText);
}
Expand Down Expand Up @@ -240,6 +238,7 @@ export function resolveServices(
services.html ??= createVueTemplateLanguageService(
ts,
createHtmlService(),
getVueOptions,
{
getScanner: (htmlService, document): html.Scanner | undefined => {
return htmlService.provide['html/languageService']().createScanner(document.getText());
Expand All @@ -248,12 +247,12 @@ export function resolveServices(
htmlService.provide['html/updateCustomData'](extraData);
},
isSupportedDocument: (document) => document.languageId === 'html',
vueCompilerOptions,
}
);
services.pug ??= createVueTemplateLanguageService(
ts,
createPugService(),
getVueOptions,
{
getScanner: (pugService, document): html.Scanner | undefined => {
const pugDocument = pugService.provide['pug/pugDocument'](document);
Expand All @@ -265,7 +264,6 @@ export function resolveServices(
pugService.provide['pug/updateCustomData'](extraData);
},
isSupportedDocument: (document) => document.languageId === 'jade',
vueCompilerOptions,
}
);
services.vue ??= createVueService();
Expand Down
27 changes: 14 additions & 13 deletions packages/language-service/src/plugins/vue-template.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CodeInformation, ServicePluginInstance, SourceMapWithDocuments } from '@volar/language-service';
import { CodeInformation, ServiceEnvironment, ServicePluginInstance, SourceMapWithDocuments } from '@volar/language-service';
import { VueFile, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core';
import { camelize, capitalize } from '@vue/shared';
import { Provide } from 'volar-service-typescript';
Expand All @@ -16,11 +16,11 @@ let modelData: html.HTMLDataV1;
export function create(
ts: typeof import('typescript/lib/tsserverlibrary'),
baseServide: ServicePlugin,
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
options: {
getScanner(service: ServicePluginInstance, document: TextDocument): html.Scanner | undefined,
updateCustomData(service: ServicePluginInstance, extraData: html.IHTMLDataProvider[]): void,
isSupportedDocument: (document: TextDocument) => boolean,
vueCompilerOptions: VueCompilerOptions,
}
): ServicePlugin {
return {
Expand All @@ -32,6 +32,7 @@ export function create(
create(context): ServicePluginInstance {

const baseServiceInstance = baseServide.create(context);
const vueCompilerOptions = getVueOptions(context.env);

builtInData ??= loadTemplateData(context.env.locale ?? 'en');
modelData ??= loadModelModifiersData(context.env.locale ?? 'en');
Expand Down Expand Up @@ -125,8 +126,8 @@ export function create(
if (sourceVirtualFile instanceof VueFile && scanner) {

// visualize missing required props
const casing = await getNameCasing(ts, context, map.sourceFileDocument.uri, options.vueCompilerOptions);
const components = getComponentNames(ts, languageService, sourceVirtualFile, options.vueCompilerOptions);
const casing = await getNameCasing(ts, context, map.sourceFileDocument.uri, vueCompilerOptions);
const components = getComponentNames(ts, languageService, sourceVirtualFile, vueCompilerOptions);
const componentProps: Record<string, string[]> = {};
let token: html.TokenType;
let current: {
Expand All @@ -143,7 +144,7 @@ export function create(
: components.find(component => component === tagName || hyphenateTag(component) === tagName);
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
if (checkTag) {
componentProps[checkTag] ??= getPropsByTag(ts, languageService, sourceVirtualFile, checkTag, options.vueCompilerOptions, true);
componentProps[checkTag] ??= getPropsByTag(ts, languageService, sourceVirtualFile, checkTag, vueCompilerOptions, true);
current = {
unburnedRequiredProps: [...componentProps[checkTag]],
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
Expand Down Expand Up @@ -174,7 +175,7 @@ export function create(
attrText = attrText.substring('v-model:'.length);
}
else if (attrText === 'v-model') {
attrText = options.vueCompilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName?
attrText = vueCompilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName?
}
else if (attrText.startsWith('@')) {
attrText = 'on-' + hyphenateAttr(attrText.substring('@'.length));
Expand Down Expand Up @@ -309,7 +310,7 @@ export function create(
if (!(sourceFile instanceof VueFile))
continue;

const templateScriptData = getComponentNames(ts, languageService, sourceFile, options.vueCompilerOptions);
const templateScriptData = getComponentNames(ts, languageService, sourceFile, vueCompilerOptions);
const components = new Set([
...templateScriptData,
...templateScriptData.map(hyphenateTag),
Expand Down Expand Up @@ -358,7 +359,7 @@ export function create(
async function provideHtmlData(map: SourceMapWithDocuments<CodeInformation>, vueSourceFile: VueFile) {

const languageService = context.inject<Provide, 'typescript/languageService'>('typescript/languageService');
const casing = await getNameCasing(ts, context, map.sourceFileDocument.uri, options.vueCompilerOptions);
const casing = await getNameCasing(ts, context, map.sourceFileDocument.uri, vueCompilerOptions);

if (builtInData.tags) {
for (const tag of builtInData.tags) {
Expand All @@ -384,15 +385,15 @@ export function create(
isApplicable: () => true,
provideTags: () => {

const components = getComponentNames(ts, languageService, vueSourceFile, options.vueCompilerOptions)
const components = getComponentNames(ts, languageService, vueSourceFile, vueCompilerOptions)
.filter(name =>
name !== 'Transition'
&& name !== 'TransitionGroup'
&& name !== 'KeepAlive'
&& name !== 'Suspense'
&& name !== 'Teleport'
);
const scriptSetupRanges = vueSourceFile.sfc.scriptSetup ? parseScriptSetupRanges(ts, vueSourceFile.sfc.scriptSetup.ast, options.vueCompilerOptions) : undefined;
const scriptSetupRanges = vueSourceFile.sfc.scriptSetup ? parseScriptSetupRanges(ts, vueSourceFile.sfc.scriptSetup.ast, vueCompilerOptions) : undefined;
const names = new Set<string>();
const tags: html.ITagData[] = [];

Expand Down Expand Up @@ -430,8 +431,8 @@ export function create(
return [];

const attrs = getElementAttrs(ts, languageService, vueSourceFile.mainTsFile.fileName, tag);
const props = new Set(getPropsByTag(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions));
const events = getEventsOfTag(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions);
const props = new Set(getPropsByTag(ts, languageService, vueSourceFile, tag, vueCompilerOptions));
const events = getEventsOfTag(ts, languageService, vueSourceFile, tag, vueCompilerOptions);
const attributes: html.IAttributeData[] = [];
const _tsCodegen = tsCodegen.get(vueSourceFile.sfc);

Expand Down Expand Up @@ -555,7 +556,7 @@ export function create(

const languageService = context.inject<Provide, 'typescript/languageService'>('typescript/languageService');
const replacement = getReplacement(completionList, map.sourceFileDocument);
const componentNames = new Set(getComponentNames(ts, languageService, vueSourceFile, options.vueCompilerOptions).map(hyphenateTag));
const componentNames = new Set(getComponentNames(ts, languageService, vueSourceFile, vueCompilerOptions).map(hyphenateTag));

if (replacement) {

Expand Down
7 changes: 4 additions & 3 deletions packages/language-service/tests/utils/createTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createLanguage } from '@volar/typescript';
import * as path from 'path';
import type * as ts from 'typescript/lib/tsserverlibrary';
import { URI } from 'vscode-uri';
import { createParsedCommandLine, resolveLanguages, resolveServices } from '../../out';
import { createParsedCommandLine, resolveLanguages, resolveServices, resolveVueCompilerOptions } from '../../out';
import { createMockServiceEnv } from './mockEnv';

export const rootUri = URI.file(path.resolve(__dirname, '../../../../test-workspace/language-service'));
Expand All @@ -26,8 +26,9 @@ function createTester(rootUri: URI) {
getScriptSnapshot,
getLanguageId: resolveCommonLanguageId,
};
const languages = resolveLanguages(ts, {}, parsedCommandLine.options, parsedCommandLine.vueOptions);
const services = resolveServices(ts, {}, parsedCommandLine.vueOptions);
const resolvedVueOptions = resolveVueCompilerOptions(parsedCommandLine.vueOptions);
const languages = resolveLanguages({}, ts, parsedCommandLine.options, resolvedVueOptions);
const services = resolveServices({}, ts, () => resolvedVueOptions);
const defaultVSCodeSettings: any = {
'typescript.preferences.quoteStyle': 'single',
'javascript.preferences.quoteStyle': 'single',
Expand Down
7 changes: 4 additions & 3 deletions packages/language-service/tests/utils/format.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as kit from '@volar/kit';
import * as ts from 'typescript';
import { describe, expect, it } from 'vitest';
import { resolveLanguages, resolveServices } from '../../out';
import { resolveLanguages, resolveServices, resolveVueCompilerOptions } from '../../out';

const languages = resolveLanguages(ts);
const services = resolveServices(ts);
const resolvedVueOptions = resolveVueCompilerOptions({});
const languages = resolveLanguages({}, ts, {}, resolvedVueOptions);
const services = resolveServices({}, ts, () => resolvedVueOptions);
const formatter = kit.createFormatter(Object.values(languages), Object.values(services));

export function defineFormatTest(options: {
Expand Down

0 comments on commit 02d0ef4

Please sign in to comment.