From 842f33907fe17728f20ebae946fb7da2f6ab9831 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 28 Feb 2024 18:12:52 +0800 Subject: [PATCH 1/2] refactor(language-core): remove `experimentalAdditionalLanguageModules` and deprecated apis --- packages/component-meta/src/base.ts | 4 +- .../vue-tsconfig.deprecated.schema.json | 5 + .../schemas/vue-tsconfig.schema.json | 4 - packages/language-core/src/languageModule.ts | 19 +- packages/language-core/src/types.ts | 1 - packages/language-core/src/utils/ts.ts | 6 - packages/language-server/src/nodeServer.ts | 40 ++-- packages/language-service/src/index.ts | 52 ++++- packages/language-service/src/plugins/css.ts | 24 +++ .../typescript.ts} | 193 ++---------------- .../src/plugins/{vue.ts => vue-sfc.ts} | 2 +- .../src/plugins/vue-template.ts | 83 ++++++-- packages/tsc/src/index.ts | 3 +- packages/typescript-plugin/src/index.ts | 4 +- 14 files changed, 186 insertions(+), 254 deletions(-) create mode 100644 packages/language-service/src/plugins/css.ts rename packages/language-service/src/{languageService.ts => plugins/typescript.ts} (50%) rename packages/language-service/src/plugins/{vue.ts => vue-sfc.ts} (99%) diff --git a/packages/component-meta/src/base.ts b/packages/component-meta/src/base.ts index 227e7400f1..2c1a907e8b 100644 --- a/packages/component-meta/src/base.ts +++ b/packages/component-meta/src/base.ts @@ -149,7 +149,7 @@ export function baseCreate( } }; - const vueLanguagePlugins = vue.createLanguages( + const vueLanguagePlugin = vue.createVueLanguagePlugin( ts, id => id, host.getCompilationSettings(), @@ -158,7 +158,7 @@ export function baseCreate( const language = createLanguage( ts, ts.sys, - vueLanguagePlugins, + [vueLanguagePlugin], configFileName, host, { diff --git a/packages/language-core/schemas/vue-tsconfig.deprecated.schema.json b/packages/language-core/schemas/vue-tsconfig.deprecated.schema.json index 94224badac..a263aabd55 100644 --- a/packages/language-core/schemas/vue-tsconfig.deprecated.schema.json +++ b/packages/language-core/schemas/vue-tsconfig.deprecated.schema.json @@ -51,6 +51,11 @@ "experimentalComponentOptionsWrapperEnable": { "deprecated": true }, + "experimentalAdditionalLanguageModules": { + "deprecated": true, + "type": "array", + "markdownDescription": "https://github.com/vuejs/language-tools/pull/2267" + }, "bypassDefineComponentToExposePropsAndEmitsForJsScriptSetupComponents": { "deprecated": true }, diff --git a/packages/language-core/schemas/vue-tsconfig.schema.json b/packages/language-core/schemas/vue-tsconfig.schema.json index 07944fa142..9b5de7adcb 100644 --- a/packages/language-core/schemas/vue-tsconfig.schema.json +++ b/packages/language-core/schemas/vue-tsconfig.schema.json @@ -106,10 +106,6 @@ }, "markdownDescription": "https://github.com/vuejs/language-tools/issues/1969" }, - "experimentalAdditionalLanguageModules": { - "type": "array", - "markdownDescription": "https://github.com/vuejs/language-tools/pull/2267" - }, "experimentalDefinePropProposal": { "enum": [ "kevinEdition", diff --git a/packages/language-core/src/languageModule.ts b/packages/language-core/src/languageModule.ts index 0502ef1072..0f30ab98bb 100644 --- a/packages/language-core/src/languageModule.ts +++ b/packages/language-core/src/languageModule.ts @@ -50,7 +50,7 @@ function getFileRegistryKey( return JSON.stringify(values); } -export function createVueLanguage( +export function createVueLanguagePlugin( ts: typeof import('typescript'), getFileName: (fileId: string) => string, compilerOptions: ts.CompilerOptions = {}, @@ -160,20 +160,3 @@ export function createVueLanguage( }, }; } - -/** - * @deprecated planed to remove in 2.0, please use createVueLanguage instead of - */ -export function createLanguages( - ts: typeof import('typescript'), - getFileName: (fileId: string) => string, - compilerOptions: ts.CompilerOptions = {}, - vueCompilerOptions: Partial = {}, - codegenStack: boolean = false, - globalTypesHolder?: string -): LanguagePlugin[] { - return [ - createVueLanguage(ts, getFileName, compilerOptions, vueCompilerOptions, codegenStack, globalTypesHolder), - ...vueCompilerOptions.experimentalAdditionalLanguageModules?.map(module => require(module)) ?? [], - ]; -} diff --git a/packages/language-core/src/types.ts b/packages/language-core/src/types.ts index 0bcd0f77cb..18a2a144d5 100644 --- a/packages/language-core/src/types.ts +++ b/packages/language-core/src/types.ts @@ -55,7 +55,6 @@ export interface VueCompilerOptions { experimentalResolveStyleCssClasses: 'scoped' | 'always' | 'never'; experimentalModelPropName: Record | Record[]>>; experimentalUseElementAccessInTemplate: boolean; - experimentalAdditionalLanguageModules: string[]; } export type VueLanguagePlugin = (ctx: { diff --git a/packages/language-core/src/utils/ts.ts b/packages/language-core/src/utils/ts.ts index 92e6a02395..6ed5a469a8 100644 --- a/packages/language-core/src/utils/ts.ts +++ b/packages/language-core/src/utils/ts.ts @@ -178,11 +178,6 @@ function getPartialVueCompilerOptions( result.plugins = plugins; } - if (rawOptions.experimentalAdditionalLanguageModules) { - result.experimentalAdditionalLanguageModules = rawOptions.experimentalAdditionalLanguageModules - .map(resolvePath) - .filter((module): module is NonNullable => !!module); - } return result; @@ -266,7 +261,6 @@ export function resolveVueCompilerOptions(vueOptions: Partial(); -const envToVueOptions = new WeakMap(); +const checkers = new WeakMap(); +const envToVueOptions = new WeakMap(); let tsdk: ReturnType; @@ -39,23 +37,21 @@ connection.onInitialize(params => { { watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions], getServicePlugins() { - const services = vue.resolveServices({}, tsdk.typescript, env => envToVueOptions.get(env)!); - - return Object.values(services); + return createVueServicePlugins(tsdk.typescript, env => envToVueOptions.get(env)!); }, async getLanguagePlugins(serviceEnv, projectContext) { const [commandLine, vueOptions] = await parseCommandLine(); - const resolvedVueOptions = vue.resolveVueCompilerOptions(vueOptions); - const languages = vue.resolveLanguages({}, tsdk.typescript, serviceEnv.typescript!.uriToFileName, commandLine?.options ?? {}, resolvedVueOptions, options.codegenStack); + const resolvedVueOptions = resolveVueCompilerOptions(vueOptions); + const vueLanguagePlugin = createVueLanguagePlugin(tsdk.typescript, serviceEnv.typescript!.uriToFileName, commandLine?.options ?? {}, resolvedVueOptions, options.codegenStack); envToVueOptions.set(serviceEnv, resolvedVueOptions); - return Object.values(languages); + return [vueLanguagePlugin]; async function parseCommandLine() { - let commandLine: vue2.ParsedCommandLine | undefined; - let vueOptions: Partial = {}; + let commandLine: ParsedCommandLine | undefined; + let vueOptions: Partial = {}; if (projectContext.typescript) { @@ -67,7 +63,7 @@ connection.onInitialize(params => { while (sysVersion !== newSysVersion) { sysVersion = newSysVersion; if (projectContext.typescript.configFileName) { - commandLine = vue2.createParsedCommandLine(tsdk.typescript, sys, projectContext.typescript.configFileName); + commandLine = createParsedCommandLine(tsdk.typescript, sys, projectContext.typescript.configFileName); } newSysVersion = await sys.sync(); } @@ -98,20 +94,20 @@ connection.onShutdown(() => { }); connection.onRequest(ParseSFCRequest.type, params => { - return vue2.parse(params); + return parse(params); }); connection.onRequest(DetectNameCasingRequest.type, async params => { const languageService = await getService(params.textDocument.uri); if (languageService) { - return nameCasing.detect(tsdk.typescript, languageService.context, params.textDocument.uri, envToVueOptions.get(languageService.context.env)!); + return detect(tsdk.typescript, languageService.context, params.textDocument.uri, envToVueOptions.get(languageService.context.env)!); } }); connection.onRequest(GetConvertTagCasingEditsRequest.type, async params => { const languageService = await getService(params.textDocument.uri); if (languageService) { - return nameCasing.convertTagName(tsdk.typescript, languageService.context, params.textDocument.uri, params.casing, envToVueOptions.get(languageService.context.env)!); + return convertTagName(tsdk.typescript, languageService.context, params.textDocument.uri, params.casing, envToVueOptions.get(languageService.context.env)!); } }); @@ -120,7 +116,7 @@ connection.onRequest(GetConvertAttrCasingEditsRequest.type, async params => { if (languageService) { const vueOptions = envToVueOptions.get(languageService.context.env); if (vueOptions) { - return nameCasing.convertAttrName(tsdk.typescript, languageService.context, params.textDocument.uri, params.casing, envToVueOptions.get(languageService.context.env)!); + return convertAttrName(tsdk.typescript, languageService.context, params.textDocument.uri, params.casing, envToVueOptions.get(languageService.context.env)!); } } }); @@ -132,7 +128,7 @@ connection.onRequest(GetComponentMeta.type, async params => { let checker = checkers.get(project); if (!checker) { - checker = componentMeta.baseCreate( + checker = baseCreate( tsdk.typescript, langaugeService.context.language.typescript!.configFileName, langaugeService.context.language.typescript!.projectHost, diff --git a/packages/language-service/src/index.ts b/packages/language-service/src/index.ts index e8802f1b09..512c9e0b81 100644 --- a/packages/language-service/src/index.ts +++ b/packages/language-service/src/index.ts @@ -1,5 +1,53 @@ export * from '@volar/language-service'; export * from '@vue/language-core'; export * from './ideFeatures/nameCasing'; -export * from './languageService'; -export { TagNameCasing, AttrNameCasing } from './types'; +export * from './types'; + +import type { ServiceEnvironment, ServicePlugin } from '@volar/language-service'; +import type { VueCompilerOptions } from './types'; + +import { create as createEmmetServicePlugin } from 'volar-service-emmet'; +import { create as createJsonServicePlugin } from 'volar-service-json'; +import { create as createPugFormatServicePlugin } from 'volar-service-pug-beautify'; +import { create as createTypeScriptTwoslashQueriesServicePlugin } from 'volar-service-typescript-twoslash-queries'; +import { create as createCssServicePlugin } from './plugins/css'; +import { create as createTypeScriptServicePlugin } from './plugins/typescript'; +import { create as createVueAutoDotValueServicePlugin } from './plugins/vue-autoinsert-dotvalue'; +import { create as createVueAutoWrapParenthesesServicePlugin } from './plugins/vue-autoinsert-parentheses'; +import { create as createVueAutoAddSpaceServicePlugin } from './plugins/vue-autoinsert-space'; +import { create as createVueReferencesCodeLensServicePlugin } from './plugins/vue-codelens-references'; +import { create as createVueDirectiveCommentsServicePlugin } from './plugins/vue-directive-comments'; +import { create as createVueDocumentDropServicePlugin } from './plugins/vue-document-drop'; +import { create as createVueExtractFileServicePlugin } from './plugins/vue-extract-file'; +import { create as createVueSfcServicePlugin } from './plugins/vue-sfc'; +import { create as createVueTemplateServicePlugin } from './plugins/vue-template'; +import { create as createVueToggleVBindServicePlugin } from './plugins/vue-toggle-v-bind-codeaction'; +import { create as createVueTwoslashQueriesServicePlugin } from './plugins/vue-twoslash-queries'; +import { create as createVueVisualizeHiddenCallbackParamServicePlugin } from './plugins/vue-visualize-hidden-callback-param'; + +export function createVueServicePlugins( + ts: typeof import('typescript'), + getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions, +): ServicePlugin[] { + return [ + createTypeScriptServicePlugin(ts, getVueOptions), + createTypeScriptTwoslashQueriesServicePlugin(), + createCssServicePlugin(), + createPugFormatServicePlugin(), + createJsonServicePlugin(), + createVueTemplateServicePlugin('html', ts, getVueOptions), + createVueTemplateServicePlugin('pug', ts, getVueOptions), + createVueSfcServicePlugin(), + createVueTwoslashQueriesServicePlugin(ts), + createVueReferencesCodeLensServicePlugin(), + createVueDocumentDropServicePlugin(ts), + createVueAutoDotValueServicePlugin(ts), + createVueAutoWrapParenthesesServicePlugin(ts), + createVueAutoAddSpaceServicePlugin(), + createVueVisualizeHiddenCallbackParamServicePlugin(), + createVueDirectiveCommentsServicePlugin(), + createVueExtractFileServicePlugin(ts), + createVueToggleVBindServicePlugin(ts), + createEmmetServicePlugin(), + ]; +} diff --git a/packages/language-service/src/plugins/css.ts b/packages/language-service/src/plugins/css.ts new file mode 100644 index 0000000000..f63fbbbb17 --- /dev/null +++ b/packages/language-service/src/plugins/css.ts @@ -0,0 +1,24 @@ +import { ServicePlugin, ServicePluginInstance } from '@volar/language-service'; +import { create as baseCreate } from 'volar-service-css'; + +export function create(): ServicePlugin { + const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] }); + return { + ...base, + create(context): ServicePluginInstance { + const baseInstance = base.create(context); + return { + ...baseInstance, + async provideDiagnostics(document, token) { + let diagnostics = await baseInstance.provideDiagnostics?.(document, token) ?? []; + if (document.languageId === 'postcss') { + diagnostics = diagnostics.filter(diag => diag.code !== 'css-semicolonexpected'); + diagnostics = diagnostics.filter(diag => diag.code !== 'css-ruleorselectorexpected'); + diagnostics = diagnostics.filter(diag => diag.code !== 'unknownAtRules'); + } + return diagnostics; + }, + }; + }, + }; +} diff --git a/packages/language-service/src/languageService.ts b/packages/language-service/src/plugins/typescript.ts similarity index 50% rename from packages/language-service/src/languageService.ts rename to packages/language-service/src/plugins/typescript.ts index a857127f81..0982136dae 100644 --- a/packages/language-service/src/languageService.ts +++ b/packages/language-service/src/plugins/typescript.ts @@ -1,78 +1,26 @@ import { ServiceEnvironment, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; -import { LanguagePlugin, VueGeneratedCode, createLanguages, hyphenateTag, scriptRanges } from '@vue/language-core'; +import { VueCompilerOptions, VueGeneratedCode, hyphenateTag, scriptRanges } from '@vue/language-core'; import { capitalize } from '@vue/shared'; -import type * as ts from 'typescript'; +import { Provide as TSProvide, create as baseCreate } from 'volar-service-typescript'; import type { Data } from 'volar-service-typescript/lib/features/completions/basic'; -import type * as html from 'vscode-html-languageservice'; import type * as vscode from 'vscode-languageserver-protocol'; -import { getNameCasing } from './ideFeatures/nameCasing'; -import { TagNameCasing, VueCompilerOptions } from './types'; +import { getNameCasing } from '../ideFeatures/nameCasing'; +import { TagNameCasing } from '../types'; +import { createAddComponentToOptionEdit } from './vue-extract-file'; -// volar services -import { create as createCssService } from 'volar-service-css'; -import { create as createEmmetService } from 'volar-service-emmet'; -import { create as createHtmlService } from 'volar-service-html'; -import { create as createJsonService } from 'volar-service-json'; -import { create as createPugService } from 'volar-service-pug'; -import { create as createPugFormatService } from 'volar-service-pug-beautify'; -import { create as createTsService, Provide as TSProvide } from 'volar-service-typescript'; -import { create as createTsTqService } from 'volar-service-typescript-twoslash-queries'; - -// our services -import { create as createVueService } from './plugins/vue'; -import { create as createDocumentDropService } from './plugins/vue-document-drop'; -import { create as createAutoDotValueService } from './plugins/vue-autoinsert-dotvalue'; -import { create as createAutoWrapParenthesesService } from './plugins/vue-autoinsert-parentheses'; -import { create as createAutoAddSpaceService } from './plugins/vue-autoinsert-space'; -import { create as createReferencesCodeLensService } from './plugins/vue-codelens-references'; -import { create as createDirectiveCommentsService } from './plugins/vue-directive-comments'; -import { create as createVueExtractFileService, createAddComponentToOptionEdit } from './plugins/vue-extract-file'; -import { create as createVueTemplateLanguageService } from './plugins/vue-template'; -import { create as createToggleVBindService } from './plugins/vue-toggle-v-bind-codeaction'; -import { create as createVueTqService } from './plugins/vue-twoslash-queries'; -import { create as createVisualizeHiddenCallbackParamService } from './plugins/vue-visualize-hidden-callback-param'; - -export interface Settings { - json?: Parameters[0]; -} - -export function resolveLanguages( - languages: Record, - ts: typeof import('typescript'), - getFileName: (fileId: string) => string, - compilerOptions: ts.CompilerOptions, - vueCompilerOptions: VueCompilerOptions, - codegenStack: boolean = false, -): Record { - - const vueLanguageModules = createLanguages(ts, getFileName, compilerOptions, vueCompilerOptions, codegenStack); - - return { - ...languages, - ...vueLanguageModules.reduce((obj, module, i) => { - obj['vue_' + i] = module; - return obj; - }, {} as Record), - }; -} - -export function resolveServices( - services: Record, +export function create( ts: typeof import('typescript'), getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions, -) { - - const tsService: ServicePlugin = services?.typescript ?? createTsService(ts); - - services ??= {}; - services.typescript = { - ...tsService, - create(context) { - const base = tsService.create(context); +): ServicePlugin { + const base = baseCreate(ts); + return { + ...base, + create(context): ServicePluginInstance { + const baseInstance = base.create(context); return { - ...base, + ...baseInstance, async provideCompletionItems(document, position, completeContext, item) { - const result = await base.provideCompletionItems?.(document, position, completeContext, item); + const result = await baseInstance.provideCompletionItems?.(document, position, completeContext, item); if (result) { // filter __VLS_ @@ -118,7 +66,7 @@ export function resolveServices( }, async resolveCompletionItem(item, token) { - item = await base.resolveCompletionItem?.(item, token) ?? item; + item = await baseInstance.resolveCompletionItem?.(item, token) ?? item; const itemData = item.data as { uri?: string; } | undefined; @@ -192,12 +140,12 @@ export function resolveServices( return item; }, async provideCodeActions(document, range, context, token) { - const result = await base.provideCodeActions?.(document, range, context, token); + const result = await baseInstance.provideCodeActions?.(document, range, context, token); return result?.filter(codeAction => codeAction.title.indexOf('__VLS_') === -1); }, async resolveCodeAction(item, token) { - const result = await base.resolveCodeAction?.(item, token) ?? item; + const result = await baseInstance.resolveCodeAction?.(item, token) ?? item; if (result?.edit?.changes) { for (const uri in result.edit.changes) { @@ -218,7 +166,7 @@ export function resolveServices( return result; }, async provideSemanticDiagnostics(document, token) { - const result = await base.provideSemanticDiagnostics?.(document, token); + const result = await baseInstance.provideSemanticDiagnostics?.(document, token); return result?.map(diagnostic => { if ( diagnostic.source === 'ts' @@ -235,111 +183,6 @@ export function resolveServices( }; }, }; - - let customData: html.IHTMLDataProvider[] = []; - const onDidChangeCustomDataListeners = new Set<() => void>(); - - services.html ??= createVueTemplateLanguageService( - ts, - createHtmlService({ - getCustomData() { - return customData; - }, - onDidChangeCustomData(listener) { - onDidChangeCustomDataListeners.add(listener); - return { - dispose() { - onDidChangeCustomDataListeners.delete(listener); - }, - }; - }, - }), - getVueOptions, - { - getScanner: (htmlService, document): html.Scanner | undefined => { - return htmlService.provide['html/languageService']().createScanner(document.getText()); - }, - updateCustomData(extraData) { - customData = extraData; - onDidChangeCustomDataListeners.forEach(l => l()); - }, - isSupportedDocument: (document) => document.languageId === 'html', - } - ); - services.pug ??= createVueTemplateLanguageService( - ts, - createPugService({ - getCustomData() { - return customData; - }, - onDidChangeCustomData(listener) { - onDidChangeCustomDataListeners.add(listener); - return { - dispose() { - onDidChangeCustomDataListeners.delete(listener); - }, - }; - }, - }), - getVueOptions, - { - getScanner(pugService, document): html.Scanner | undefined { - const pugDocument = pugService.provide['pug/pugDocument'](document); - if (pugDocument) { - return pugService.provide['pug/languageService']().createScanner(pugDocument); - } - }, - updateCustomData(extraData) { - customData = extraData; - onDidChangeCustomDataListeners.forEach(l => l()); - }, - isSupportedDocument(document) { - return document.languageId === 'jade'; - }, - } - ); - services.vue ??= createVueService(); - - const baseCssService = createCssService({ scssDocumentSelector: ['scss', 'postcss'] }); - const cssService: ServicePlugin = { - ...baseCssService, - create(context): ServicePluginInstance { - const serviceInstance = baseCssService.create(context); - return { - ...serviceInstance, - async provideDiagnostics(document, token) { - let diagnostics = await serviceInstance.provideDiagnostics?.(document, token) ?? []; - if (document.languageId === 'postcss') { - diagnostics = diagnostics.filter(diag => diag.code !== 'css-semicolonexpected'); - diagnostics = diagnostics.filter(diag => diag.code !== 'css-ruleorselectorexpected'); - diagnostics = diagnostics.filter(diag => diag.code !== 'unknownAtRules'); - } - return diagnostics; - }, - }; - }, - }; - - services.css ??= cssService; - services['pug-beautify'] ??= createPugFormatService(); - services.json ??= createJsonService(); - services['typescript/twoslash-queries'] ??= createTsTqService(); - services['vue/referencesCodeLens'] ??= createReferencesCodeLensService(); - services['vue/documentDrop'] ??= createDocumentDropService(ts); - services['vue/autoInsertDotValue'] ??= createAutoDotValueService(ts); - services['vue/twoslash-queries'] ??= createVueTqService(ts); - services['vue/autoInsertParentheses'] ??= createAutoWrapParenthesesService(ts); - services['vue/autoInsertSpaces'] ??= createAutoAddSpaceService(); - services['vue/visualizeHiddenCallbackParam'] ??= createVisualizeHiddenCallbackParamService(); - services['vue/directiveComments'] ??= createDirectiveCommentsService(); - services['vue/extractComponent'] ??= createVueExtractFileService(ts); - services['vue/toggleVBind'] ??= createToggleVBindService(ts); - services.emmet ??= createEmmetService(); - - services.html.name += ' (html)'; - services.pug.name += ' (pug)'; - - return services; } // fix https://github.com/vuejs/language-tools/issues/916 diff --git a/packages/language-service/src/plugins/vue.ts b/packages/language-service/src/plugins/vue-sfc.ts similarity index 99% rename from packages/language-service/src/plugins/vue.ts rename to packages/language-service/src/plugins/vue-sfc.ts index 3edd8e53d8..a9b8a60d6d 100644 --- a/packages/language-service/src/plugins/vue.ts +++ b/packages/language-service/src/plugins/vue-sfc.ts @@ -15,7 +15,7 @@ export interface Provide { export function create(): ServicePlugin { return { - name: 'vue-basic', + name: 'vue-sfc', create(context): ServicePluginInstance { const htmlPlugin = createHtmlService({ diff --git a/packages/language-service/src/plugins/vue-template.ts b/packages/language-service/src/plugins/vue-template.ts index 58dbbb21ac..2b96b5e587 100644 --- a/packages/language-service/src/plugins/vue-template.ts +++ b/packages/language-service/src/plugins/vue-template.ts @@ -1,4 +1,4 @@ -import { CodeInformation, ServiceEnvironment, ServicePluginInstance, SourceMapWithDocuments } from '@volar/language-service'; +import { CodeInformation, ServiceEnvironment, ServicePluginInstance, SourceMapWithDocuments, Disposable } from '@volar/language-service'; import { VueGeneratedCode, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core'; import { camelize, capitalize } from '@vue/shared'; import { Provide } from 'volar-service-typescript'; @@ -9,29 +9,46 @@ import { getComponentNames, getElementAttrs, getEventsOfTag, getPropsByTag, getT import { getNameCasing } from '../ideFeatures/nameCasing'; import { AttrNameCasing, ServicePlugin, TagNameCasing, VueCompilerOptions } from '../types'; import { loadModelModifiersData, loadTemplateData } from './data'; +import { create as createHtmlService } from 'volar-service-html'; +import { create as createPugService } from 'volar-service-pug'; let builtInData: html.HTMLDataV1; let modelData: html.HTMLDataV1; export function create( + mode: 'html' | 'pug', ts: typeof import('typescript'), - baseServide: ServicePlugin, getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions, - options: { - getScanner(service: ServicePluginInstance, document: TextDocument): html.Scanner | undefined, - updateCustomData(extraData: html.IHTMLDataProvider[]): void, - isSupportedDocument: (document: TextDocument) => boolean, - } ): ServicePlugin { + + let customData: html.IHTMLDataProvider[] = []; + + const onDidChangeCustomDataListeners = new Set<() => void>(); + const onDidChangeCustomData = (listener: () => void): Disposable => { + onDidChangeCustomDataListeners.add(listener); + return { + dispose() { + onDidChangeCustomDataListeners.delete(listener); + }, + }; + }; + const baseServicePlugin = mode === 'pug' ? createPugService : createHtmlService; + const baseService = baseServicePlugin({ + getCustomData() { + return customData; + }, + onDidChangeCustomData, + }); + return { - name: 'vue-template', + name: `vue-template (${mode})`, triggerCharacters: [ - ...baseServide.triggerCharacters ?? [], + ...baseService.triggerCharacters ?? [], '@', // vue event shorthand ], create(context): ServicePluginInstance { - const baseServiceInstance = baseServide.create(context); + const baseServiceInstance = baseService.create(context); const vueCompilerOptions = getVueOptions(context.env); builtInData ??= loadTemplateData(context.env.locale ?? 'en'); @@ -73,7 +90,7 @@ export function create( async provideCompletionItems(document, position, completionContext, token) { - if (!options.isSupportedDocument(document)) + if (!isSupportedDocument(document)) return; const [virtualCode] = context.documents.getVirtualCodeByUri(document.uri); @@ -105,7 +122,7 @@ export function create( async provideInlayHints(document) { - if (!options.isSupportedDocument(document)) + if (!isSupportedDocument(document)) return; const enabled = await context.env.getConfiguration?.('vue.inlayHints.missingProps') ?? false; @@ -121,7 +138,7 @@ export function create( for (const map of context.documents.getMaps(virtualCode)) { const sourceVirtualFile = context.language.files.get(map.sourceDocument.uri)?.generated?.code; - const scanner = options.getScanner(baseServiceInstance, document); + const scanner = getScanner(baseServiceInstance, document); if (sourceVirtualFile instanceof VueGeneratedCode && scanner) { @@ -222,18 +239,18 @@ export function create( provideHover(document, position, token) { - if (!options.isSupportedDocument(document)) + if (!isSupportedDocument(document)) return; if (context.documents.getVirtualCodeByUri(document.uri)[0]) - options.updateCustomData([]); + updateCustomData([]); return baseServiceInstance.provideHover?.(document, position, token); }, async provideDiagnostics(document, token) { - if (!options.isSupportedDocument(document)) + if (!isSupportedDocument(document)) return; const originalResult = await baseServiceInstance.provideDiagnostics?.(document, token); @@ -291,11 +308,11 @@ export function create( async provideDocumentSemanticTokens(document, range, legend, token) { - if (!options.isSupportedDocument(document)) + if (!isSupportedDocument(document)) return; const result = await baseServiceInstance.provideDocumentSemanticTokens?.(document, range, legend, token) ?? []; - const scanner = options.getScanner(baseServiceInstance, document); + const scanner = getScanner(baseServiceInstance, document); if (!scanner) return; @@ -378,7 +395,7 @@ export function create( } } - options.updateCustomData([ + updateCustomData([ html.newHTMLDataProvider('vue-template-built-in', builtInData), { getId: () => 'vue-template', @@ -677,10 +694,36 @@ export function create( } } - options.updateCustomData([]); + updateCustomData([]); } }, }; + + function getScanner(service: ServicePluginInstance, document: TextDocument) { + if (mode === 'html') { + return service.provide['html/languageService']().createScanner(document.getText()); + } + else { + const pugDocument = service.provide['pug/pugDocument'](document); + if (pugDocument) { + return service.provide['pug/languageService']().createScanner(pugDocument); + } + } + } + + function updateCustomData(extraData: html.IHTMLDataProvider[]) { + customData = extraData; + onDidChangeCustomDataListeners.forEach(l => l()); + } + + function isSupportedDocument(document: TextDocument) { + if (mode === 'pug') { + return document.languageId === 'jade'; + } + else { + return document.languageId === 'html'; + } + } }; function createInternalItemId(type: 'componentEvent' | 'componentProp', args: string[]) { diff --git a/packages/tsc/src/index.ts b/packages/tsc/src/index.ts index 825c915bee..4b0f79edd6 100644 --- a/packages/tsc/src/index.ts +++ b/packages/tsc/src/index.ts @@ -21,7 +21,7 @@ export function run() { runExtensions.length === extensions.length && runExtensions.every(ext => extensions.includes(ext)) ) { - return vue.createLanguages( + const vueLanguagePlugin = vue.createVueLanguagePlugin( ts, id => id, options.options, @@ -29,6 +29,7 @@ export function run() { false, createFakeGlobalTypesHolder(options)?.replace(windowsPathReg, '/'), ); + return [vueLanguagePlugin]; } else { runExtensions = extensions; diff --git a/packages/typescript-plugin/src/index.ts b/packages/typescript-plugin/src/index.ts index affc66336b..190370f911 100644 --- a/packages/typescript-plugin/src/index.ts +++ b/packages/typescript-plugin/src/index.ts @@ -6,7 +6,7 @@ const windowsPathReg = /\\/g; export = createLanguageServicePlugin((ts, info) => { const vueOptions = vue.resolveVueCompilerOptions(getVueCompilerOptions()); - const languagePlugins = vue.createLanguages( + const languagePlugin = vue.createVueLanguagePlugin( ts, id => id, info.languageServiceHost.getCompilationSettings(), @@ -22,7 +22,7 @@ export = createLanguageServicePlugin((ts, info) => { return result; }; - return languagePlugins; + return [languagePlugin]; function getVueCompilerOptions() { if (info.project.projectKind === ts.server.ProjectKind.Configured) { From ef64a9bb2d0fc9c3c46eceb394445b0296c35434 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 28 Feb 2024 18:20:39 +0800 Subject: [PATCH 2/2] updates --- packages/language-service/tests/utils/createTester.ts | 10 +++++----- packages/language-service/tests/utils/format.ts | 8 ++++---- packages/tsc/tests/dts.spec.ts | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/language-service/tests/utils/createTester.ts b/packages/language-service/tests/utils/createTester.ts index 255025bd47..8de133f95d 100644 --- a/packages/language-service/tests/utils/createTester.ts +++ b/packages/language-service/tests/utils/createTester.ts @@ -3,7 +3,7 @@ import { createLanguage } from '@volar/typescript'; import * as path from 'path'; import type * as ts from 'typescript'; import { URI } from 'vscode-uri'; -import { createParsedCommandLine, resolveLanguages, resolveServices, resolveVueCompilerOptions } from '../../out'; +import { createParsedCommandLine, createVueLanguagePlugin, createVueServicePlugins, resolveVueCompilerOptions } from '../../out'; import { createMockServiceEnv } from './mockEnv'; export const rootUri = URI.file(path.resolve(__dirname, '../../../../test-workspace/language-service')).toString(); @@ -27,8 +27,8 @@ function createTester(rootUri: string) { getLanguageId: resolveCommonLanguageId, }; const resolvedVueOptions = resolveVueCompilerOptions(parsedCommandLine.vueOptions); - const languages = resolveLanguages({}, ts, serviceEnv.typescript!.uriToFileName, parsedCommandLine.options, resolvedVueOptions); - const services = resolveServices({}, ts, () => resolvedVueOptions); + const vueLanguagePlugin = createVueLanguagePlugin(ts, serviceEnv.typescript!.uriToFileName, parsedCommandLine.options, resolvedVueOptions); + const vueServicePlugins = createVueServicePlugins(ts, () => resolvedVueOptions); const defaultVSCodeSettings: any = { 'typescript.preferences.quoteStyle': 'single', 'javascript.preferences.quoteStyle': 'single', @@ -40,7 +40,7 @@ function createTester(rootUri: string) { const language = createLanguage( ts, ts.sys, - Object.values(languages), + [vueLanguagePlugin], realTsConfig, projectHost, { @@ -48,7 +48,7 @@ function createTester(rootUri: string) { fileNameToFileId: serviceEnv.typescript!.fileNameToUri, }, ); - const languageService = createLanguageService(language, Object.values(services), serviceEnv); + const languageService = createLanguageService(language, vueServicePlugins, serviceEnv); return { serviceEnv, diff --git a/packages/language-service/tests/utils/format.ts b/packages/language-service/tests/utils/format.ts index 72dffd5fd9..31ef0f280d 100644 --- a/packages/language-service/tests/utils/format.ts +++ b/packages/language-service/tests/utils/format.ts @@ -1,12 +1,12 @@ import * as kit from '@volar/kit'; import * as ts from 'typescript'; import { describe, expect, it } from 'vitest'; -import { resolveLanguages, resolveServices, resolveVueCompilerOptions } from '../../out'; +import { createVueLanguagePlugin, createVueServicePlugins, resolveVueCompilerOptions } from '../../out'; const resolvedVueOptions = resolveVueCompilerOptions({}); -const languages = resolveLanguages({}, ts, fileId => formatter.env.typescript!.uriToFileName(fileId), {}, resolvedVueOptions); -const services = resolveServices({}, ts, () => resolvedVueOptions); -const formatter = kit.createFormatter(Object.values(languages), Object.values(services)); +const vueLanguagePlugin = createVueLanguagePlugin(ts, fileId => formatter.env.typescript!.uriToFileName(fileId), {}, resolvedVueOptions); +const vueServicePLugins = createVueServicePlugins(ts, () => resolvedVueOptions); +const formatter = kit.createFormatter([vueLanguagePlugin], vueServicePLugins); export function defineFormatTest(options: { title: string; diff --git a/packages/tsc/tests/dts.spec.ts b/packages/tsc/tests/dts.spec.ts index 63346a25a6..9f450597a5 100644 --- a/packages/tsc/tests/dts.spec.ts +++ b/packages/tsc/tests/dts.spec.ts @@ -30,7 +30,7 @@ describe('vue-tsc-dts', () => { const vueOptions = typeof configFilePath === 'string' ? vue.createParsedCommandLine(ts, ts.sys, configFilePath.replace(windowsPathReg, '/')).vueOptions : {}; - return vue.createLanguages( + const vueLanguagePlugin = vue.createVueLanguagePlugin( ts, id => id, options.options, @@ -38,6 +38,7 @@ describe('vue-tsc-dts', () => { false, fakeGlobalTypesHolder?.replace(windowsPathReg, '/'), ); + return [vueLanguagePlugin]; }); const program = createProgram(options);