diff --git a/packages/language-core/lib/codegen/script/globalTypes.ts b/packages/language-core/lib/codegen/script/globalTypes.ts index 70125ca668..9638adef0f 100644 --- a/packages/language-core/lib/codegen/script/globalTypes.ts +++ b/packages/language-core/lib/codegen/script/globalTypes.ts @@ -67,7 +67,10 @@ declare global { function __VLS_nonNullable(t: T): T extends null | undefined ? never : T; type __VLS_SelfComponent = string extends N ? {} : N extends string ? { [P in N]: C } : {}; - type __VLS_WithComponent = + type __VLS_WithComponent = + N1 extends keyof Ctx ? N1 extends N0 ? Pick : { [K in N0]: Ctx[N1] } : + N2 extends keyof Ctx ? N2 extends N0 ? Pick : { [K in N0]: Ctx[N2] } : + N3 extends keyof Ctx ? N3 extends N0 ? Pick : { [K in N0]: Ctx[N3] } : N1 extends keyof LocalComponents ? N1 extends N0 ? Pick : { [K in N0]: LocalComponents[N1] } : N2 extends keyof LocalComponents ? N2 extends N0 ? Pick : { [K in N0]: LocalComponents[N2] } : N3 extends keyof LocalComponents ? N3 extends N0 ? Pick : { [K in N0]: LocalComponents[N3] } : @@ -88,10 +91,10 @@ declare global { : (_: {}${vueCompilerOptions.strictTemplates ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${vueCompilerOptions.strictTemplates ? '' : ' & Record'} } }; function __VLS_elementAsFunction(tag: T, endTag?: T): (_: T${vueCompilerOptions.strictTemplates ? '' : ' & Record'}) => void; function __VLS_functionalComponentArgsRest any>(t: T): Parameters['length'] extends 2 ? [any] : []; - function __VLS_pickFunctionalComponentCtx(comp: T, compInstance: K): __VLS_PickNotAny< + function __VLS_pickFunctionalComponentCtx(comp: T, compInstance: K): NonNullable<__VLS_PickNotAny< '__ctx' extends keyof __VLS_PickNotAny ? K extends { __ctx?: infer Ctx } ? Ctx : never : any , T extends (props: any, ctx: infer Ctx) => any ? Ctx : any - >; + >>; type __VLS_FunctionalComponentProps = '__ctx' extends keyof __VLS_PickNotAny ? K extends { __ctx?: { props?: infer P } } ? NonNullable

: never : T extends (props: infer P, ...args: any) => any ? P : diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index ec48ad0a81..92da84d736 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -45,6 +45,7 @@ export interface ScriptCodegenOptions { scriptSetupRanges: ScriptSetupRanges | undefined; templateCodegen: TemplateCodegenContext & { codes: Code[]; } | undefined; globalTypes: boolean; + edited: boolean; getGeneratedLength: () => number; linkedCodeMappings: Mapping[]; } diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index 3a895a603c..c7c8a9cbcf 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -23,7 +23,7 @@ export function* generateTemplate( else { yield `const __VLS_template = (() => {${newLine}`; } - const templateCodegenCtx = createTemplateCodegenContext(new Set()); + const templateCodegenCtx = createTemplateCodegenContext({ scriptSetupBindingNames: new Set(), edited: options.edited }); yield `const __VLS_template_return = () => {${newLine}`; yield* generateCtx(options, isClassComponent); yield* generateTemplateContext(options, templateCodegenCtx); diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 480643d1a8..85c1548aa1 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -57,7 +57,7 @@ const _codeFeatures = { export type TemplateCodegenContext = ReturnType; -export function createTemplateCodegenContext(scriptSetupBindingNames: TemplateCodegenOptions['scriptSetupBindingNames']) { +export function createTemplateCodegenContext(options: Pick) { let ignoredError = false; let expectErrorToken: { errors: number; @@ -190,6 +190,9 @@ export function createTemplateCodegenContext(scriptSetupBindingNames: TemplateCo } }, generateAutoImportCompletion: function* (): Generator { + if (!options.edited) { + return; + } const all = [...accessExternalVariables.entries()]; if (!all.some(([_, offsets]) => offsets.size)) { return; @@ -198,7 +201,7 @@ export function createTemplateCodegenContext(scriptSetupBindingNames: TemplateCo yield `[`; for (const [varName, offsets] of all) { for (const offset of offsets) { - if (scriptSetupBindingNames.has(varName)) { + if (options.scriptSetupBindingNames.has(varName)) { // #3409 yield [ varName, diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 42026ad78b..2a3edac1da 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -23,8 +23,7 @@ export function* generateComponent( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - currentComponent: CompilerDOM.ElementNode | undefined, - componentCtxVar: string | undefined + currentComponent: CompilerDOM.ElementNode | undefined ): Generator { const startTagOffset = node.loc.start.offset + options.template.content.substring(node.loc.start.offset).indexOf(node.tag); const endTagOffset = !node.isSelfClosing && options.template.lang === 'html' ? node.loc.start.offset + node.loc.source.lastIndexOf(node.tag) : undefined; @@ -137,40 +136,26 @@ export function* generateComponent( yield `)${endOfLine}`; } else if (!isComponentTag) { - yield `// @ts-ignore${newLine}`; - yield `const ${var_originalComponent} = ({} as `; - for (const componentName of possibleOriginalNames) { - yield `'${componentName}' extends keyof typeof __VLS_ctx ? { '${getCanonicalComponentName(node.tag)}': typeof __VLS_ctx`; - yield* generatePropertyAccess(options, ctx, componentName); - yield ` }: `; - } - yield `typeof __VLS_resolvedLocalAndGlobalComponents)${newLine}`; - yield* generatePropertyAccess( - options, - ctx, - getCanonicalComponentName(node.tag), + yield `const ${var_originalComponent} = __VLS_resolvedLocalAndGlobalComponents.`; + yield* generateCanonicalComponentName( + node.tag, startTagOffset, - ctx.codeFeatures.verification + { + // with hover support + ...ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation, + ...ctx.codeFeatures.verification, + } ); - yield endOfLine; + yield `${endOfLine}`; - // hover support - for (const offset of tagOffsets) { - yield `({} as { ${getCanonicalComponentName(node.tag)}: typeof ${var_originalComponent} }).`; - yield* generateCanonicalComponentName( - node.tag, - offset, - ctx.codeFeatures.withoutHighlightAndCompletionAndNavigation - ); - yield endOfLine; - } const camelizedTag = camelize(node.tag); if (variableNameRegex.test(camelizedTag)) { // renaming / find references support + yield `/** @type { [`; for (const tagOffset of tagOffsets) { for (const shouldCapitalize of (node.tag[0] === node.tag[0].toUpperCase() ? [false] : [true, false])) { const expectName = shouldCapitalize ? capitalize(camelizedTag) : camelizedTag; - yield `__VLS_components.`; + yield `typeof __VLS_components.`; yield* generateCamelized( shouldCapitalize ? capitalize(node.tag) : node.tag, tagOffset, @@ -181,17 +166,16 @@ export function* generateComponent( }, } ); - yield `;`; + yield `, `; } } - yield `${newLine}`; + yield `] } */${newLine}`; // auto import support - yield `// @ts-ignore${newLine}`; // #2304 - yield `[`; - for (const tagOffset of tagOffsets) { + if (options.edited) { + yield `// @ts-ignore${newLine}`; // #2304 yield* generateCamelized( capitalize(node.tag), - tagOffset, + startTagOffset, { completion: { isAdditional: true, @@ -199,9 +183,8 @@ export function* generateComponent( }, } ); - yield `,`; + yield `${endOfLine}`; } - yield `]${endOfLine}`; } } else { @@ -213,38 +196,17 @@ export function* generateComponent( yield* generateElementProps(options, ctx, node, props, false); yield `}))${endOfLine}`; - if (options.vueCompilerOptions.strictTemplates) { - // with strictTemplates, generate once for props type-checking + instance type - yield `const ${var_componentInstance} = ${var_functionalComponent}(`; - yield* wrapWith( - startTagOffset, - startTagOffset + node.tag.length, - ctx.codeFeatures.verification, - `{`, - ...generateElementProps(options, ctx, node, props, true, propsFailedExps), - `}` - ); - yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; - } - else { - // without strictTemplates, this only for instance type - yield `const ${var_componentInstance} = ${var_functionalComponent}({`; - yield* generateElementProps(options, ctx, node, props, false); - yield `}, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; - // and this for props type-checking - yield `({} as (props: __VLS_FunctionalComponentProps & Record) => void)(`; - yield* wrapWith( - startTagOffset, - startTagOffset + node.tag.length, - ctx.codeFeatures.verification, - `{`, - ...generateElementProps(options, ctx, node, props, true, propsFailedExps), - `}` - ); - yield `)${endOfLine}`; - } + yield `const ${var_componentInstance} = ${var_functionalComponent}(`; + yield* wrapWith( + startTagOffset, + startTagOffset + node.tag.length, + ctx.codeFeatures.verification, + `{`, + ...generateElementProps(options, ctx, node, props, true, propsFailedExps), + `}` + ); + yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; - componentCtxVar = var_defineComponentCtx; currentComponent = node; for (const failedExp of propsFailedExps) { @@ -262,28 +224,14 @@ export function* generateComponent( } const refName = yield* generateVScope(options, ctx, node, props); + if (refName) { + ctx.usedComponentCtxVars.add(var_defineComponentCtx); + } - ctx.usedComponentCtxVars.add(componentCtxVar); const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEmit, var_componentEvents); - - if (var_defineComponentCtx && ctx.usedComponentCtxVars.has(var_defineComponentCtx)) { - yield `const ${componentCtxVar} = __VLS_nonNullable(__VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance}))${endOfLine}`; - if (refName) { - yield `// @ts-ignore${newLine}`; - if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL - && node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION - && node.codegenNode.props.properties.find(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for') - ) { - yield `(${refName} ??= []).push(${var_defineComponentCtx})`; - } else { - yield `${refName} = ${var_defineComponentCtx}`; - } - - yield endOfLine; - } - } if (usedComponentEventsVar) { - yield `let ${var_componentEmit}!: typeof ${componentCtxVar}.emit${endOfLine}`; + ctx.usedComponentCtxVars.add(var_defineComponentCtx); + yield `let ${var_componentEmit}!: typeof ${var_defineComponentCtx}.emit${endOfLine}`; yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; } @@ -301,10 +249,27 @@ export function* generateComponent( const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; if (slotDir) { - yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar); + yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, var_defineComponentCtx); } else { - yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar); + yield* generateElementChildren(options, ctx, node, currentComponent, var_defineComponentCtx); + } + + if (ctx.usedComponentCtxVars.has(var_defineComponentCtx)) { + yield `const ${var_defineComponentCtx} = __VLS_pickFunctionalComponentCtx(${var_originalComponent}, ${var_componentInstance})${endOfLine}`; + if (refName) { + yield `// @ts-ignore${newLine}`; + if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL + && node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION + && node.codegenNode.props.properties.find(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for') + ) { + yield `(${refName} ??= []).push(${var_defineComponentCtx})`; + } else { + yield `${refName} = ${var_defineComponentCtx}`; + } + + yield endOfLine; + } } } diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index 67e38651c1..a184a2c798 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -28,6 +28,7 @@ export function* generateElementChildren( && node.tagType !== CompilerDOM.ElementTypes.ELEMENT && node.tagType !== CompilerDOM.ElementTypes.TEMPLATE ) { + ctx.usedComponentCtxVars.add(componentCtxVar); yield `__VLS_nonNullable(${componentCtxVar}.slots).`; yield* wrapWith( node.children[0].loc.start.offset, diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index 780e9d9618..5d4960192a 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -109,6 +109,7 @@ export function* generateElementProps( if (shouldSpread) { yield `...{ `; } + const codeInfo = ctx.codeFeatures.withoutHighlightAndCompletion; const codes = wrapWith( prop.loc.start.offset, prop.loc.end.offset, @@ -121,8 +122,20 @@ export function* generateElementProps( propName, prop.arg.loc.start.offset, { - ...ctx.codeFeatures.withoutHighlightAndCompletion, - navigation: ctx.codeFeatures.withoutHighlightAndCompletion.navigation + ...codeInfo, + verification: options.vueCompilerOptions.strictTemplates + ? codeInfo.verification + : { + shouldReport(_source, code) { + if (String(code) === '2353' || String(code) === '2561') { + return false; + } + return typeof codeInfo.verification === 'object' + ? codeInfo.verification.shouldReport?.(_source, code) ?? true + : true; + }, + }, + navigation: codeInfo.navigation ? { resolveRenameNewName: camelize, resolveRenameEditText: shouldCamelize ? hyphenateAttr : undefined, @@ -183,6 +196,7 @@ export function* generateElementProps( if (shouldSpread) { yield `...{ `; } + const codeInfo = ctx.codeFeatures.withoutHighlightAndCompletion; const codes = conditionWrapWith( enableCodeFeatures, prop.loc.start.offset, @@ -195,7 +209,19 @@ export function* generateElementProps( prop.loc.start.offset, shouldCamelize ? { - ...ctx.codeFeatures.withoutHighlightAndCompletion, + ...codeInfo, + verification: options.vueCompilerOptions.strictTemplates + ? codeInfo.verification + : { + shouldReport(_source, code) { + if (String(code) === '2353' || String(code) === '2561') { + return false; + } + return typeof codeInfo.verification === 'object' + ? codeInfo.verification.shouldReport?.(_source, code) ?? true + : true; + }, + }, navigation: ctx.codeFeatures.withoutHighlightAndCompletion.navigation ? { resolveRenameNewName: camelize, diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index a338437e97..b6aa1f43ce 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -15,6 +15,7 @@ export interface TemplateCodegenOptions { template: NonNullable; scriptSetupBindingNames: Set; scriptSetupImportComponentNames: Set; + edited: boolean; templateRefNames: Map; hasDefineSlots?: boolean; slotsAssignName?: string; @@ -23,7 +24,7 @@ export interface TemplateCodegenOptions { } export function* generateTemplate(options: TemplateCodegenOptions): Generator { - const ctx = createTemplateCodegenContext(options.scriptSetupBindingNames); + const ctx = createTemplateCodegenContext(options); if (options.slotsAssignName) { ctx.addLocalVariable(options.slotsAssignName); @@ -105,15 +106,21 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator { - yield `let __VLS_resolvedLocalAndGlobalComponents!: {}`; + yield `let __VLS_resolvedLocalAndGlobalComponents!: Required<{}`; if (options.template.ast) { + const components = new Set(); for (const node of forEachElementNode(options.template.ast)) { if ( node.tagType === CompilerDOM.ElementTypes.COMPONENT && node.tag.toLowerCase() !== 'component' && !node.tag.includes('.') // namespace tag ) { - yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_localComponents, `; + if (components.has(node.tag)) { + continue; + } + components.add(node.tag); + yield newLine; + yield ` & __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', typeof __VLS_ctx, typeof __VLS_localComponents, `; yield getPossibleOriginalComponentNames(node.tag, false) .map(name => `"${name}"`) .join(', '); @@ -121,7 +128,7 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator${endOfLine}`; } } diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 999c0d4709..ad3608242f 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -80,7 +80,7 @@ export function* generateTemplateChild( yield* generateElement(options, ctx, node, currentComponent, componentCtxVar); } else { - yield* generateComponent(options, ctx, node, currentComponent, componentCtxVar); + yield* generateComponent(options, ctx, node, currentComponent); } } } diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index a84dfef4b2..c5e1308e9c 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -9,6 +9,8 @@ import type { Code, Sfc, VueLanguagePlugin } from '../types'; export const tsCodegen = new WeakMap>(); +const fileEditTimes = new Map(); + const plugin: VueLanguagePlugin = ctx => { return { @@ -92,6 +94,7 @@ function createTsx( compilerOptions: ctx.compilerOptions, vueCompilerOptions: ctx.vueCompilerOptions, template: _sfc.template, + edited: ctx.vueCompilerOptions.__test || (fileEditTimes.get(fileName) ?? 0) >= 2, scriptSetupBindingNames: scriptSetupBindingNames(), scriptSetupImportComponentNames: scriptSetupImportComponentNames(), templateRefNames: new Map(), @@ -157,9 +160,11 @@ function createTsx( templateCodegen: _template, compilerOptions: ctx.compilerOptions, vueCompilerOptions: ctx.vueCompilerOptions, + edited: ctx.vueCompilerOptions.__test || (fileEditTimes.get(fileName) ?? 0) >= 2, getGeneratedLength: () => generatedLength, linkedCodeMappings, }); + fileEditTimes.set(fileName, (fileEditTimes.get(fileName) ?? 0) + 1); let current = codegen.next(); diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 11860c48de..57dfd05898 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -53,6 +53,9 @@ export interface VueCompilerOptions { experimentalDefinePropProposal: 'kevinEdition' | 'johnsonEdition' | false; experimentalResolveStyleCssClasses: 'scoped' | 'always' | 'never'; experimentalModelPropName: Record | Record[]>>; + + // internal + __test?: boolean; } export const validVersions = [2, 2.1] as const; diff --git a/packages/language-server/lib/initialize.ts b/packages/language-server/lib/initialize.ts index 450353bfcb..bfb91fb916 100644 --- a/packages/language-server/lib/initialize.ts +++ b/packages/language-server/lib/initialize.ts @@ -37,6 +37,7 @@ export function initialize( compilerOptions = ts.getDefaultCompilerOptions(); vueCompilerOptions = resolveVueCompilerOptions({}); } + vueCompilerOptions.__test = params.initializationOptions.typescript.disableAutoImportCache; updateFileWatcher(vueCompilerOptions); return { languagePlugins: [createVueLanguagePlugin2( diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index 541467169d..65b4c4d787 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -26,6 +26,7 @@ connection.onInitialize(params => { vueOptions: resolveVueCompilerOptions({}), options: ts.getDefaultCompilerOptions(), }; + commandLine.vueOptions.__test = params.initializationOptions.typescript.disableAutoImportCache; return { languagePlugins: [createVueLanguagePlugin2( ts, diff --git a/packages/language-server/tests/completions.spec.ts b/packages/language-server/tests/completions.spec.ts index 96c9624688..044f28966f 100644 --- a/packages/language-server/tests/completions.spec.ts +++ b/packages/language-server/tests/completions.spec.ts @@ -338,6 +338,12 @@ describe('Completions', async () => { }, }, ], + "commitCharacters": [ + ".", + ",", + ";", + "(", + ], "detail": "Add import from "./ComponentForAutoImport.vue" (property) default: DefineComponent<{}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly>, {}, {}>", "documentation": {