From 0e54fedb8188fe27f8695c0de73358fb5ca3d648 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 24 Nov 2023 15:36:56 +0800 Subject: [PATCH] first fix for https://github.com/volarjs/volar.js/pull/91 [skip ci] --- packages/component-meta/src/base.ts | 51 +++-- .../language-core/src/generators/script.ts | 72 +++---- .../language-core/src/generators/template.ts | 204 +++++++++++------- packages/language-core/src/languageModule.ts | 19 +- packages/language-core/src/plugins/file-md.ts | 4 +- .../src/plugins/vue-sfc-customblocks.ts | 4 +- .../src/plugins/vue-sfc-scripts.ts | 15 +- .../src/plugins/vue-sfc-styles.ts | 4 +- .../src/plugins/vue-sfc-template.ts | 4 +- packages/language-core/src/plugins/vue-tsx.ts | 40 ++-- packages/language-core/src/types.ts | 13 ++ .../src/virtualFile/computedFiles.ts | 30 ++- .../src/virtualFile/computedMappings.ts | 18 +- .../src/virtualFile/embeddedFile.ts | 9 +- .../language-core/src/virtualFile/vueFile.ts | 14 +- packages/language-plugin-pug/src/index.ts | 2 +- .../src/languageServerPlugin.ts | 35 +-- .../src/ideFeatures/dragImport.ts | 8 +- .../src/ideFeatures/nameCasing.ts | 10 +- .../language-service/src/languageService.ts | 8 +- .../src/plugins/vue-autoinsert-parentheses.ts | 10 +- .../src/plugins/vue-codelens-references.ts | 16 +- .../src/plugins/vue-extract-file.ts | 12 +- .../src/plugins/vue-template.ts | 26 +-- .../src/plugins/vue-twoslash-queries.ts | 14 +- .../vue-visualize-hidden-callback-param.ts | 17 +- .../tests/utils/createTester.ts | 18 +- packages/tsc-eslint-hook/src/index.ts | 18 +- packages/tsc/src/index.ts | 62 +++--- packages/typescript-plugin/src/index.ts | 1 + 30 files changed, 409 insertions(+), 349 deletions(-) diff --git a/packages/component-meta/src/base.ts b/packages/component-meta/src/base.ts index e0d58439c2..cba220ab32 100644 --- a/packages/component-meta/src/base.ts +++ b/packages/component-meta/src/base.ts @@ -3,7 +3,7 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import * as path from 'path-browserify'; import { code as typeHelpersCode } from 'vue-component-type-helpers'; import { code as vue2TypeHelpersCode } from 'vue-component-type-helpers/vue2'; -import { createLanguageServiceHost, decorateLanguageService } from '@volar/typescript'; +import { createProject, decorateLanguageService, ProjectHost } from '@volar/typescript'; import type { MetaCheckerOptions, @@ -71,8 +71,7 @@ function createCheckerWorker( let projectVersion = 0; const scriptSnapshots = new Map(); - const _host: vue.TypeScriptProjectHost = { - configFileName, + const _host: ProjectHost = { getCurrentDirectory: () => rootPath, getProjectVersion: () => projectVersion.toString(), getCompilationSettings: () => parsedCommandLine.options, @@ -87,10 +86,13 @@ function createCheckerWorker( } return scriptSnapshots.get(fileName); }, + getFileId: fileName => fileName, + getFileName: id => id, + getLanguageId: vue.resolveCommonLanguageId, }; return { - ...baseCreate(ts, _host, vue.resolveVueCompilerOptions(parsedCommandLine.vueOptions), checkerOptions, globalComponentName), + ...baseCreate(ts, configFileName, _host, vue.resolveVueCompilerOptions(parsedCommandLine.vueOptions), checkerOptions, globalComponentName), updateFile(fileName: string, text: string) { fileName = fileName.replace(windowsPathReg, '/'); scriptSnapshots.set(fileName, ts.ScriptSnapshot.fromString(text)); @@ -115,16 +117,17 @@ function createCheckerWorker( export function baseCreate( ts: typeof import('typescript/lib/tsserverlibrary'), - host: vue.TypeScriptProjectHost, + configFileName: string | undefined, + projectHost: ProjectHost, vueCompilerOptions: vue.VueCompilerOptions, checkerOptions: MetaCheckerOptions, globalComponentName: string, ) { const globalComponentSnapshot = ts.ScriptSnapshot.fromString(''); const metaSnapshots: Record = {}; - const getScriptFileNames = host.getScriptFileNames; - const getScriptSnapshot = host.getScriptSnapshot; - host.getScriptFileNames = () => { + const getScriptFileNames = projectHost.getScriptFileNames; + const getScriptSnapshot = projectHost.getScriptSnapshot; + projectHost.getScriptFileNames = () => { const names = getScriptFileNames(); return [ ...names, @@ -133,7 +136,7 @@ export function baseCreate( getMetaFileName(globalComponentName), ]; }; - host.getScriptSnapshot = (fileName) => { + projectHost.getScriptSnapshot = (fileName) => { if (isMetaFileName(fileName)) { if (!metaSnapshots[fileName]) { metaSnapshots[fileName] = ts.ScriptSnapshot.fromString(getMetaScriptContent(fileName)); @@ -150,22 +153,24 @@ export function baseCreate( const vueLanguages = vue.createLanguages( ts, - host.getCompilationSettings(), + projectHost.getCompilationSettings(), vueCompilerOptions, ); - const fileNameResolutionHost = { - fileNameToId: (fileName: string) => fileName, - idToFileName: (id: string) => id, - }; - const project = vue.createTypeScriptProject(vueLanguages, host, fileNameResolutionHost.fileNameToId, vue.resolveCommonLanguageId); - const tsLsHost = createLanguageServiceHost(project.typescript!.projectHost, project.fileProvider, fileNameResolutionHost, ts, ts.sys); - const tsLs = ts.createLanguageService(tsLsHost); + const project = createProject( + ts, + ts.sys, + vueLanguages, + configFileName, + projectHost, + ); + const { languageServiceHost } = project.typescript!; + const tsLs = ts.createLanguageService(languageServiceHost); decorateLanguageService(project.fileProvider, tsLs, false); if (checkerOptions.forceUseTs) { - const getScriptKind = tsLsHost.getScriptKind; - tsLsHost.getScriptKind = (fileName) => { + const getScriptKind = languageServiceHost.getScriptKind?.bind(languageServiceHost); + languageServiceHost.getScriptKind = (fileName) => { if (fileName.endsWith('.vue.js')) { return ts.ScriptKind.TS; } @@ -299,9 +304,9 @@ ${vueCompilerOptions.target < 3 ? vue2TypeHelpersCode : typeHelpersCode} // fill defaults const printer = ts.createPrinter(checkerOptions.printer); - const snapshot = host.getScriptSnapshot(componentPath)!; + const snapshot = projectHost.getScriptSnapshot(componentPath)!; - const vueSourceFile = project.fileProvider.getSourceFile(componentPath)?.root; + const vueSourceFile = project.fileProvider.getSourceFile(componentPath)?.virtualFile?.[0]; const vueDefaults = vueSourceFile && exportName === 'default' ? (vueSourceFile instanceof vue.VueFile ? readVueComponentDefaultProps(vueSourceFile, printer, ts, vueCompilerOptions) : {}) : {}; @@ -643,8 +648,8 @@ function createSchemaResolvers( if (virtualFile) { const maps = core.fileProvider.getMaps(virtualFile); for (const [source, [_, map]] of maps) { - const start = map.toSourceOffset(declaration.getStart()); - const end = map.toSourceOffset(declaration.getEnd()); + const start = map.getSourceOffset(declaration.getStart()); + const end = map.getSourceOffset(declaration.getEnd()); if (start && end) { return { file: source, diff --git a/packages/language-core/src/generators/script.ts b/packages/language-core/src/generators/script.ts index 9a0f859ea1..115cd5891b 100644 --- a/packages/language-core/src/generators/script.ts +++ b/packages/language-core/src/generators/script.ts @@ -1,4 +1,4 @@ -import { FileRangeCapabilities, MirrorBehaviorCapabilities } from '@volar/language-core'; +import { LinkedCodeTrigger } from '@volar/language-core'; import * as SourceMaps from '@volar/source-map'; import { Segment, getLength } from '@volar/source-map'; import * as muggle from 'muggle-string'; @@ -7,7 +7,7 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as templateGen from '../generators/template'; import type { ScriptRanges } from '../parsers/scriptRanges'; import type { ScriptSetupRanges } from '../parsers/scriptSetupRanges'; -import type { TextRange, VueCompilerOptions } from '../types'; +import type { TextRange, VueCodeInformation, VueCompilerOptions } from '../types'; import { Sfc } from '../types'; import { getSlotsPropertyName, hyphenateTag } from '../utils/shared'; import { walkInterpolationFragment } from '../utils/transform'; @@ -27,8 +27,8 @@ export function generate( codegenStack: boolean, ) { - const [codes, codeStacks] = codegenStack ? muggle.track([] as Segment[]) : [[], []]; - const mirrorBehaviorMappings: SourceMaps.Mapping<[MirrorBehaviorCapabilities, MirrorBehaviorCapabilities]>[] = []; + const [codes, codeStacks] = codegenStack ? muggle.track([] as Segment[]) : [[], []]; + const mirrorBehaviorMappings: SourceMaps.Mapping<[LinkedCodeTrigger, LinkedCodeTrigger]>[] = []; //#region monkey fix: https://github.com/vuejs/language-tools/pull/2113 if (!script && !scriptSetup) { @@ -155,10 +155,10 @@ export function generate( 'script', [script.srcOffset - 1, script.srcOffset + script.src.length + 1], { - ...FileRangeCapabilities.full, - rename: src === script.src ? true : { - normalize: undefined, - apply(newName) { + renameEdits: src === script.src ? true : { + shouldRename: false, + shouldEdit: true, + resolveEditText(newName) { if ( newName.endsWith('.jsx') || newName.endsWith('.js') @@ -245,7 +245,7 @@ export function generate( scriptSetup.content.substring(0, Math.max(scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.leadingCommentEndOffset)) + '\n', 'scriptSetup', 0, - FileRangeCapabilities.full, + {}, ]); } function generateExportDefaultEndMapping() { @@ -257,7 +257,7 @@ export function generate( '', 'scriptSetup', scriptSetup.content.length, - { diagnostic: true }, + { diagnostics: true }, ]); codes.push(`\n`); } @@ -279,7 +279,7 @@ export function generate( scriptSetup.generic, scriptSetup.name, scriptSetup.genericOffset, - FileRangeCapabilities.full, + {}, ]); if (!scriptSetup.generic.endsWith(',')) { codes.push(`,`); @@ -410,14 +410,12 @@ export function generate( const propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); const propMirror = definePropMirrors[propName]; if (propMirror) { - mirrorBehaviorMappings.push({ - sourceRange: [defineProp.name.start + scriptSetupGeneratedOffset, defineProp.name.end + scriptSetupGeneratedOffset], - generatedRange: propMirror, - data: [ - MirrorBehaviorCapabilities.full, - MirrorBehaviorCapabilities.full, - ], - }); + mirrorBehaviorMappings.push([ + undefined, + [defineProp.name.start + scriptSetupGeneratedOffset, defineProp.name.end + scriptSetupGeneratedOffset], + propMirror, + [{}, {}], + ]); } } } @@ -761,14 +759,12 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: const scriptEnd = getLength(codes); codes.push(',\n'); - mirrorBehaviorMappings.push({ - sourceRange: [scriptStart, scriptEnd], - generatedRange: [templateStart, templateEnd], - data: [ - MirrorBehaviorCapabilities.full, - MirrorBehaviorCapabilities.full, - ], - }); + mirrorBehaviorMappings.push([ + undefined, + [scriptStart, scriptEnd], + [templateStart, templateEnd], + [{}, {}], + ]); } } codes.push(`};\n`); // return { @@ -793,7 +789,7 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: componentsOption.start, { references: true, - rename: true, + renameEdits: true, }, ]); } @@ -907,7 +903,7 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: classRange.start, { references: true, - referencesCodeLens, + __referencesCodeLens: referencesCodeLens, }, ]); codes.push(`'`); @@ -917,9 +913,11 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: [classRange.start, classRange.end], { references: true, - rename: { - normalize: normalizeCssRename, - apply: applyCssRename, + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: normalizeCssRename, + resolveEditText: applyCssRename, }, }, ]); @@ -954,8 +952,8 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: style.name, cssBind.offset + fragOffset, onlyForErrorMapping - ? { diagnostic: true } - : FileRangeCapabilities.full, + ? { diagnostics: true } + : {}, ]); } }, @@ -999,7 +997,7 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: (vueTag === 'script' ? script : scriptSetup)!.content.substring(start, end), vueTag, start, - FileRangeCapabilities.full, // diagnostic also working for setup() returns unused in template checking + {}, // diagnostic also working for setup() returns unused in template checking ]); muggle.resetOffsetStack(); } @@ -1011,8 +1009,8 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: start, { references: true, - definition: true, - rename: true, + definitions: true, + renameEdits: true, }, ]); muggle.resetOffsetStack(); diff --git a/packages/language-core/src/generators/template.ts b/packages/language-core/src/generators/template.ts index 6a069f79a3..8df3ee1589 100644 --- a/packages/language-core/src/generators/template.ts +++ b/packages/language-core/src/generators/template.ts @@ -1,18 +1,39 @@ -import { FileRangeCapabilities } from '@volar/language-core'; import { Segment } from '@volar/source-map'; import * as CompilerDOM from '@vue/compiler-dom'; import { camelize, capitalize } from '@vue/shared'; import { minimatch } from 'minimatch'; import * as muggle from 'muggle-string'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import { Sfc, VueCompilerOptions } from '../types'; +import { Sfc, VueCodeInformation, VueCompilerOptions } from '../types'; import { hyphenateAttr, hyphenateTag } from '../utils/shared'; import { collectVars, walkInterpolationFragment } from '../utils/transform'; const capabilitiesPresets = { - all: FileRangeCapabilities.full, + disabledAll: { + diagnostics: false, + renameEdits: false, + formattingEdits: false, + completionItems: false, + definitions: false, + references: false, + foldingRanges: false, + inlayHints: false, + codeActions: false, + symbols: false, + selectionRanges: false, + linkedEditingRanges: false, + colors: false, + autoInserts: false, + codeLenses: false, + highlights: false, + links: false, + semanticTokens: false, + hover: false, + signatureHelps: false, + } satisfies VueCodeInformation, + all: {} satisfies VueCodeInformation, allWithHiddenParam: { - ...FileRangeCapabilities.full, __hint: { + __hint: { setting: 'vue.inlayHints.inlineHandlerLeading', label: '$event =>', tooltip: [ @@ -21,20 +42,20 @@ const capabilitiesPresets = { '[More info](https://github.com/vuejs/language-tools/issues/2445#issuecomment-1444771420)', ].join('\n\n'), paddingRight: true, - } /* TODO */ - } as FileRangeCapabilities, - noDiagnostic: { ...FileRangeCapabilities.full, diagnostic: false } satisfies FileRangeCapabilities, - diagnosticOnly: { diagnostic: true } satisfies FileRangeCapabilities, - tagHover: { hover: true } satisfies FileRangeCapabilities, - event: { hover: true, diagnostic: true } satisfies FileRangeCapabilities, - tagReference: { references: true, definition: true, rename: { normalize: undefined, apply: noEditApply } } satisfies FileRangeCapabilities, - attr: { hover: true, diagnostic: true, references: true, definition: true, rename: true } satisfies FileRangeCapabilities, - attrReference: { references: true, definition: true, rename: true } satisfies FileRangeCapabilities, - slotProp: { references: true, definition: true, rename: true, diagnostic: true } satisfies FileRangeCapabilities, - scopedClassName: { references: true, definition: true, rename: true, completion: true } satisfies FileRangeCapabilities, - slotName: { hover: true, diagnostic: true, references: true, definition: true, completion: true } satisfies FileRangeCapabilities, - slotNameExport: { hover: true, diagnostic: true, references: true, definition: true, /* referencesCodeLens: true */ } satisfies FileRangeCapabilities, - refAttr: { references: true, definition: true, rename: true } satisfies FileRangeCapabilities, + } + } as VueCodeInformation, + noDiagnostics: { diagnostics: false } satisfies VueCodeInformation, + diagnosticOnly: { diagnostics: true } satisfies VueCodeInformation, + tagHover: { hover: true } satisfies VueCodeInformation, + event: { hover: true, diagnostics: true } satisfies VueCodeInformation, + tagReference: { references: true, definitions: true, renameEdits: { shouldRename: false, shouldEdit: true } } satisfies VueCodeInformation, + attr: { hover: true, diagnostics: true, references: true, definitions: true, renameEdits: true } satisfies VueCodeInformation, + attrReference: { references: true, definitions: true, renameEdits: true } satisfies VueCodeInformation, + slotProp: { references: true, definitions: true, renameEdits: true, diagnostics: true } satisfies VueCodeInformation, + scopedClassName: { references: true, definitions: true, renameEdits: true, completionItems: true } satisfies VueCodeInformation, + slotName: { hover: true, diagnostics: true, references: true, definitions: true, completionItems: true } satisfies VueCodeInformation, + slotNameExport: { hover: true, diagnostics: true, references: true, definitions: true, /* __referencesCodeLens: true */ } satisfies VueCodeInformation, + refAttr: { references: true, definitions: true, renameEdits: true } satisfies VueCodeInformation, }; const formatBrackets = { normal: ['`${', '}`;'] as [string, string], @@ -63,7 +84,7 @@ const transformContext: CompilerDOM.TransformContext = { expressionPlugins: ['typescript'], }; -type Code = Segment; +type Code = Segment; export function generate( ts: typeof import('typescript/lib/tsserverlibrary'), @@ -153,7 +174,7 @@ export function generate( slot.loc, { ...capabilitiesPresets.slotNameExport, - referencesCodeLens: true, + __referencesCodeLens: true, }, ], slot.nodeLoc), ); @@ -173,8 +194,8 @@ export function generate( offset, { ...capabilitiesPresets.scopedClassName, - displayWithLink: stylesScopedClasses.has(className), - }, + __displayWithLink: stylesScopedClasses.has(className), + } satisfies VueCodeInformation, ])); codes.push(`];\n`); } @@ -235,9 +256,11 @@ export function generate( tagRange, { ...capabilitiesPresets.tagReference, - rename: { - normalize: tagName === name ? capabilitiesPresets.tagReference.rename.normalize : camelizeComponentName, - apply: getTagRenameApply(tagName), + renameEdits: { + shouldEdit: true, + shouldRename: true, + resolveNewName: tagName !== name ? camelizeComponentName : undefined, + resolveEditText: getTagRenameApply(tagName), }, ...nativeTags.has(tagName) ? { ...capabilitiesPresets.tagHover, @@ -269,9 +292,9 @@ export function generate( 'template', tagRange, { - completion: { - additional: true, - autoImportOnly: true, + completionItems: { + isAdditional: true, + onlyImport: true, }, }, ]); @@ -331,10 +354,10 @@ export function generate( continue; } const cap = code[3]; - if (cap.diagnostic) { + if (cap.diagnostics) { code[3] = { ...cap, - diagnostic: false, + diagnostics: false, }; } } @@ -352,10 +375,10 @@ export function generate( continue; } const cap = code[3]; - if (cap.diagnostic) { + if (cap.diagnostics) { code[3] = { ...cap, - diagnostic: { + diagnostics: { shouldReport: suppressError, }, }; @@ -367,7 +390,7 @@ export function generate( 'template', [expectedErrorNode.loc.start.offset, expectedErrorNode.loc.end.offset], { - diagnostic: { + diagnostics: { shouldReport: () => errors === 0, }, }, @@ -907,7 +930,7 @@ export function generate( 'default', 'template', [slotDir.loc.start.offset, slotDir.loc.start.offset + (slotDir.loc.source.startsWith('#') ? '#'.length : slotDir.loc.source.startsWith('v-slot:') ? 'v-slot:'.length : 0)], - { ...capabilitiesPresets.slotName, completion: false }, + { ...capabilitiesPresets.slotName, completionItems: false }, ]) ), ['', 'template', (slotDir.arg ?? slotDir).loc.end.offset, capabilitiesPresets.diagnosticOnly], @@ -943,7 +966,7 @@ export function generate( '', 'template', slotDir.loc.start.offset + (slotDir.loc.source.startsWith('#') ? '#'.length : slotDir.loc.source.startsWith('v-slot:') ? 'v-slot:'.length : 0), - { completion: true }, + { completionItems: true }, ], `'/* empty slot name completion */]\n`, ); @@ -998,13 +1021,15 @@ export function generate( [prop.arg.loc.start.offset, prop.arg.loc.end.offset], { ...capabilitiesPresets.attrReference, - rename: { + renameEdits: { + shouldRename: true, + shouldEdit: true, // @click-outside -> onClickOutside - normalize(newName) { + resolveNewName(newName) { return camelize('on-' + newName); }, // onClickOutside -> @click-outside - apply(newName) { + resolveEditText(newName) { const hName = hyphenateAttr(newName); if (hyphenateAttr(newName).startsWith('on-')) { return camelize(hName.slice('on-'.length)); @@ -1170,22 +1195,22 @@ export function generate( const codes: Code[] = []; - let caps_all: FileRangeCapabilities = capabilitiesPresets.all; - let caps_diagnosticOnly: FileRangeCapabilities = capabilitiesPresets.diagnosticOnly; - let caps_attr: FileRangeCapabilities = capabilitiesPresets.attr; + let caps_all: VueCodeInformation = capabilitiesPresets.all; + let caps_diagnosticOnly: VueCodeInformation = capabilitiesPresets.diagnosticOnly; + let caps_attr: VueCodeInformation = capabilitiesPresets.attr; if (mode === 'extraReferences') { caps_all = { references: caps_all.references, - rename: caps_all.rename, + renameEdits: caps_all.renameEdits, }; caps_diagnosticOnly = { references: caps_diagnosticOnly.references, - rename: caps_diagnosticOnly.rename, + renameEdits: caps_diagnosticOnly.renameEdits, }; caps_attr = { references: caps_attr.references, - rename: caps_attr.rename, + renameEdits: caps_attr.renameEdits, }; } @@ -1272,9 +1297,11 @@ export function generate( [prop.arg.loc.start.offset, prop.arg.loc.start.offset + attrNameText.length], // patch style attr, { ...caps_attr, - rename: { - normalize: camelize, - apply: camelized ? hyphenateAttr : noEditApply, + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: camelized ? hyphenateAttr : undefined, }, }, ], (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {})), @@ -1288,9 +1315,11 @@ export function generate( [prop.arg.loc.start.offset, prop.arg.loc.end.offset], { ...caps_attr, - rename: { - normalize: camelize, - apply: camelized ? hyphenateAttr : noEditApply, + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: camelized ? hyphenateAttr : undefined, }, }, ], (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {})), @@ -1367,9 +1396,11 @@ export function generate( [prop.loc.start.offset, prop.loc.start.offset + prop.name.length], { ...caps_attr, - rename: { - normalize: camelize, - apply: camelized ? hyphenateAttr : noEditApply, + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: camelized ? hyphenateAttr : undefined, }, }, ], (prop.loc as any).name_1 ?? ((prop.loc as any).name_1 = {})) @@ -1472,7 +1503,11 @@ export function generate( content, 'template', prop.arg.loc.start.offset + start, - capabilitiesPresets.all, + { + formattingEdits: false, + foldingRanges: false, + symbols: false, + }, ]); cssCodes.push(` }\n`); } @@ -1526,14 +1561,16 @@ export function generate( 'template', [prop.loc.start.offset, prop.loc.start.offset + 'v-'.length + prop.name.length], { - ...capabilitiesPresets.noDiagnostic, - completion: { + ...capabilitiesPresets.noDiagnostics, + completionItems: { // fix https://github.com/vuejs/language-tools/issues/1905 - additional: true, + isAdditional: true, }, - rename: { - normalize: camelize, - apply: getPropRenameApply(prop.name), + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: getPropRenameApply(prop.name), }, }, ], @@ -1694,9 +1731,11 @@ export function generate( [prop.arg.loc.start.offset, prop.arg.loc.end.offset], { ...capabilitiesPresets.slotProp, - rename: { - normalize: camelize, - apply: getPropRenameApply(prop.arg.content), + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: getPropRenameApply(prop.arg.content), }, }, ], prop.arg.loc), @@ -1723,9 +1762,11 @@ export function generate( prop.loc.start.offset, { ...capabilitiesPresets.attr, - rename: { - normalize: camelize, - apply: getPropRenameApply(prop.name), + renameEdits: { + shouldRename: true, + shouldEdit: true, + resolveNewName: camelize, + resolveEditText: getPropRenameApply(prop.name), }, }, ], prop.loc), @@ -1806,7 +1847,15 @@ export function generate( codes.push('['); for (const _vars of tempVars) { for (const v of _vars) { - codes.push([v.text, 'template', v.offset, { completion: { additional: true } }]); + codes.push([ + v.text, + 'template', + v.offset, + { + ...capabilitiesPresets, + completionItems: { isAdditional: true }, + }, + ]); codes.push(','); } } @@ -1819,7 +1868,16 @@ export function generate( function createFormatCode(mapCode: string, sourceOffset: number, formatWrapper: [string, string]): Code[] { return [ formatWrapper[0], - [mapCode, 'template', sourceOffset, { completion: true /* fix vue-autoinsert-parentheses not working */ }], + [ + mapCode, + 'template', + sourceOffset, + { + ...capabilitiesPresets, + formattingEdits: true, + linkedEditingRanges: true, // support vue-autoinsert-parentheses + }, + ], formatWrapper[1], '\n', ]; @@ -1851,7 +1909,7 @@ export function generate( _code: string, astHolder: any, start: number | undefined, - data: FileRangeCapabilities | (() => FileRangeCapabilities) | undefined, + data: VueCodeInformation | (() => VueCodeInformation) | undefined, prefix: string, suffix: string, ): Code[] { @@ -2007,15 +2065,11 @@ function camelizeComponentName(newName: string) { } function getTagRenameApply(oldName: string) { - return oldName === hyphenateTag(oldName) ? hyphenateTag : noEditApply; + return oldName === hyphenateTag(oldName) ? hyphenateTag : undefined; } function getPropRenameApply(oldName: string) { - return oldName === hyphenateAttr(oldName) ? hyphenateAttr : noEditApply; -} - -function noEditApply(n: string) { - return n; + return oldName === hyphenateAttr(oldName) ? hyphenateAttr : undefined; } function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number, vueCompilerOptions: VueCompilerOptions) { diff --git a/packages/language-core/src/languageModule.ts b/packages/language-core/src/languageModule.ts index 283a098eaa..5e34a58962 100644 --- a/packages/language-core/src/languageModule.ts +++ b/packages/language-core/src/languageModule.ts @@ -84,17 +84,22 @@ export function createVueLanguage( sourceFile.update(snapshot); }, typescript: { - resolveProjectHost(host) { + resolveSourceFileName(tsFileName) { + const baseName = path.basename(tsFileName); + if (baseName.indexOf('.vue.')) { // .vue.ts .vue.d.ts .vue.js .vue.jsx .vue.tsx + return tsFileName.substring(0, tsFileName.lastIndexOf('.vue.') + '.vue'.length); + } + }, + resolveModuleName(moduleName, impliedNodeFormat) { + if (impliedNodeFormat === 99 satisfies ts.ModuleKind.ESNext && vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) { + return `${moduleName}.js`; + } + }, + resolveLanguageServiceHost(host) { const sharedTypesSnapshot = ts.ScriptSnapshot.fromString(sharedTypes.getTypesCode(vueCompilerOptions)); const sharedTypesFileName = path.join(host.getCurrentDirectory(), sharedTypes.baseName); return { ...host, - resolveModuleName(moduleName, impliedNodeFormat) { - if (impliedNodeFormat === ts.ModuleKind.ESNext && vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) { - return `${moduleName}.js`; - } - return host.resolveModuleName?.(moduleName, impliedNodeFormat) ?? moduleName; - }, getScriptFileNames() { return [ sharedTypesFileName, diff --git a/packages/language-core/src/plugins/file-md.ts b/packages/language-core/src/plugins/file-md.ts index b4d37b339b..e9883a29f8 100644 --- a/packages/language-core/src/plugins/file-md.ts +++ b/packages/language-core/src/plugins/file-md.ts @@ -74,8 +74,8 @@ const plugin: VueLanguagePlugin = () => { return sfc; function transformRange(block: SFCBlock) { - block.loc.start.offset = file2VueSourceMap.toSourceOffset(block.loc.start.offset)?.[0] ?? -1; - block.loc.end.offset = file2VueSourceMap.toSourceOffset(block.loc.end.offset)?.[0] ?? -1; + block.loc.start.offset = file2VueSourceMap.getSourceOffset(block.loc.start.offset)?.[0] ?? -1; + block.loc.end.offset = file2VueSourceMap.getSourceOffset(block.loc.end.offset)?.[0] ?? -1; } }; } diff --git a/packages/language-core/src/plugins/vue-sfc-customblocks.ts b/packages/language-core/src/plugins/vue-sfc-customblocks.ts index b2bdf2e79e..835e877416 100644 --- a/packages/language-core/src/plugins/vue-sfc-customblocks.ts +++ b/packages/language-core/src/plugins/vue-sfc-customblocks.ts @@ -1,4 +1,3 @@ -import { FileCapabilities, FileRangeCapabilities } from '@volar/language-core'; import { VueLanguagePlugin } from '../types'; const customBlockReg = /^(.*)\.customBlock_([^_]+)_(\d+)\.([^.]+)$/; @@ -24,12 +23,11 @@ const plugin: VueLanguagePlugin = () => { const index = parseInt(match[3]); const customBlock = sfc.customBlocks[index]; - embeddedFile.capabilities = FileCapabilities.full; embeddedFile.content.push([ customBlock.content, customBlock.name, 0, - FileRangeCapabilities.full, + {}, ]); } }, diff --git a/packages/language-core/src/plugins/vue-sfc-scripts.ts b/packages/language-core/src/plugins/vue-sfc-scripts.ts index e50695bf7c..af566e5a5a 100644 --- a/packages/language-core/src/plugins/vue-sfc-scripts.ts +++ b/packages/language-core/src/plugins/vue-sfc-scripts.ts @@ -1,4 +1,3 @@ -import { FileCapabilities, FileKind } from '@volar/language-core'; import { VueLanguagePlugin } from '../types'; const scriptFormatReg = /^(.*)\.script_format\.([^.]+)$/; @@ -26,19 +25,19 @@ const plugin: VueLanguagePlugin = () => { const scriptSetupMatch = embeddedFile.fileName.match(scriptSetupFormatReg); const script = scriptMatch ? sfc.script : scriptSetupMatch ? sfc.scriptSetup : undefined; if (script) { - embeddedFile.kind = FileKind.TextFile; - embeddedFile.capabilities = { - ...FileCapabilities.full, - diagnostic: false, - codeAction: false, - inlayHint: false, - }; embeddedFile.content.push([ script.content, script.name, 0, {}, ]); + embeddedFile.content.forEach(code => { + if (typeof code !== 'string') { + code[3].diagnostics = false; + code[3].codeActions = false; + code[3].inlayHints = false; + } + }); } }, }; diff --git a/packages/language-core/src/plugins/vue-sfc-styles.ts b/packages/language-core/src/plugins/vue-sfc-styles.ts index b750349a1d..9aacbd28aa 100644 --- a/packages/language-core/src/plugins/vue-sfc-styles.ts +++ b/packages/language-core/src/plugins/vue-sfc-styles.ts @@ -1,4 +1,3 @@ -import { FileCapabilities, FileRangeCapabilities } from '@volar/language-core'; import { VueLanguagePlugin } from '../types'; const styleReg = /^(.*)\.style_(\d+)\.([^.]+)$/; @@ -24,12 +23,11 @@ const plugin: VueLanguagePlugin = () => { const index = parseInt(match[2]); const style = sfc.styles[index]; - embeddedFile.capabilities = FileCapabilities.full; embeddedFile.content.push([ style.content, style.name, 0, - FileRangeCapabilities.full, + {}, ]); } }, diff --git a/packages/language-core/src/plugins/vue-sfc-template.ts b/packages/language-core/src/plugins/vue-sfc-template.ts index b21e8e8cd4..439d4d565e 100644 --- a/packages/language-core/src/plugins/vue-sfc-template.ts +++ b/packages/language-core/src/plugins/vue-sfc-template.ts @@ -1,4 +1,3 @@ -import { FileCapabilities, FileRangeCapabilities } from '@volar/language-core'; import { VueLanguagePlugin } from '../types'; const templateReg = /^(.*)\.template\.([^.]+)$/; @@ -19,12 +18,11 @@ const plugin: VueLanguagePlugin = () => { resolveEmbeddedFile(_fileName, sfc, embeddedFile) { const match = embeddedFile.fileName.match(templateReg); if (match && sfc.template) { - embeddedFile.capabilities = FileCapabilities.full; embeddedFile.content.push([ sfc.template.content, sfc.template.name, 0, - FileRangeCapabilities.full, + {}, ]); } }, diff --git a/packages/language-core/src/plugins/vue-tsx.ts b/packages/language-core/src/plugins/vue-tsx.ts index b3cb5e826b..092658444a 100644 --- a/packages/language-core/src/plugins/vue-tsx.ts +++ b/packages/language-core/src/plugins/vue-tsx.ts @@ -1,11 +1,11 @@ +import type { CodeInformation } from '@volar/language-core'; import { computed, computedSet } from 'computeds'; +import * as muggle from 'muggle-string'; import { generate as generateScript } from '../generators/script'; import { generate as generateTemplate } from '../generators/template'; import { parseScriptRanges } from '../parsers/scriptRanges'; import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges'; import { Sfc, VueLanguagePlugin } from '../types'; -import { FileCapabilities, FileKind } from '@volar/language-core'; -import * as muggle from 'muggle-string'; const templateFormatReg = /^\.template_format\.ts$/; const templateStyleCssReg = /^\.template_style\.css$/; @@ -43,35 +43,34 @@ const plugin: VueLanguagePlugin = (ctx) => { resolveEmbeddedFile(fileName, sfc, embeddedFile) { const _tsx = useTsx(fileName, sfc); + const lang = _tsx.lang(); const suffix = embeddedFile.fileName.replace(fileName, ''); - if (suffix === '.' + _tsx.lang()) { - embeddedFile.kind = FileKind.TypeScriptHostFile; - embeddedFile.capabilities = { - ...FileCapabilities.full, - foldingRange: false, - documentFormatting: false, - documentSymbol: false, + if (suffix === '.' + lang) { + embeddedFile.typescript = { + scriptKind: lang === 'js' ? ctx.modules.typescript.ScriptKind.JS + : lang === 'jsx' ? ctx.modules.typescript.ScriptKind.JSX + : lang === 'tsx' ? ctx.modules.typescript.ScriptKind.TSX + : ctx.modules.typescript.ScriptKind.TS }; const tsx = _tsx.generatedScript(); if (tsx) { const [content, contentStacks] = ctx.codegenStack ? muggle.track([...tsx.codes], [...tsx.codeStacks]) : [[...tsx.codes], [...tsx.codeStacks]]; + content.forEach(code => { + if (typeof code !== 'string') { + code[3].foldingRanges = false; + code[3].formattingEdits = false; + code[3].symbols = false; + } + }); embeddedFile.content = content; embeddedFile.contentStacks = contentStacks; - embeddedFile.mirrorBehaviorMappings = [...tsx.mirrorBehaviorMappings]; + embeddedFile.linkedCodeMappings = [...tsx.mirrorBehaviorMappings]; } } else if (suffix.match(templateFormatReg)) { embeddedFile.parentFileName = fileName + '.template.' + sfc.template?.lang; - embeddedFile.kind = FileKind.TextFile; - embeddedFile.capabilities = { - ...FileCapabilities.full, - diagnostic: false, - foldingRange: false, - codeAction: false, - inlayHint: false, - }; const template = _tsx.generatedTemplate(); if (template) { @@ -105,12 +104,9 @@ const plugin: VueLanguagePlugin = (ctx) => { const [content, contentStacks] = ctx.codegenStack ? muggle.track([...template.cssCodes], [...template.cssCodeStacks]) : [[...template.cssCodes], [...template.cssCodeStacks]]; - embeddedFile.content = content; + embeddedFile.content = content as muggle.Segment[]; embeddedFile.contentStacks = contentStacks; } - - // for color pickers support - embeddedFile.capabilities.documentSymbol = true; } }, }; diff --git a/packages/language-core/src/types.ts b/packages/language-core/src/types.ts index 4096cadafd..e88c6e048b 100644 --- a/packages/language-core/src/types.ts +++ b/packages/language-core/src/types.ts @@ -2,6 +2,7 @@ import type * as CompilerDOM from '@vue/compiler-dom'; import type { SFCParseResult } from '@vue/compiler-sfc'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { VueEmbeddedFile } from './virtualFile/embeddedFile'; +import type { CodeInformation } from '@volar/language-core'; export type { SFCParseResult } from '@vue/compiler-sfc'; @@ -10,6 +11,18 @@ export type RawVueCompilerOptions = Partial { - const str: Segment[] = [[snapshot().getText(0, snapshot().getLength()), undefined, 0, FileRangeCapabilities.full]]; + const str: Segment[] = [[snapshot().getText(0, snapshot().getLength()), undefined, 0, {}]]; for (const block of [ sfc.script, sfc.scriptSetup, @@ -30,15 +29,16 @@ export function computedMappings( ); } } - return str.map>((m) => { + return str.map>((m) => { const text = m[0]; const start = m[2] as number; const end = start + text.length; - return { - sourceRange: [start, end], - generatedRange: [start, end], - data: m[3] as FileRangeCapabilities, - }; + return [ + undefined, + [start, end], + [start, end], + m[3] as VueCodeInformation, + ]; }); }); } diff --git a/packages/language-core/src/virtualFile/embeddedFile.ts b/packages/language-core/src/virtualFile/embeddedFile.ts index 001ec2e871..0520b35562 100644 --- a/packages/language-core/src/virtualFile/embeddedFile.ts +++ b/packages/language-core/src/virtualFile/embeddedFile.ts @@ -1,16 +1,15 @@ -import { FileCapabilities, FileKind, FileRangeCapabilities, MirrorBehaviorCapabilities } from '@volar/language-core'; +import { CodeInformation, LinkedCodeTrigger, VirtualFile } from '@volar/language-core'; import { Mapping, Segment, StackNode } from '@volar/source-map'; export class VueEmbeddedFile { public parentFileName?: string; - public kind = FileKind.TextFile; - public capabilities: FileCapabilities = {}; - public mirrorBehaviorMappings: Mapping<[MirrorBehaviorCapabilities, MirrorBehaviorCapabilities]>[] = []; + public typescript: VirtualFile['typescript']; + public linkedCodeMappings: Mapping<[LinkedCodeTrigger, LinkedCodeTrigger]>[] = []; constructor( public fileName: string, - public content: Segment[], + public content: Segment[], public contentStacks: StackNode[], ) { } } diff --git a/packages/language-core/src/virtualFile/vueFile.ts b/packages/language-core/src/virtualFile/vueFile.ts index 35e65e59e4..79546b53ef 100644 --- a/packages/language-core/src/virtualFile/vueFile.ts +++ b/packages/language-core/src/virtualFile/vueFile.ts @@ -1,4 +1,4 @@ -import { FileCapabilities, FileKind, VirtualFile, forEachEmbeddedFile } from '@volar/language-core'; +import { VirtualFile, forEachEmbeddedFile } from '@volar/language-core'; import { Stack } from '@volar/source-map'; import type * as ts from 'typescript/lib/tsserverlibrary'; import { VueCompilerOptions, VueLanguagePlugin } from '../types'; @@ -25,20 +25,16 @@ export class VueFile implements VirtualFile { // others - capabilities = FileCapabilities.full; - kind = FileKind.TextFile; codegenStacks: Stack[] = []; get embeddedFiles() { return this.getEmbeddedFiles(); } get mainTsFile() { - let result: VirtualFile | undefined; - forEachEmbeddedFile(this, file => { - if (file.kind === FileKind.TypeScriptHostFile && file.id.substring(this.id.length).match(jsxReg)) { - result = file; + for (const file of forEachEmbeddedFile(this)) { + if (file.typescript && file.id.substring(this.id.length).match(jsxReg)) { + return file; } - }); - return result; + } } get snapshot() { return this._snapshot(); diff --git a/packages/language-plugin-pug/src/index.ts b/packages/language-plugin-pug/src/index.ts index d6e078a922..a96af4647c 100644 --- a/packages/language-plugin-pug/src/index.ts +++ b/packages/language-plugin-pug/src/index.ts @@ -38,7 +38,7 @@ const plugin: VueLanguagePlugin = ({ modules }) => { get(target, prop) { if (prop === 'offset') { const htmlOffset = target.offset; - for (const mapped of map.toSourceOffsets(htmlOffset)) { + for (const mapped of map.getSourceOffsets(htmlOffset)) { return mapped[0]; } return -1; diff --git a/packages/language-server/src/languageServerPlugin.ts b/packages/language-server/src/languageServerPlugin.ts index 27968b3d41..50e1118444 100644 --- a/packages/language-server/src/languageServerPlugin.ts +++ b/packages/language-server/src/languageServerPlugin.ts @@ -32,7 +32,7 @@ export function createServerPlugin(connection: Connection) { return { extraFileExtensions: vueFileExtensions.map(ext => ({ extension: ext, isMixedContent: true, scriptKind: ts.ScriptKind.Deferred })), watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions], - async resolveConfig(config, env, projectHost) { + async resolveConfig(config, env, info) { const vueOptions = await getVueCompilerOptions(); @@ -40,7 +40,7 @@ export function createServerPlugin(connection: Connection) { envToVueOptions.set(env, vue.resolveVueCompilerOptions(vueOptions)); } - config.languages = vue.resolveLanguages(ts, config.languages ?? {}, projectHost?.getCompilationSettings() ?? {}, vueOptions, options.codegenStack); + config.languages = vue.resolveLanguages(ts, config.languages ?? {}, info?.parsedCommandLine.options ?? {}, vueOptions, options.codegenStack); config.services = vue.resolveServices(config.services ?? {}, vueOptions); return config; @@ -49,18 +49,18 @@ export function createServerPlugin(connection: Connection) { let vueOptions: Partial = {}; - if (env && projectHost) { + if (env && info) { const sys = createSys(ts, env, env.uriToFileName(env.workspaceFolder.uri.toString())); let sysVersion: number | undefined; let newSysVersion = await sys.sync(); while (sysVersion !== newSysVersion) { sysVersion = newSysVersion; - if (projectHost.configFileName) { - vueOptions = vue2.createParsedCommandLine(ts, sys, projectHost.configFileName).vueOptions; + if (info.configFileName) { + vueOptions = vue2.createParsedCommandLine(ts, sys, info.configFileName).vueOptions; } else { - vueOptions = vue2.createParsedCommandLineByJson(ts, sys, projectHost.getCurrentDirectory(), projectHost.getCompilationSettings()).vueOptions; + vueOptions = vue2.createParsedCommandLineByJson(ts, sys, env.uriToFileName(env.workspaceFolder.uri.toString()), info.parsedCommandLine.options).vueOptions; } newSysVersion = await sys.sync(); } @@ -120,17 +120,18 @@ export function createServerPlugin(connection: Connection) { const langaugeService = project.getLanguageService(); let checker = checkers.get(project); - if (!checker) { - checker = componentMeta.baseCreate( - ts, - langaugeService.context.project.typescript!.projectHost, - envToVueOptions.get(langaugeService.context.env)!, - {}, - langaugeService.context.project.typescript!.projectHost.getCurrentDirectory() + '/tsconfig.json.global.vue', - ); - checkers.set(project, checker); - } - return checker.getComponentMeta(langaugeService.context.env.uriToFileName(params.uri)); + // if (!checker) { + // checker = componentMeta.baseCreate( + // ts, + // langaugeService.context.project.typescript?.configFileName, + // langaugeService.context.project.typescript!.projectHost, + // envToVueOptions.get(langaugeService.context.env)!, + // {}, + // langaugeService.context.project.typescript!.projectHost.getCurrentDirectory() + '/tsconfig.json.global.vue', + // ); + // checkers.set(project, checker); + // } + return checker?.getComponentMeta(langaugeService.context.env.uriToFileName(params.uri)); }); async function getService(uri: string) { diff --git a/packages/language-service/src/ideFeatures/dragImport.ts b/packages/language-service/src/ideFeatures/dragImport.ts index 41ddc6203e..193178e5ae 100644 --- a/packages/language-service/src/ideFeatures/dragImport.ts +++ b/packages/language-service/src/ideFeatures/dragImport.ts @@ -18,12 +18,16 @@ export function getDragImportEdits( additionalEdits: vscode.TextEdit[]; } | undefined { + const sourceFile = ctx.project.fileProvider.getSourceFile(uri); + if (!sourceFile) + return; + let baseName = importUri.substring(importUri.lastIndexOf('/') + 1); baseName = baseName.substring(0, baseName.lastIndexOf('.')); const newName = capitalize(camelize(baseName)); - const document = ctx!.getTextDocument(uri)!; - const [vueFile] = ctx!.project.fileProvider.getVirtualFile(document.uri) as [VueFile, any]; + const document = ctx.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); + const [vueFile] = ctx.project.fileProvider.getVirtualFile(document.uri) as [VueFile, any]; const { sfc } = vueFile; const script = sfc.scriptSetup ?? sfc.script; diff --git a/packages/language-service/src/ideFeatures/nameCasing.ts b/packages/language-service/src/ideFeatures/nameCasing.ts index b49f91ca0a..af5273b425 100644 --- a/packages/language-service/src/ideFeatures/nameCasing.ts +++ b/packages/language-service/src/ideFeatures/nameCasing.ts @@ -13,7 +13,7 @@ export async function convertTagName( vueCompilerOptions: VueCompilerOptions, ) { - const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.virtualFile?.[0]; if (!(rootFile instanceof VueFile)) return; @@ -23,7 +23,7 @@ export async function convertTagName( const languageService = context.inject('typescript/languageService'); const template = desc.template; - const document = context.documents.getDocumentByUri(rootFile.id, rootFile.languageId, rootFile.snapshot); + const document = context.documents.get(rootFile.id, rootFile.languageId, rootFile.snapshot); const edits: vscode.TextEdit[] = []; const components = getComponentNames(ts, languageService, context.env, rootFile, vueCompilerOptions); const tags = getTemplateTagsAndAttrs(rootFile); @@ -56,7 +56,7 @@ export async function convertAttrName( vueCompilerOptions: VueCompilerOptions, ) { - const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.virtualFile?.[0]; if (!(rootFile instanceof VueFile)) return; @@ -66,7 +66,7 @@ export async function convertAttrName( const languageService = context.inject('typescript/languageService'); const template = desc.template; - const document = context.documents.getDocumentByUri(rootFile.id, rootFile.languageId, rootFile.snapshot); + const document = context.documents.get(rootFile.id, rootFile.languageId, rootFile.snapshot); const edits: vscode.TextEdit[] = []; const components = getComponentNames(ts, languageService, context.env, rootFile, vueCompilerOptions); const tags = getTemplateTagsAndAttrs(rootFile); @@ -128,7 +128,7 @@ export function detect( attr: AttrNameCasing[], } { - const rootFile = context.project.fileProvider.getSourceFile(uri)?.root; + const rootFile = context.project.fileProvider.getSourceFile(uri)?.virtualFile?.[0]; if (!(rootFile instanceof VueFile)) { return { tag: [], diff --git a/packages/language-service/src/languageService.ts b/packages/language-service/src/languageService.ts index 7f70b65e3a..c8a6f21565 100644 --- a/packages/language-service/src/languageService.ts +++ b/packages/language-service/src/languageService.ts @@ -93,13 +93,13 @@ export function resolveServices( if (virtualFile && sourceFile) { - for (const map of ctx.documents.getMapsByVirtualFile(virtualFile)) { + for (const map of ctx.documents.getMaps(virtualFile)) { - const sourceVirtualFile = ctx.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + const sourceVirtualFile = ctx.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; if (sourceVirtualFile instanceof VueFile) { - const isAutoImport = !!map.toSourcePosition(position, data => typeof data.completion === 'object' && !!data.completion.autoImportOnly); + const isAutoImport = !!map.toSourcePosition(position, data => typeof data.completionItems === 'object' && !!data.completionItems.onlyImport); if (isAutoImport) { for (const item of result.items) { @@ -183,7 +183,7 @@ export function resolveServices( const componentName = newName ?? item.textEdit.newText; const optionEdit = ExtractComponentService.createAddComponentToOptionEdit(ts, ast, componentName); if (optionEdit) { - const textDoc = ctx.documents.getDocumentByUri(virtualFile.id, virtualFile.languageId, virtualFile.snapshot); + const textDoc = ctx.documents.get(virtualFile.id, virtualFile.languageId, virtualFile.snapshot); item.additionalTextEdits.push({ range: { start: textDoc.positionAt(optionEdit.range.start), diff --git a/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts b/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts index f6e134a86f..ad87f690e5 100644 --- a/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts +++ b/packages/language-service/src/plugins/vue-autoinsert-parentheses.ts @@ -1,4 +1,4 @@ -import { Service } from '@volar/language-service'; +import { MappingKey, Service } from '@volar/language-service'; import { isCharacterTyping } from './vue-autoinsert-dotvalue'; const plugin: Service = (context, modules) => { @@ -31,8 +31,8 @@ const plugin: Service = (context, modules) => { const offset = document.offsetAt(position); for (const mappedRange of virtualFile.mappings) { - if (mappedRange.generatedRange[1] === offset) { - const text = document.getText().substring(mappedRange.generatedRange[0], mappedRange.generatedRange[1]); + if (mappedRange[MappingKey.GENERATED_CODE_RANGE][1] === offset) { + const text = document.getText().substring(mappedRange[MappingKey.GENERATED_CODE_RANGE][0], mappedRange[MappingKey.GENERATED_CODE_RANGE][1]); const ast = ts.createSourceFile(context.env.uriToFileName(virtualFile.id), text, ts.ScriptTarget.Latest); if (ast.statements.length === 1) { const statement = ast.statements[0]; @@ -63,8 +63,8 @@ const plugin: Service = (context, modules) => { .replaceAll('}', '\\}'); return { range: { - start: document.positionAt(mappedRange.generatedRange[0]), - end: document.positionAt(mappedRange.generatedRange[1]), + start: document.positionAt(mappedRange[MappingKey.GENERATED_CODE_RANGE][0]), + end: document.positionAt(mappedRange[MappingKey.GENERATED_CODE_RANGE][1]), }, newText: '(' + escapedText + '$0' + ')', }; diff --git a/packages/language-service/src/plugins/vue-codelens-references.ts b/packages/language-service/src/plugins/vue-codelens-references.ts index bded680896..6d15e91e62 100644 --- a/packages/language-service/src/plugins/vue-codelens-references.ts +++ b/packages/language-service/src/plugins/vue-codelens-references.ts @@ -1,5 +1,5 @@ import { Service } from '@volar/language-service'; -import { SourceFile, VueFile } from '@vue/language-core'; +import { MappingKey, SourceFile, VueCodeInformation, VueFile } from '@vue/language-core'; import type * as vscode from 'vscode-languageserver-protocol'; export const create = function (): Service { @@ -13,19 +13,19 @@ export const create = function (): Service { provideReferencesCodeLensRanges(document) { - return worker(document.uri, async (_, sourceFile) => { + return worker(document.uri, async (vueFile) => { const result: vscode.Range[] = []; - for (const [_, map] of context.documents.getMapsBySourceFile(sourceFile) ?? []) { - for (const mapping of map.map.mappings) { + for (const map of context.documents.getMaps(vueFile) ?? []) { + for (const mapping of map.map.codeMappings) { - if (!mapping.data.referencesCodeLens) + if (!(mapping[MappingKey.DATA] as VueCodeInformation).__referencesCodeLens) continue; result.push({ - start: document.positionAt(mapping.sourceRange[0]), - end: document.positionAt(mapping.sourceRange[1]), + start: document.positionAt(mapping[MappingKey.SOURCE_CODE_RANGE][0]), + end: document.positionAt(mapping[MappingKey.SOURCE_CODE_RANGE][1]), }); } } @@ -38,7 +38,7 @@ export const create = function (): Service { await worker(document.uri, async (vueFile) => { - const document = context.documents.getDocumentByUri(vueFile.id, vueFile.languageId, vueFile.snapshot); + const document = context.documents.get(vueFile.id, vueFile.languageId, vueFile.snapshot); const offset = document.offsetAt(range.start); const blocks = [ vueFile.sfc.script, diff --git a/packages/language-service/src/plugins/vue-extract-file.ts b/packages/language-service/src/plugins/vue-extract-file.ts index 63a544d133..dd5029cc22 100644 --- a/packages/language-service/src/plugins/vue-extract-file.ts +++ b/packages/language-service/src/plugins/vue-extract-file.ts @@ -1,6 +1,6 @@ import { CreateFile, Service, ServiceContext, TextDocumentEdit, TextEdit } from '@volar/language-service'; import { ExpressionNode, type TemplateChildNode } from '@vue/compiler-dom'; -import { Sfc, VueFile, scriptRanges } from '@vue/language-core'; +import { MappingKey, Sfc, VueFile, scriptRanges } from '@vue/language-core'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { Provide } from 'volar-service-typescript'; import type * as vscode from 'vscode-languageserver-protocol'; @@ -62,9 +62,9 @@ export const create = function (): Service { async resolveCodeAction(codeAction) { const { uri, range, newName } = codeAction.data as ActionData; - const document = ctx!.getTextDocument(uri)!; const [startOffset, endOffset]: [number, number] = range; - const [vueFile] = ctx!.project.fileProvider.getVirtualFile(document.uri) as [VueFile, any]; + const [vueFile] = ctx!.project.fileProvider.getVirtualFile(uri) as [VueFile, any]; + const document = ctx!.documents.get(uri, vueFile.languageId, vueFile.snapshot)!; const { sfc } = vueFile; const script = sfc.scriptSetup ?? sfc.script; @@ -180,7 +180,7 @@ export const create = function (): Service { }>(); const checker = languageService.getProgram()!.getTypeChecker(); const [virtualFile] = ctx!.project.fileProvider.getVirtualFile(tsScriptUri); - const maps = virtualFile ? [...ctx!.documents.getMapsByVirtualFile(virtualFile)] : []; + const maps = virtualFile ? [...ctx!.documents.getMaps(virtualFile)] : []; sourceFile.forEachChild(function visit(node) { if ( @@ -191,8 +191,8 @@ export const create = function (): Service { ) { const { name } = node; for (const map of maps) { - const source = map.map.toSourceOffset(name.getEnd()); - if (source && source[0] >= sfc.template!.startTagEnd + templateCodeRange![0] && source[0] <= sfc.template!.startTagEnd + templateCodeRange![1] && source[1].data.semanticTokens) { + const source = map.map.getSourceOffset(name.getEnd()); + if (source && source[0] >= sfc.template!.startTagEnd + templateCodeRange![0] && source[0] <= sfc.template!.startTagEnd + templateCodeRange![1] && source[1][MappingKey.DATA].semanticTokens) { if (!result.has(name.text)) { const type = checker.getTypeAtLocation(node); const typeString = checker.typeToString(type, node, ts.TypeFormatFlags.NoTruncation); diff --git a/packages/language-service/src/plugins/vue-template.ts b/packages/language-service/src/plugins/vue-template.ts index bc41d886af..9040ab06f2 100644 --- a/packages/language-service/src/plugins/vue-template.ts +++ b/packages/language-service/src/plugins/vue-template.ts @@ -1,4 +1,4 @@ -import { FileRangeCapabilities, Service, ServiceContext, SourceMapWithDocuments } from '@volar/language-service'; +import { CodeInformation, Service, ServiceContext, SourceMapWithDocuments } from '@volar/language-service'; import { VueFile, hyphenateAttr, hyphenateTag, parseScriptSetupRanges, tsCodegen } from '@vue/language-core'; import { camelize, capitalize } from '@vue/shared'; import * as html from 'vscode-html-languageservice'; @@ -78,8 +78,8 @@ export const create = (options: { const [virtualFile] = _context.project.fileProvider.getVirtualFile(document.uri); if (virtualFile) { - for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { - const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + for (const map of _context.documents.getMaps(virtualFile)) { + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; if (sourceVirtualFile instanceof VueFile) { await provideHtmlData(map, sourceVirtualFile); } @@ -91,8 +91,8 @@ export const create = (options: { return; if (virtualFile) { - for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { - const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + for (const map of _context.documents.getMaps(virtualFile)) { + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; if (sourceVirtualFile instanceof VueFile) { afterHtmlCompletion(htmlComplete, map, sourceVirtualFile); } @@ -117,9 +117,9 @@ export const create = (options: { if (!virtualFile) return; - for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { + for (const map of _context.documents.getMaps(virtualFile)) { - const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; const scanner = options.getScanner(htmlOrPugService, document); if (sourceVirtualFile instanceof VueFile && scanner) { @@ -241,9 +241,9 @@ export const create = (options: { if (!virtualFile) return; - for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { + for (const map of _context.documents.getMaps(virtualFile)) { - const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + const sourceVirtualFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; if (!(sourceVirtualFile instanceof VueFile)) continue; @@ -303,9 +303,9 @@ export const create = (options: { if (!virtualFile) return; - for (const map of _context.documents.getMapsByVirtualFile(virtualFile)) { + for (const map of _context.documents.getMaps(virtualFile)) { - const sourceFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.root; + const sourceFile = _context.project.fileProvider.getSourceFile(map.sourceFileDocument.uri)?.virtualFile?.[0]; if (!(sourceFile instanceof VueFile)) continue; @@ -355,7 +355,7 @@ export const create = (options: { }, }; - async function provideHtmlData(map: SourceMapWithDocuments, vueSourceFile: VueFile) { + async function provideHtmlData(map: SourceMapWithDocuments, vueSourceFile: VueFile) { const languageService = _context!.inject('typescript/languageService'); const languageServiceHost = _context!.inject('typescript/languageServiceHost'); @@ -549,7 +549,7 @@ export const create = (options: { ]); } - function afterHtmlCompletion(completionList: vscode.CompletionList, map: SourceMapWithDocuments, vueSourceFile: VueFile) { + function afterHtmlCompletion(completionList: vscode.CompletionList, map: SourceMapWithDocuments, vueSourceFile: VueFile) { const languageService = _context!.inject('typescript/languageService'); const replacement = getReplacement(completionList, map.sourceFileDocument); diff --git a/packages/language-service/src/plugins/vue-twoslash-queries.ts b/packages/language-service/src/plugins/vue-twoslash-queries.ts index bc1057fe09..dfb1384624 100644 --- a/packages/language-service/src/plugins/vue-twoslash-queries.ts +++ b/packages/language-service/src/plugins/vue-twoslash-queries.ts @@ -1,4 +1,4 @@ -import { FileKind, forEachEmbeddedFile, Service, ServiceContext } from '@volar/language-service'; +import { forEachEmbeddedFile, Service, ServiceContext } from '@volar/language-service'; import * as vue from '@vue/language-core'; import type * as vscode from 'vscode-languageserver-protocol'; @@ -29,12 +29,12 @@ const plugin: Service = (context: ServiceContext { - if (virtualFile.kind === FileKind.TypeScriptHostFile) { - for (const map of context.documents.getMapsByVirtualFile(virtualFile)) { + for (const virtualFile of forEachEmbeddedFile(vueFile)) { + if (virtualFile.typescript) { + for (const map of context.documents.getMaps(virtualFile)) { for (const [pointerPosition, hoverOffset] of hoverOffsets) { - for (const [tsOffset, mapping] of map.map.toGeneratedOffsets(hoverOffset)) { - if (mapping.data.hover) { + for (const [tsOffset, mapping] of map.map.getGeneratedOffsets(hoverOffset)) { + if (mapping[vue.MappingKey.DATA].hover) { const quickInfo = languageService.getQuickInfoAtPosition(context.env.uriToFileName(virtualFile.id), tsOffset); if (quickInfo) { inlayHints.push({ @@ -50,7 +50,7 @@ const plugin: Service = (context: ServiceContext { @@ -21,17 +22,11 @@ const plugin: Service = (context) => { for (const mapping of vitualFile.mappings) { - const hint: { - setting: string; - label: string; - tooltip: string; - paddingRight?: boolean; - paddingLeft?: boolean; - } | undefined = (mapping.data as any).__hint; + const hint = (mapping[MappingKey.DATA] as VueCodeInformation).__hint; if ( - mapping.generatedRange[0] >= start - && mapping.generatedRange[1] <= end + mapping[MappingKey.GENERATED_CODE_RANGE][0] >= start + && mapping[MappingKey.GENERATED_CODE_RANGE][1] <= end && hint ) { @@ -44,7 +39,7 @@ const plugin: Service = (context) => { label: hint.label, paddingRight: hint.paddingRight, paddingLeft: hint.paddingLeft, - position: document.positionAt(mapping.generatedRange[0]), + position: document.positionAt(mapping[MappingKey.GENERATED_CODE_RANGE][0]), kind: 2 satisfies typeof vscode.InlayHintKind.Parameter, tooltip: { kind: 'markdown', diff --git a/packages/language-service/tests/utils/createTester.ts b/packages/language-service/tests/utils/createTester.ts index 0f7c0342e1..3585655e89 100644 --- a/packages/language-service/tests/utils/createTester.ts +++ b/packages/language-service/tests/utils/createTester.ts @@ -1,4 +1,5 @@ -import { TypeScriptProjectHost, createLanguageService, createTypeScriptProject, resolveCommonLanguageId } from '@volar/language-service'; +import { createLanguageService, resolveCommonLanguageId } from '@volar/language-service'; +import { createProject, ProjectHost } from '@volar/typescript'; import * as path from 'path'; import type * as ts from 'typescript/lib/tsserverlibrary'; import { URI } from 'vscode-uri'; @@ -17,13 +18,16 @@ function createTester(root: string) { const parsedCommandLine = createParsedCommandLine(ts, ts.sys, realTsConfig); parsedCommandLine.fileNames = parsedCommandLine.fileNames.map(fileName => fileName.replace(/\\/g, '/')); const scriptSnapshots = new Map(); - const projectHost: TypeScriptProjectHost = { - configFileName: realTsConfig, + const serviceEnv = createMockServiceEnv(rootUri, () => currentVSCodeSettings ?? defaultVSCodeSettings); + const projectHost: ProjectHost = { getCurrentDirectory: () => root, getProjectVersion: () => '0', getScriptFileNames: () => parsedCommandLine.fileNames, getCompilationSettings: () => parsedCommandLine.options, getScriptSnapshot, + getFileName: serviceEnv.uriToFileName, + getFileId: serviceEnv.fileNameToUri, + getLanguageId: resolveCommonLanguageId, }; const languages = resolveLanguages(ts, {}, parsedCommandLine.options, parsedCommandLine.vueOptions); const services = resolveServices({}, parsedCommandLine.vueOptions); @@ -32,12 +36,12 @@ function createTester(root: string) { 'javascript.preferences.quoteStyle': 'single', }; let currentVSCodeSettings: any; - const serviceEnv = createMockServiceEnv(rootUri, () => currentVSCodeSettings ?? defaultVSCodeSettings); - const project = createTypeScriptProject( + const project = createProject( + ts, + ts.sys, Object.values(languages), + realTsConfig, projectHost, - serviceEnv.fileNameToUri, - resolveCommonLanguageId ); const languageService = createLanguageService({ typescript: ts as any }, Object.values(services), serviceEnv, project); diff --git a/packages/tsc-eslint-hook/src/index.ts b/packages/tsc-eslint-hook/src/index.ts index f84742efe8..454b42dcd2 100644 --- a/packages/tsc-eslint-hook/src/index.ts +++ b/packages/tsc-eslint-hook/src/index.ts @@ -13,13 +13,13 @@ export = async function ( baseConfig: resolveConfig(tsProgram), useEslintrc: false, }); - const fileNames = program.__vue.project.typescript!.projectHost.getScriptFileNames(); + const fileNames = program.__vue.project.typescript!.languageServiceHost.getScriptFileNames(); const fileProvider = program.__vue.project.fileProvider; const formatter = await eslint.loadFormatter(); for (const fileName of fileNames) { - const vueFile = fileProvider.getSourceFile(fileName)?.root; + const vueFile = fileProvider.getSourceFile(fileName)?.virtualFile?.[0]; if (vueFile) { @@ -27,7 +27,7 @@ export = async function ( const all: typeof vueFile.embeddedFiles = []; vueFile.embeddedFiles.forEach(async function visit(embeddedFile) { - if (embeddedFile.capabilities.diagnostic) { + if (embeddedFile.mappings.some(mapping => mapping[3].diagnostics ?? true)) { all.push(embeddedFile); } embeddedFile.embeddedFiles.forEach(visit); @@ -71,15 +71,19 @@ export = async function ( if (sourceSnapshot !== vueFile.snapshot) continue; - for (const start of map.toSourceOffsets(msgStart)) { + for (const start of map.getSourceOffsets(msgStart)) { - const reportStart = typeof start[1].data.diagnostic === 'object' ? typeof start[1].data.diagnostic.shouldReport() : !!start[1].data.diagnostic; + const reportStart = typeof start[1][3].diagnostics === 'object' + ? typeof start[1][3].diagnostics.shouldReport() + : (start[1][3].diagnostics ?? true); if (!reportStart) continue; - for (const end of map.toSourceOffsets(msgEnd, true)) { + for (const end of map.getSourceOffsets(msgEnd, true)) { - const reportEnd = typeof end[1].data.diagnostic === 'object' ? typeof end[1].data.diagnostic.shouldReport() : !!end[1].data.diagnostic; + const reportEnd = typeof end[1][3].diagnostics === 'object' + ? typeof end[1][3].diagnostics.shouldReport() + : (end[1][3].diagnostics ?? true); if (!reportEnd) continue; diff --git a/packages/tsc/src/index.ts b/packages/tsc/src/index.ts index 2664923368..11e8a2d204 100644 --- a/packages/tsc/src/index.ts +++ b/packages/tsc/src/index.ts @@ -1,6 +1,6 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import * as vue from '@vue/language-core'; -import * as volarTs from '@volar/typescript'; +import { createProject, decorateLanguageService, getDocumentRegistry, getProgram, ProjectHost } from '@volar/typescript'; import { state } from './shared'; export type Hook = (program: _Program) => void; @@ -19,17 +19,10 @@ const windowsPathReg = /\\/g; export function createProgram(options: ts.CreateProgramOptions) { - if (!options.options.noEmit && !options.options.emitDeclarationOnly) - throw toThrow('js emit is not supported'); - - if (!options.options.noEmit && options.options.noEmitOnError) - throw toThrow('noEmitOnError is not supported'); - - if (options.options.extendedDiagnostics || options.options.generateTrace) - throw toThrow('--extendedDiagnostics / --generateTrace is not supported, please run `Write Virtual Files` in VSCode to write virtual files and use `--extendedDiagnostics` / `--generateTrace` via tsc instead of vue-tsc to debug.'); - - if (!options.host) - throw toThrow('!options.host'); + assert(options.options.noEmit || options.options.emitDeclarationOnly, 'js emit is not supported'); + assert(options.options.noEmit || !options.options.noEmitOnError, 'noEmitOnError is not supported'); + assert(!options.options.extendedDiagnostics && !options.options.generateTrace, '--extendedDiagnostics / --generateTrace is not supported, please run `Write Virtual Files` in VSCode to write virtual files and use `--extendedDiagnostics` / `--generateTrace` via tsc instead of vue-tsc to debug.'); + assert(options.host, '!options.host'); const ts = require('typescript') as typeof import('typescript/lib/tsserverlibrary'); @@ -60,8 +53,7 @@ export function createProgram(options: ts.CreateProgramOptions) { modifiedTime: number, scriptSnapshot: ts.IScriptSnapshot, }>(); - const projectHost: vue.TypeScriptProjectHost = { - configFileName: undefined, + const projectHost: ProjectHost = { getCurrentDirectory() { return ctx.options.host!.getCurrentDirectory().replace(windowsPathReg, '/'); }, @@ -75,36 +67,36 @@ export function createProgram(options: ts.CreateProgramOptions) { }, getProjectReferences: () => ctx.options.projectReferences, getCancellationToken: ctx.options.host!.getCancellationToken ? () => ctx.options.host!.getCancellationToken!() : undefined, + getFileId: fileName => fileName, + getFileName: id => id, + getLanguageId: vue.resolveCommonLanguageId, }; - const fileNameResolutionHost = { - fileNameToId: (fileName: string) => fileName, - idToFileName: (id: string) => id, - }; - const project = vue.createTypeScriptProject( + const project = createProject( + ts, + ts.sys, vue.createLanguages( ts, projectHost.getCompilationSettings(), vueCompilerOptions, ), + undefined, projectHost, - fileNameResolutionHost.fileNameToId, - vue.resolveCommonLanguageId, ); - const languageServiceHost = volarTs.createLanguageServiceHost( - project.typescript!.projectHost, - project.fileProvider, - fileNameResolutionHost, - ts, - ts.sys + const vueTsLs = ts.createLanguageService( + project.typescript!.languageServiceHost, + getDocumentRegistry( + ts, + ts.sys.useCaseSensitiveFileNames, + projectHost.getCurrentDirectory() + ) ); - const vueTsLs = ts.createLanguageService(languageServiceHost, volarTs.getDocumentRegistry(ts, ts.sys.useCaseSensitiveFileNames, projectHost.getCurrentDirectory())); - volarTs.decorateLanguageService(project.fileProvider, vueTsLs, false); + decorateLanguageService(project.fileProvider, vueTsLs, false); - program = volarTs.getProgram( + program = getProgram( ts as any, project.fileProvider, - fileNameResolutionHost, + projectHost, vueTsLs, ts.sys ) as (ts.Program & { __vue: ProgramContext; }); @@ -176,7 +168,9 @@ export function createProgram(options: ts.CreateProgramOptions) { return program; } -function toThrow(msg: string) { - console.error(msg); - return msg; +function assert(condition: unknown, message: string): asserts condition { + if (!condition) { + console.error(message); + throw new Error(message); + } } diff --git a/packages/typescript-plugin/src/index.ts b/packages/typescript-plugin/src/index.ts index cc0aeb3f57..f4a84e17b2 100644 --- a/packages/typescript-plugin/src/index.ts +++ b/packages/typescript-plugin/src/index.ts @@ -16,6 +16,7 @@ const init: ts.server.PluginModuleFactory = (modules) => { info.languageServiceHost.getCompilationSettings(), getVueCompilerOptions(), ), + ts.sys.useCaseSensitiveFileNames, () => { } );