diff --git a/packages/language-core/lib/codegen/script/component.ts b/packages/language-core/lib/codegen/script/component.ts index 9c60e85014..6a5080eddc 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -40,11 +40,13 @@ export function* generateComponent( const { args } = options.scriptRanges.exportDefault; yield generateSfcBlockSection(options.sfc.script, args.start + 1, args.end - 1, codeFeatures.all); } - if (options.vueCompilerOptions.target >= 3.5 && scriptSetupRanges.templateRefs.length) { - yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`; - } - if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) { - yield `__typeEl: {} as __VLS_TemplateResult['rootEl'],${newLine}`; + if (options.vueCompilerOptions.target >= 3.5) { + if (scriptSetupRanges.templateRefs.length) { + yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`; + } + if (options.templateCodegen?.singleRootElTypes.length) { + yield `__typeEl: {} as __VLS_TemplateResult['rootEl'],${newLine}`; + } } yield `})`; } diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index c5abab54e1..31aab1dd92 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -130,10 +130,10 @@ export function createTemplateCodegenContext(options: Pick(), templateRefs, - singleRootElType: undefined as string | undefined, - singleRootNode: undefined as CompilerDOM.ElementNode | undefined, + singleRootElTypes: [] as string[], + singleRootNodes: new Set(), accessExternalVariable(name: string, offset?: number) { let arr = accessExternalVariables.get(name); if (!arr) { diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index ec8721835e..20f815d4e2 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -233,7 +233,8 @@ export function* generateComponent( } const [refName, offset] = yield* generateVScope(options, ctx, node, props); - const isRootNode = node === ctx.singleRootNode; + const tag = hyphenateTag(node.tag); + const isRootNode = ctx.singleRootNodes.has(node) && !options.vueCompilerOptions.fallthroughComponentTags.includes(tag); if (refName || isRootNode) { const varName = ctx.getInternalVariable(); @@ -252,7 +253,7 @@ export function* generateComponent( ctx.templateRefs.set(refName, [varName, offset!]); } if (isRootNode) { - ctx.singleRootElType = `NonNullable['$el']`; + ctx.singleRootElTypes.push(`NonNullable['$el']`); } } @@ -267,7 +268,7 @@ export function* generateComponent( options.vueCompilerOptions.fallthroughAttributes && ( node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs') - || node === ctx.singleRootNode + || ctx.singleRootNodes.has(node) ) ) { const varAttrs = ctx.getInternalVariable(); @@ -352,8 +353,8 @@ export function* generateElement( if (refName) { ctx.templateRefs.set(refName, [`__VLS_nativeElements['${node.tag}']`, offset!]); } - if (ctx.singleRootNode === node) { - ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`; + if (ctx.singleRootNodes.has(node)) { + ctx.singleRootElTypes.push(`typeof __VLS_nativeElements['${node.tag}']`); } const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; @@ -368,7 +369,7 @@ export function* generateElement( options.vueCompilerOptions.fallthroughAttributes && ( node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs') - || node === ctx.singleRootNode + || ctx.singleRootNodes.has(node) ) ) { ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`); diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index feac4f497a..b881e6a17d 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -113,12 +113,17 @@ function* generateRefs(ctx: TemplateCodegenContext): Generator { } function* generateRootEl(ctx: TemplateCodegenContext): Generator { - if (ctx.singleRootElType) { - yield `var $el!: ${ctx.singleRootElType}${endOfLine}`; + yield `var $el!: `; + if (ctx.singleRootElTypes.length && !ctx.singleRootNodes.has(null)) { + yield newLine; + for (const type of ctx.singleRootElTypes) { + yield `| ${type}${newLine}`; + } } else { - yield `var $el!: any${endOfLine}`; + yield `any`; } + yield endOfLine; } function* generatePreResolveComponents(options: TemplateCodegenOptions): Generator { diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index ea61cbb83c..6d7940c756 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -1,6 +1,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; import { endOfLine, newLine } from '../common'; +import { hyphenateTag } from '../../utils/shared'; import type { TemplateCodegenContext } from './context'; import { generateComponent, generateElement } from './element'; import type { TemplateCodegenOptions } from './index'; @@ -47,18 +48,16 @@ export function* generateTemplateChild( } } - const shouldInheritRootNodeAttrs = options.inheritAttrs; - const cur = node as CompilerDOM.ElementNode | CompilerDOM.IfNode | CompilerDOM.ForNode; if (cur.codegenNode?.type === CompilerDOM.NodeTypes.JS_CACHE_EXPRESSION) { cur.codegenNode = cur.codegenNode.value as any; } if (node.type === CompilerDOM.NodeTypes.ROOT) { - let prev: CompilerDOM.TemplateChildNode | undefined; - if (shouldInheritRootNodeAttrs && node.children.length === 1 && node.children[0].type === CompilerDOM.NodeTypes.ELEMENT) { - ctx.singleRootNode = node.children[0]; + if (options.inheritAttrs) { + ctx.singleRootNodes = new Set(collectSingleRootNodes(options, node.children)); } + let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); prev = childNode; @@ -129,6 +128,36 @@ export function* generateTemplateChild( } } +function* collectSingleRootNodes( + options: TemplateCodegenOptions, + children: CompilerDOM.TemplateChildNode[] +): Generator { + if (children.length !== 1) { + // used to determine whether the component is always has a single root + if (children.length > 1) { + yield null; + } + return; + } + + const child = children[0]; + if (child.type === CompilerDOM.NodeTypes.IF) { + for (const branch of child.branches) { + yield* collectSingleRootNodes(options, branch.children); + } + return; + } + else if (child.type !== CompilerDOM.NodeTypes.ELEMENT) { + return; + } + yield child; + + const tag = hyphenateTag(child.tag); + if (options.vueCompilerOptions.fallthroughComponentTags.includes(tag)) { + yield* collectSingleRootNodes(options, child.children); + } +} + // TODO: track https://github.com/vuejs/vue-next/issues/3498 export function getVForNode(node: CompilerDOM.ElementNode) { const forDirective = node.props.find( diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 8d1803d89b..3a14181187 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -31,6 +31,7 @@ export interface VueCompilerOptions { strictTemplates: boolean; skipTemplateCodegen: boolean; fallthroughAttributes: boolean; + fallthroughComponentTags: string[]; dataAttributes: string[]; htmlAttributes: string[]; optionsWrapper: [string, string] | []; diff --git a/packages/language-core/lib/utils/ts.ts b/packages/language-core/lib/utils/ts.ts index 292825783f..b7692d1a35 100644 --- a/packages/language-core/lib/utils/ts.ts +++ b/packages/language-core/lib/utils/ts.ts @@ -4,6 +4,7 @@ import { posix as path } from 'path-browserify'; import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types'; import { getAllExtensions } from '../languagePlugin'; import { generateGlobalTypes } from '../codegen/globalTypes'; +import { hyphenateTag } from './shared'; export type ParsedCommandLine = ts.ParsedCommandLine & { vueOptions: VueCompilerOptions; @@ -234,6 +235,12 @@ export function resolveVueCompilerOptions(vueOptions: Partial hyphenateTag(tag)), dataAttributes: vueOptions.dataAttributes ?? [], htmlAttributes: vueOptions.htmlAttributes ?? ['aria-*'], optionsWrapper: vueOptions.optionsWrapper ?? ( diff --git a/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/basic.vue b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/basic.vue new file mode 100644 index 0000000000..3574ef2165 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/basic.vue @@ -0,0 +1,17 @@ + + + diff --git a/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/child.vue b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/child.vue new file mode 100644 index 0000000000..caf86cdbbe --- /dev/null +++ b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/child.vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/main.vue b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/main.vue new file mode 100644 index 0000000000..0954792cf7 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/fallthroughComponentTag/main.vue @@ -0,0 +1,7 @@ + + +