diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index 2108cfe9d3..8eedff34e0 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -478,7 +478,10 @@ function createSchemaResolvers( }; } function resolveSlotProperties(prop: ts.Symbol): SlotMeta { - const subtype = typeChecker.getTypeOfSymbolAtLocation(typeChecker.getTypeOfSymbolAtLocation(prop, symbolNode!).getCallSignatures()[0].parameters[0], symbolNode!); + const propType = typeChecker.getNonNullableType(typeChecker.getTypeOfSymbolAtLocation(prop, symbolNode!)); + const signatures = propType.getCallSignatures(); + const paramType = signatures[0].parameters[0]; + const subtype = typeChecker.getTypeOfSymbolAtLocation(paramType, symbolNode!); let schema: PropertyMetaSchema; return { diff --git a/packages/vue-language-core/src/generators/script.ts b/packages/vue-language-core/src/generators/script.ts index fc10e0d251..95ae4f4d8d 100644 --- a/packages/vue-language-core/src/generators/script.ts +++ b/packages/vue-language-core/src/generators/script.ts @@ -649,6 +649,8 @@ export function generate( const useGlobalThisTypeInCtx = fileName.endsWith('.html'); + codeGen.push(`let __VLS_any: any;\n`); + codeGen.push(`let __VLS_ctx!: ${useGlobalThisTypeInCtx ? 'typeof globalThis &' : ''}`); if (sfc.scriptSetup) { codeGen.push(`InstanceType {}>> & `); diff --git a/packages/vue-language-core/src/generators/template.ts b/packages/vue-language-core/src/generators/template.ts index 6b1b98b149..31faa9762d 100644 --- a/packages/vue-language-core/src/generators/template.ts +++ b/packages/vue-language-core/src/generators/template.ts @@ -6,6 +6,7 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import { VueCompilerOptions } from '../types'; import { colletVars, walkInterpolationFragment } from '../utils/transform'; import minimatch from 'minimatch'; +import * as muggle from 'muggle-string'; const capabilitiesPresets = { all: FileRangeCapabilities.full, @@ -49,7 +50,6 @@ const transformContext: CompilerDOM.TransformContext = { export function generate( ts: typeof import('typescript/lib/tsserverlibrary'), - compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions, sourceTemplate: string, sourceLang: string, @@ -105,7 +105,7 @@ export function generate( codeGen.push(`declare var __VLS_slots:\n`); for (const [exp, slot] of slotExps) { hasSlot = true; - codeGen.push(`Record, (_: typeof ${slot.varName}) => any> &\n`); + codeGen.push(`Partial, (_: typeof ${slot.varName}) => any>> &\n`); } codeGen.push(`{\n`); for (const [name, slot] of slots) { @@ -119,7 +119,7 @@ export function generate( }, slot.nodeLoc, ); - codeGen.push(`: (_: typeof ${slot.varName}) => any,\n`); + codeGen.push(`?(_: typeof ${slot.varName}): any,\n`); } codeGen.push(`};\n`); } @@ -334,6 +334,7 @@ export function generate( if (branch.condition?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { codeGen.push(` `); + const beforeCodeLength = codeGen.length; writeInterpolation( branch.condition.content, branch.condition.loc.start.offset, @@ -342,16 +343,15 @@ export function generate( ')', branch.condition.loc, ); + const afterCodeLength = codeGen.length; appendFormattingCode( branch.condition.content, branch.condition.loc.start.offset, formatBrackets.round, ); - if (vueCompilerOptions.narrowingTypesInInlineHandlers) { - blockConditions.push(branch.condition.content); - addedBlockCondition = true; - } + blockConditions.push(muggle.toString(codeGen.slice(beforeCodeLength, afterCodeLength))); + addedBlockCondition = true; } codeGen.push(` {\n`); @@ -543,7 +543,7 @@ export function generate( codeGen.push(`;\n`); } - codeGen.push(`(__VLS_x as import('./__VLS_types.js').IntrinsicElements)[`); + codeGen.push(`(__VLS_any as import('./__VLS_types.js').IntrinsicElements)[`); writeCodeWithQuotes( node.tag, tagOffsets[0], @@ -565,7 +565,7 @@ export function generate( codeGen.push(['', 'template', startTagOffset, capabilitiesPresets.diagnosticOnly]); // diagnostic start { - codeGen.push(`(__VLS_x as import('./__VLS_types.js').asFunctionalComponent)`); + codeGen.push(`(__VLS_any as import('./__VLS_types.js').asFunctionalComponent)`); } codeGen.push(['', 'template', startTagOffset + node.tag.length, capabilitiesPresets.diagnosticOnly]); // diagnostic end codeGen.push(`(`); @@ -591,7 +591,7 @@ export function generate( codeGen.push(['', 'template', startTagOffset, capabilitiesPresets.diagnosticOnly]); // diagnostic start { - codeGen.push(`(__VLS_x as import('./__VLS_types.js').asFunctionalComponent prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); - let inScope = false; - let originalConditionsNum = blockConditions.length; - - if (vScope?.type === CompilerDOM.NodeTypes.DIRECTIVE && vScope.exp) { + writeInlineCss(node); - const scopeVar = `__VLS_${elementIndex++}`; - const condition = `(await import('./__VLS_types.js')).withScope(__VLS_ctx, ${scopeVar})`; + const vScope = node.props.find(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); + let inScope = false; + let originalConditionsNum = blockConditions.length; - codeGen.push(`const ${scopeVar} = `); - codeGen.push([ - vScope.exp.loc.source, - 'template', - vScope.exp.loc.start.offset, - capabilitiesPresets.all, - ]); - codeGen.push(';\n'); - codeGen.push(`if (${condition}) {\n`); - blockConditions.push(condition); - inScope = true; - } + if (vScope?.type === CompilerDOM.NodeTypes.DIRECTIVE && vScope.exp) { - writeDirectives(node); - writeElReferences(node); // - if (cssScopedClasses.length) writeClassScoped(node); - writeEvents(node); - writeSlots(node, startTagOffset); + const scopeVar = `__VLS_${elementIndex++}`; + const condition = `(await import('./__VLS_types.js')).withScope(__VLS_ctx, ${scopeVar})`; - for (const childNode of node.children) { - visitNode(childNode, parentEl); - } + codeGen.push(`const ${scopeVar} = `); + codeGen.push([ + vScope.exp.loc.source, + 'template', + vScope.exp.loc.start.offset, + capabilitiesPresets.all, + ]); + codeGen.push(';\n'); + codeGen.push(`if (${condition}) {\n`); + blockConditions.push(condition); + inScope = true; + } - if (slotBlockVars) { - for (const varName of slotBlockVars) - localVars[varName]--; - } + writeDirectives(node); + writeElReferences(node); // + if (cssScopedClasses.length) writeClassScoped(node); + writeEvents(node); + writeSlots(node, startTagOffset); + writeChildren(node, parentEl); - if (inScope) { - codeGen.push('}\n'); - blockConditions.length = originalConditionsNum; - } + if (inScope) { + codeGen.push('}\n'); + blockConditions.length = originalConditionsNum; } + //#endregion + codeGen.push(`}\n`); } function writeEvents(node: CompilerDOM.ElementNode) { @@ -808,7 +793,7 @@ export function generate( if (isCompoundExpression) { prefix = '$event => {\n'; - if (blockConditions.length) { + if (vueCompilerOptions.narrowingTypesInInlineHandlers) { for (const blockCondition of blockConditions) { prefix += `if (!(${blockCondition})) return;\n`; } @@ -1283,141 +1268,179 @@ export function generate( } } } - function writeImportSlots(node: CompilerDOM.ElementNode, parentEl: CompilerDOM.ElementNode | undefined, slotBlockVars: string[]) { + function writeChildren(node: CompilerDOM.ElementNode, parentEl: CompilerDOM.ElementNode | undefined) { - const componentVar = parentEl ? componentVars[parentEl.tag] : undefined; - - for (const prop of node.props) { - if ( - prop.type === CompilerDOM.NodeTypes.DIRECTIVE - && prop.name === 'slot' - ) { - - const varComponentInstanceA = `__VLS_${elementIndex++}`; - const varComponentInstanceB = `__VLS_${elementIndex++}`; - const varSlots = `__VLS_${elementIndex++}`; - - if (componentVar && parentEl) { - codeGen.push(`const ${varComponentInstanceA} = new __VLS_templateComponents.${componentVar}({ `); - writeProps(parentEl, 'class', 'slots'); - codeGen.push(`});\n`); - codeGen.push(`const ${varComponentInstanceB} = __VLS_templateComponents.${componentVar}({ `); - writeProps(parentEl, 'class', 'slots'); - codeGen.push(`});\n`); - writeInterpolationVarsExtraCompletion(); - codeGen.push(`let ${varSlots}!: import('./__VLS_types.js').ExtractComponentSlots>;\n`); - } - - if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - codeGen.push(`const `); + if (!parentEl) { + for (const childNode of node.children) { + visitNode(childNode, undefined); + } + return; + } - const collectAst = createTsAst(prop, `const ${prop.exp.content}`); - colletVars(ts, collectAst, slotBlockVars); + const componentVar = parentEl ? componentVars[parentEl.tag] : undefined; + const slotAndChildNodes: Record = {}; - codeGen.push([ - prop.exp.content, - 'template', - prop.exp.loc.start.offset, - capabilitiesPresets.all, - ]); - appendFormattingCode( - prop.exp.content, - prop.exp.loc.start.offset, - formatBrackets.round, - ); + for (const child of node.children) { + if (child.type === CompilerDOM.NodeTypes.COMMENT) { + continue; + } + if (child.type !== CompilerDOM.NodeTypes.ELEMENT) { + slotAndChildNodes.default ??= { nodes: [], slotDir: undefined }; + slotAndChildNodes.default.nodes.push(child); + } + else { + const slotDir = child.props.find(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'slot') as CompilerDOM.DirectiveNode | undefined; + const slotName = (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) || 'default'; + slotAndChildNodes[slotName] ??= { nodes: [], slotDir: undefined }; + slotAndChildNodes[slotName].nodes.push(child); + slotAndChildNodes[slotName].slotDir ??= slotDir; + } + } + + if (componentVar && parentEl) { + const varComponentInstanceA = `__VLS_${elementIndex++}`; + const varComponentInstanceB = `__VLS_${elementIndex++}`; + codeGen.push(`const ${varComponentInstanceA} = new __VLS_templateComponents.${componentVar}({ `); + writeProps(parentEl, 'class', 'slots'); + codeGen.push(`});\n`); + codeGen.push(`const ${varComponentInstanceB} = __VLS_templateComponents.${componentVar}({ `); + writeProps(parentEl, 'class', 'slots'); + codeGen.push(`});\n`); + writeInterpolationVarsExtraCompletion(); + if (vueCompilerOptions.strictTemplates) { + codeGen.push([ + '', + 'template', + parentEl.loc.start.offset, + capabilitiesPresets.diagnosticOnly, + ]); + } + codeGen.push(`(__VLS_any as import('./__VLS_types.js').ExtractComponentSlots>)`); + if (vueCompilerOptions.strictTemplates) { + codeGen.push([ + '', + 'template', + parentEl.loc.end.offset, + capabilitiesPresets.diagnosticOnly, + ]); + } + } + else { + codeGen.push(`(__VLS_any as Record)`); + } + codeGen.push(` = {\n`); - codeGen.push(` = `); - } + for (const [slotName, { nodes, slotDir }] of Object.entries(slotAndChildNodes)) { - if (!componentVar || !parentEl) { - // fix https://github.com/johnsoncodehk/volar/issues/1425 - codeGen.push(`{} as any;\n`); - continue; - } + let isStatic = true; + if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + isStatic = slotDir.arg.isStatic; + } + const argRange: [number, number] | undefined = + slotDir + ? slotDir.arg + ? [slotDir.arg.loc.start.offset, slotDir.arg.loc.end.offset] + : [slotDir.loc.start.offset, slotDir.loc.start.offset + slotDir.loc.source.split('=')[0].length] + : undefined; - let slotName = 'default'; - let isStatic = true; - if (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.content !== '') { - isStatic = prop.arg.isStatic; - slotName = prop.arg.content; - } - const argRange: [number, number] = prop.arg - ? [prop.arg.loc.start.offset, prop.arg.loc.end.offset] - : [prop.loc.start.offset, prop.loc.start.offset + prop.loc.source.split('=')[0].length]; + if (!slotDir || !argRange) { codeGen.push([ '', 'template', - argRange[0], - capabilitiesPresets.diagnosticOnly, + Math.min(...nodes.map(node => node.loc.start.offset)), + { references: true }, ]); - codeGen.push(varSlots); - if (isStatic) { - // https://github.com/johnsoncodehk/volar/issues/2236 - if (!compilerOptions.noPropertyAccessFromIndexSignature) { - writePropertyAccess( - slotName, - argRange, - { - ...capabilitiesPresets.slotName, - completion: !!prop.arg, - }, - ); - } - else { - codeGen.push(`[`); - writeCodeWithQuotes( - slotName, - argRange, - { - ...capabilitiesPresets.slotName, - completion: !!prop.arg, - }, - ); - codeGen.push(`]`); - } - } - else { - codeGen.push(`[`); - writeInterpolation( - slotName, - argRange[0] + 1, - capabilitiesPresets.all, - '', - '', - (prop.loc as any).slot_name ?? ((prop.loc as any).slot_name = {}), - ); - codeGen.push(`]`); - writeInterpolationVarsExtraCompletion(); - } + codeGen.push(slotName); codeGen.push([ '', 'template', - argRange[1], - capabilitiesPresets.diagnosticOnly, + Math.max(...nodes.map(node => node.loc.end.offset)), + { references: true }, ]); - codeGen.push(`;\n`); + } + else if (isStatic) { + writeObjectProperty( + slotName, + argRange, + { + ...capabilitiesPresets.slotName, + completion: !!slotDir.arg, + }, + slotDir.arg?.loc ?? slotDir.loc, + ); + } + else { + codeGen.push(`[`); + writeInterpolation( + slotName, + argRange[0] + 1, + capabilitiesPresets.all, + '', + '', + (slotDir.loc as any).slot_name ?? ((slotDir.loc as any).slot_name = {}), + ); + codeGen.push(`]`); + writeInterpolationVarsExtraCompletion(); + } + codeGen.push(`(`); - if (isStatic && !prop.arg) { + const slotBlockVars: string[] = []; - let offset = prop.loc.start.offset; + if (slotDir?.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - if (prop.loc.source.startsWith('#')) - offset += '#'.length; - else if (prop.loc.source.startsWith('v-slot:')) - offset += 'v-slot:'.length; + const collectAst = createTsAst(slotDir, `(${slotDir.exp.content}) => {}`); + colletVars(ts, collectAst, slotBlockVars); - codeGen.push(varSlots); - codeGen.push(`['`); - codeGen.push([ - '', - 'template', - offset, - { completion: true }, - ]); - codeGen.push(`'];\n`); - } + codeGen.push([ + slotDir.exp.content, + 'template', + slotDir.exp.loc.start.offset, + capabilitiesPresets.all, + ]); + appendFormattingCode( + slotDir.exp.content, + slotDir.exp.loc.start.offset, + formatBrackets.round, + ); + } + + codeGen.push(`): any {\n`); + for (const blockCondition of blockConditions) { + codeGen.push(`if (!(${blockCondition})) return;\n`); + } + slotBlockVars.forEach(varName => { + localVars[varName] ??= 0; + localVars[varName]++; + }); + for (const childNode of nodes) { + visitNode(childNode, undefined); + } + slotBlockVars.forEach(varName => { + localVars[varName]--; + }); + codeGen.push(`},\n`); + + if (isStatic && slotDir && !slotDir.arg) { + + let offset = slotDir.loc.start.offset; + + if (slotDir.loc.source.startsWith('#')) + offset += '#'.length; + else if (slotDir.loc.source.startsWith('v-slot:')) + offset += 'v-slot:'.length; + + codeGen.push(`'`); + codeGen.push([ + '', + 'template', + offset, + { completion: true }, + ]); + codeGen.push(`'/* empty slot name completion */\n`); } } + + codeGen.push(`};\n`); } function writeDirectives(node: CompilerDOM.ElementNode) { for (const prop of node.props) { diff --git a/packages/vue-language-core/src/plugins/vue-tsx.ts b/packages/vue-language-core/src/plugins/vue-tsx.ts index 2ec37944dd..1ffbee5323 100644 --- a/packages/vue-language-core/src/plugins/vue-tsx.ts +++ b/packages/vue-language-core/src/plugins/vue-tsx.ts @@ -145,7 +145,6 @@ const plugin: VueLanguagePlugin = ({ modules, vueCompilerOptions, compilerOption return templateGen.generate( ts, - compilerOptions, vueCompilerOptions, _sfc.template?.content ?? '', _sfc.template?.lang ?? 'html', diff --git a/packages/vue-language-core/src/utils/localTypes.ts b/packages/vue-language-core/src/utils/localTypes.ts index e1bbc6ec14..4dba771a7d 100644 --- a/packages/vue-language-core/src/utils/localTypes.ts +++ b/packages/vue-language-core/src/utils/localTypes.ts @@ -64,8 +64,9 @@ export declare function makeOptional(t: T): { [K in keyof T]?: T[K] }; // TODO: make it stricter between class component type and functional component type export type ExtractComponentSlots = IsAny extends true ? Record - : T extends { ${slots}?: infer S } ? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : any } - : T extends { children?: infer S } ? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : any } + : T extends { ${slots}?: infer S } ? S + : T extends { children?: infer S } ? S + : T extends { [K in keyof PickNotAny]?: infer S } ? S : Record; export type FillingEventArg_ParametersLength any> = IsAny> extends true ? -1 : Parameters['length']; @@ -135,8 +136,6 @@ export type EventObject = { > > }; - -type IntrinsicElements = JSX.IntrinsicElements; `.trim(); }