test(compiler): test v-slot transform
This commit is contained in:
parent
96749e0178
commit
1c410205de
@ -1234,6 +1234,50 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-slot shorthand', () => {
|
||||||
|
const ast = parse('<Comp #a="{ b }" />')
|
||||||
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
|
expect(directive).toStrictEqual({
|
||||||
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
name: 'slot',
|
||||||
|
arg: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'a',
|
||||||
|
isStatic: true,
|
||||||
|
loc: {
|
||||||
|
source: 'a',
|
||||||
|
start: {
|
||||||
|
column: 8,
|
||||||
|
line: 1,
|
||||||
|
offset: 7
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
column: 9,
|
||||||
|
line: 1,
|
||||||
|
offset: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifiers: [],
|
||||||
|
exp: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: '{ b }',
|
||||||
|
isStatic: false,
|
||||||
|
loc: {
|
||||||
|
start: { offset: 10, line: 1, column: 11 },
|
||||||
|
end: { offset: 15, line: 1, column: 16 },
|
||||||
|
source: '{ b }'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loc: {
|
||||||
|
start: { offset: 6, line: 1, column: 7 },
|
||||||
|
end: { offset: 16, line: 1, column: 17 },
|
||||||
|
source: '#a="{ b }"'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('end tags are case-insensitive.', () => {
|
test('end tags are case-insensitive.', () => {
|
||||||
const ast = parse('<div>hello</DIV>after')
|
const ast = parse('<div>hello</DIV>after')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots dynamically named slots 1`] = `
|
||||||
|
"const { resolveComponent, createVNode, toString } = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
const _ctx = this
|
||||||
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return createVNode(_component_Comp, 0, {
|
||||||
|
[_ctx.one]: ({ foo }) => [
|
||||||
|
toString(foo),
|
||||||
|
toString(_ctx.bar)
|
||||||
|
],
|
||||||
|
[_ctx.two]: ({ bar }) => [
|
||||||
|
toString(_ctx.foo),
|
||||||
|
toString(bar)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots explicit default slot 1`] = `
|
||||||
|
"const { resolveComponent, createVNode, toString } = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
const _ctx = this
|
||||||
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return createVNode(_component_Comp, 0, {
|
||||||
|
default: ({ foo }) => [
|
||||||
|
toString(foo),
|
||||||
|
toString(_ctx.bar)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots implicit default slot 1`] = `
|
||||||
|
"const { resolveComponent, createVNode } = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
const _ctx = this
|
||||||
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return createVNode(_component_Comp, 0, {
|
||||||
|
default: () => [
|
||||||
|
createVNode(\\"div\\")
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots named slots 1`] = `
|
||||||
|
"const { resolveComponent, createVNode, toString } = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
const _ctx = this
|
||||||
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return createVNode(_component_Comp, 0, {
|
||||||
|
one: ({ foo }) => [
|
||||||
|
toString(foo),
|
||||||
|
toString(_ctx.bar)
|
||||||
|
],
|
||||||
|
two: ({ bar }) => [
|
||||||
|
toString(_ctx.foo),
|
||||||
|
toString(bar)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots nested slots scoping 1`] = `
|
||||||
|
"const { resolveComponent, createVNode, toString } = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
const _ctx = this
|
||||||
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
const _component_Inner = resolveComponent(\\"Inner\\")
|
||||||
|
|
||||||
|
return createVNode(_component_Comp, 0, {
|
||||||
|
default: ({ foo }) => [
|
||||||
|
createVNode(_component_Inner, 0, {
|
||||||
|
default: ({ bar }) => [
|
||||||
|
toString(foo),
|
||||||
|
toString(bar),
|
||||||
|
toString(_ctx.baz)
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
toString(foo),
|
||||||
|
toString(_ctx.bar),
|
||||||
|
toString(_ctx.baz)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
`;
|
@ -3,7 +3,8 @@ import {
|
|||||||
parse,
|
parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
ErrorCodes
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
@ -321,4 +322,27 @@ describe('compiler: transform <slot> outlets', () => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`error on unexpected custom directive on <slot>`, () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<slot v-foo />`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.indexOf('v-foo')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
loc: {
|
||||||
|
source: `v-foo`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 5,
|
||||||
|
line: 1,
|
||||||
|
column: index + 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { CompilerOptions, parse, transform, generate } from '../../src'
|
import {
|
||||||
|
CompilerOptions,
|
||||||
|
parse,
|
||||||
|
transform,
|
||||||
|
generate,
|
||||||
|
ElementNode,
|
||||||
|
NodeTypes,
|
||||||
|
ErrorCodes
|
||||||
|
} from '../../src'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
@ -20,22 +28,411 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
|||||||
},
|
},
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
return ast
|
return {
|
||||||
|
root: ast,
|
||||||
|
slots: (ast.children[0] as ElementNode).codegenNode!.arguments[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSlotMatcher(obj: Record<string, any>) {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: Object.keys(obj).map(key => {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_PROPERTY,
|
||||||
|
key: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
isStatic: !/^\[/.test(key),
|
||||||
|
content: key.replace(/^\[|\]$/g, '')
|
||||||
|
},
|
||||||
|
value: obj[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler: transform component slots', () => {
|
describe('compiler: transform component slots', () => {
|
||||||
test('generate slot', () => {
|
test('implicit default slot', () => {
|
||||||
const ast = parseWithSlots(
|
const { root, slots } = parseWithSlots(`<Comp><div/></Comp>`, {
|
||||||
`
|
prefixIdentifiers: true
|
||||||
<Comp>
|
})
|
||||||
<Comp v-slot="{ dur }">
|
expect(slots).toMatchObject(
|
||||||
hello {{ dur }}
|
createSlotMatcher({
|
||||||
</Comp>
|
default: {
|
||||||
</Comp>
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
`,
|
params: undefined,
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `div`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('explicit default slot', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||||
{ prefixIdentifiers: true }
|
{ prefixIdentifiers: true }
|
||||||
)
|
)
|
||||||
const { code } = generate(ast, { prefixIdentifiers: true })
|
expect(slots).toMatchObject(
|
||||||
console.log(code)
|
createSlotMatcher({
|
||||||
|
default: {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ foo }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named slots', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template v-slot:one="{ foo }">
|
||||||
|
{{ foo }}{{ bar }}
|
||||||
|
</template>
|
||||||
|
<template #two="{ bar }">
|
||||||
|
{{ foo }}{{ bar }}
|
||||||
|
</template>
|
||||||
|
</Comp>`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
one: {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ foo }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ bar }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamically named slots', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template v-slot:[one]="{ foo }">
|
||||||
|
{{ foo }}{{ bar }}
|
||||||
|
</template>
|
||||||
|
<template #[two]="{ bar }">
|
||||||
|
{{ foo }}{{ bar }}
|
||||||
|
</template>
|
||||||
|
</Comp>`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
'[_ctx.one]': {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ foo }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'[_ctx.two]': {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ bar }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested slots scoping', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template #default="{ foo }">
|
||||||
|
<Inner v-slot="{ bar }">
|
||||||
|
{{ foo }}{{ bar }}{{ baz }}
|
||||||
|
</Inner>
|
||||||
|
{{ foo }}{{ bar }}{{ baz }}
|
||||||
|
</template>
|
||||||
|
</Comp>`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
default: {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ foo }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
codegenNode: {
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
arguments: [
|
||||||
|
`_component_Inner`,
|
||||||
|
`0`,
|
||||||
|
createSlotMatcher({
|
||||||
|
default: {
|
||||||
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `{ bar }`,
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `bar`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.baz`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// test scope
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.baz`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on extraneous children w/ named slots', () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.indexOf('bar')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
||||||
|
loc: {
|
||||||
|
source: `bar`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 3,
|
||||||
|
line: 1,
|
||||||
|
column: index + 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on duplicated slot names', () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<Comp><template #foo></template><template #foo></template></Comp>`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.lastIndexOf('#foo')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_DUPLICATE_SLOT_NAMES,
|
||||||
|
loc: {
|
||||||
|
source: `#foo`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 4,
|
||||||
|
line: 1,
|
||||||
|
column: index + 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on invalid mixed slot usage', () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<Comp v-slot="foo"><template #foo></template></Comp>`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.lastIndexOf('#foo')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_MIXED_SLOT_USAGE,
|
||||||
|
loc: {
|
||||||
|
source: `#foo`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 4,
|
||||||
|
line: 1,
|
||||||
|
column: index + 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on v-slot usage on plain elements', () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<div v-slot/>`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.indexOf('v-slot')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_MISPLACED_V_SLOT,
|
||||||
|
loc: {
|
||||||
|
source: `v-slot`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 6,
|
||||||
|
line: 1,
|
||||||
|
column: index + 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on named slot on component', () => {
|
||||||
|
const onError = jest.fn()
|
||||||
|
const source = `<Comp v-slot:foo>foo</Comp>`
|
||||||
|
parseWithSlots(source, { onError })
|
||||||
|
const index = source.indexOf('v-slot')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_NAMED_SLOT_ON_COMPONENT,
|
||||||
|
loc: {
|
||||||
|
source: `v-slot:foo`,
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 10,
|
||||||
|
line: 1,
|
||||||
|
column: index + 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -73,6 +73,7 @@ export const enum ErrorCodes {
|
|||||||
X_MIXED_SLOT_USAGE,
|
X_MIXED_SLOT_USAGE,
|
||||||
X_DUPLICATE_SLOT_NAMES,
|
X_DUPLICATE_SLOT_NAMES,
|
||||||
X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
X_EXTRANEOUS_NON_SLOT_CHILDREN,
|
||||||
|
X_MISPLACED_V_SLOT,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
X_PREFIX_ID_NOT_SUPPORTED,
|
X_PREFIX_ID_NOT_SUPPORTED,
|
||||||
@ -155,6 +156,8 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN]:
|
[ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN]:
|
||||||
`Extraneous children found when component has explicit slots. ` +
|
`Extraneous children found when component has explicit slots. ` +
|
||||||
`These children will be ignored.`,
|
`These children will be ignored.`,
|
||||||
|
[ErrorCodes.X_MISPLACED_V_SLOT]: `v-slot can only be used on components or <template> tags.`,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||||
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
|
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
|
||||||
|
@ -384,6 +384,11 @@ function parseTag(
|
|||||||
const props = []
|
const props = []
|
||||||
const ns = context.options.getNamespace(tag, parent)
|
const ns = context.options.getNamespace(tag, parent)
|
||||||
|
|
||||||
|
let tagType = ElementTypes.ELEMENT
|
||||||
|
if (tag === 'slot') tagType = ElementTypes.SLOT
|
||||||
|
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
|
||||||
|
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
|
||||||
|
|
||||||
advanceBy(context, match[0].length)
|
advanceBy(context, match[0].length)
|
||||||
advanceSpaces(context)
|
advanceSpaces(context)
|
||||||
|
|
||||||
@ -427,12 +432,6 @@ function parseTag(
|
|||||||
advanceBy(context, isSelfClosing ? 2 : 1)
|
advanceBy(context, isSelfClosing ? 2 : 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let tagType = ElementTypes.ELEMENT
|
|
||||||
|
|
||||||
if (tag === 'slot') tagType = ElementTypes.SLOT
|
|
||||||
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
|
|
||||||
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
ns,
|
ns,
|
||||||
|
@ -39,7 +39,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
node.tagType === ElementTypes.COMPONENT
|
node.tagType === ElementTypes.COMPONENT
|
||||||
) {
|
) {
|
||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
const hasProps = node.props.length > 0
|
let hasProps = node.props.length > 0
|
||||||
const hasChildren = node.children.length > 0
|
const hasChildren = node.children.length > 0
|
||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let runtimeDirectives: DirectiveNode[] | undefined
|
||||||
let componentIdentifier: string | undefined
|
let componentIdentifier: string | undefined
|
||||||
@ -58,9 +58,18 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
]
|
]
|
||||||
// props
|
// props
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
const { props, directives } = buildProps(node.props, node.loc, context)
|
const { props, directives } = buildProps(
|
||||||
args.push(props)
|
node.props,
|
||||||
|
node.loc,
|
||||||
|
context,
|
||||||
|
isComponent
|
||||||
|
)
|
||||||
runtimeDirectives = directives
|
runtimeDirectives = directives
|
||||||
|
if (!props) {
|
||||||
|
hasProps = false
|
||||||
|
} else {
|
||||||
|
args.push(props)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// children
|
// children
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
@ -104,9 +113,10 @@ type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
|||||||
export function buildProps(
|
export function buildProps(
|
||||||
props: ElementNode['props'],
|
props: ElementNode['props'],
|
||||||
elementLoc: SourceLocation,
|
elementLoc: SourceLocation,
|
||||||
context: TransformContext
|
context: TransformContext,
|
||||||
|
isComponent: boolean = false
|
||||||
): {
|
): {
|
||||||
props: PropsExpression
|
props: PropsExpression | undefined
|
||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
} {
|
} {
|
||||||
let isStatic = true
|
let isStatic = true
|
||||||
@ -141,6 +151,11 @@ export function buildProps(
|
|||||||
|
|
||||||
// skip v-slot - it is handled by its dedicated transform.
|
// skip v-slot - it is handled by its dedicated transform.
|
||||||
if (name === 'slot') {
|
if (name === 'slot') {
|
||||||
|
if (!isComponent) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_MISPLACED_V_SLOT, loc)
|
||||||
|
)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +212,7 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let propsExpression: PropsExpression
|
let propsExpression: PropsExpression | undefined = undefined
|
||||||
|
|
||||||
// has v-bind="object" or v-on="object", wrap with mergeProps
|
// has v-bind="object" or v-on="object", wrap with mergeProps
|
||||||
if (mergeArgs.length) {
|
if (mergeArgs.length) {
|
||||||
@ -216,7 +231,7 @@ export function buildProps(
|
|||||||
// single v-bind with nothing else - no need for a mergeProps call
|
// single v-bind with nothing else - no need for a mergeProps call
|
||||||
propsExpression = mergeArgs[0]
|
propsExpression = mergeArgs[0]
|
||||||
}
|
}
|
||||||
} else {
|
} else if (properties.length) {
|
||||||
propsExpression = createObjectExpression(
|
propsExpression = createObjectExpression(
|
||||||
dedupeProperties(properties),
|
dedupeProperties(properties),
|
||||||
elementLoc
|
elementLoc
|
||||||
@ -224,7 +239,7 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hoist the object if it's fully static
|
// hoist the object if it's fully static
|
||||||
if (isStatic) {
|
if (isStatic && propsExpression) {
|
||||||
propsExpression = context.hoist(propsExpression)
|
propsExpression = context.hoist(propsExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
|
|||||||
nameIndex > -1
|
nameIndex > -1
|
||||||
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
||||||
: props
|
: props
|
||||||
const hasProps = propsWithoutName.length
|
let hasProps = propsWithoutName.length > 0
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
const { props: propsExpression, directives } = buildProps(
|
const { props: propsExpression, directives } = buildProps(
|
||||||
propsWithoutName,
|
propsWithoutName,
|
||||||
@ -79,7 +79,11 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
slotArgs.push(propsExpression)
|
if (propsExpression) {
|
||||||
|
slotArgs.push(propsExpression)
|
||||||
|
} else {
|
||||||
|
hasProps = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
|
Loading…
Reference in New Issue
Block a user