Skip to content

Commit 5afe4ff

Browse files
committed
fix(compiler-core): warn when slot used on non-root template
Implements the warning from vue2 in vue3. fixes #11521
1 parent d7d0371 commit 5afe4ff

File tree

6 files changed

+99
-5
lines changed

6 files changed

+99
-5
lines changed

packages/compiler-core/__tests__/transform.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { transformElement } from '../src/transforms/transformElement'
2020
import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
2121
import { transformText } from '../src/transforms/transformText'
2222
import { PatchFlags } from '@vue/shared'
23+
import type { CompilerOptions } from '@vue/compiler-core'
2324

2425
describe('compiler: transform', () => {
2526
test('context state', () => {
@@ -378,4 +379,75 @@ describe('compiler: transform', () => {
378379
)
379380
})
380381
})
382+
383+
describe('errors', () => {
384+
function transformWithCodegen(
385+
template: string,
386+
options: CompilerOptions = {},
387+
) {
388+
const ast = baseParse(template)
389+
transform(ast, {
390+
nodeTransforms: [
391+
transformIf,
392+
transformFor,
393+
transformText,
394+
transformSlotOutlet,
395+
transformElement,
396+
],
397+
...options,
398+
})
399+
return ast
400+
}
401+
402+
test('warn when v-slot used on non-root level <template>', () => {
403+
const onError = vi.fn()
404+
405+
const ast = transformWithCodegen(
406+
`
407+
<template>
408+
<Bar>
409+
<template>
410+
<template #header> Header </template>
411+
</template>
412+
</Bar>
413+
</template>`,
414+
{ onError },
415+
)
416+
417+
expect(onError.mock.calls[0]).toMatchObject([
418+
{
419+
code: ErrorCodes.X_TEMPLATE_NOT_ROOT,
420+
loc: (ast.children[0] as any).children[0].children[0].children[0].loc,
421+
},
422+
])
423+
})
424+
425+
test('dont warn when v-slot used on root level <template>', () => {
426+
const onError = vi.fn()
427+
428+
transformWithCodegen(
429+
`<template>
430+
<bar>
431+
<template #header> Header </template>
432+
</bar>
433+
</template>`,
434+
{ onError },
435+
)
436+
437+
expect(onError.mock.calls).toEqual([])
438+
})
439+
440+
test('dont warn when v-slot used on root level <template> inside custom component', () => {
441+
const onError = vi.fn()
442+
443+
transformWithCodegen(
444+
`<template>
445+
<div is="vue:customComp"><template v-slot="slotProps"></template></div>
446+
</template>`,
447+
{ onError },
448+
)
449+
450+
expect(onError.mock.calls).toEqual([])
451+
})
452+
})
381453
})

packages/compiler-core/src/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export enum ErrorCodes {
9090
X_V_MODEL_ON_PROPS,
9191
X_INVALID_EXPRESSION,
9292
X_KEEP_ALIVE_INVALID_CHILDREN,
93+
X_TEMPLATE_NOT_ROOT,
9394

9495
// generic errors
9596
X_PREFIX_ID_NOT_SUPPORTED,
@@ -151,6 +152,9 @@ export const errorMessages: Record<ErrorCodes, string> = {
151152
'End bracket for dynamic directive argument was not found. ' +
152153
'Note that dynamic directive argument cannot contain spaces.',
153154
[ErrorCodes.X_MISSING_DIRECTIVE_NAME]: 'Legal directive name was expected.',
155+
[ErrorCodes.X_TEMPLATE_NOT_ROOT]:
156+
`<template v-slot> can only appear at the root level inside ` +
157+
`the receiving component`,
154158

155159
// transform errors
156160
[ErrorCodes.X_V_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,

packages/compiler-core/src/parser.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
634634
if (!inVPre) {
635635
if (tag === 'slot') {
636636
el.tagType = ElementTypes.SLOT
637-
} else if (isFragmentTemplate(el)) {
637+
} else if (isFragmentTemplate(el, stack[0])) {
638638
el.tagType = ElementTypes.TEMPLATE
639639
} else if (isComponent(el)) {
640640
el.tagType = ElementTypes.COMPONENT
@@ -698,7 +698,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
698698
currentOptions,
699699
) &&
700700
el.tag === 'template' &&
701-
!isFragmentTemplate(el)
701+
!isFragmentTemplate(el, stack[0])
702702
) {
703703
__DEV__ &&
704704
warnDeprecation(
@@ -749,7 +749,10 @@ function backTrack(index: number, c: number) {
749749
}
750750

751751
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
752-
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
752+
function isFragmentTemplate(
753+
{ tag, props, loc }: ElementNode,
754+
parent: ElementNode,
755+
): boolean {
753756
if (tag === 'template') {
754757
for (let i = 0; i < props.length; i++) {
755758
if (

packages/compiler-core/src/transform.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ import {
3030
isArray,
3131
isString,
3232
} from '@vue/shared'
33-
import { defaultOnError, defaultOnWarn } from './errors'
33+
import {
34+
ErrorCodes,
35+
createCompilerError,
36+
defaultOnError,
37+
defaultOnWarn,
38+
} from './errors'
3439
import {
3540
CREATE_COMMENT,
3641
FRAGMENT,
@@ -496,6 +501,15 @@ export function createStructuralDirectiveTransform(
496501
// structural directive transforms are not concerned with slots
497502
// as they are handled separately in vSlot.ts
498503
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
504+
if (
505+
context.parent &&
506+
'tagType' in context.parent &&
507+
context.parent.tagType !== ElementTypes.COMPONENT
508+
) {
509+
context.onError(
510+
createCompilerError(ErrorCodes.X_TEMPLATE_NOT_ROOT, node.loc),
511+
)
512+
}
499513
return
500514
}
501515
const exitFns = []

packages/compiler-dom/src/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function createDOMCompilerError(
2121
}
2222

2323
export enum DOMErrorCodes {
24-
X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
24+
X_V_HTML_NO_EXPRESSION = 54 /* ErrorCodes.__EXTEND_POINT__ */,
2525
X_V_HTML_WITH_CHILDREN,
2626
X_V_TEXT_NO_EXPRESSION,
2727
X_V_TEXT_WITH_CHILDREN,

packages/compiler-sfc/src/parse.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export function parse(
158158
errors.push(e)
159159
},
160160
})
161+
161162
ast.children.forEach(node => {
162163
if (node.type !== NodeTypes.ELEMENT) {
163164
return

0 commit comments

Comments
 (0)