Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler-core): support shorthand syntax for v-bind #9451

Merged
merged 3 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 117 additions & 3 deletions packages/compiler-core/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,60 @@ describe('compiler: transform v-bind', () => {
})
})

test('no expression', () => {
const node = parseWithVBind(`<div v-bind:id />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
isStatic: true,
loc: {
start: {
line: 1,
column: 13,
offset: 12
},
end: {
line: 1,
column: 15,
offset: 14
}
}
},
value: {
content: `id`,
isStatic: false,
loc: {
start: {
line: 1,
column: 1,
offset: 0
},
end: {
line: 1,
column: 1,
offset: 0
}
}
}
})
})

test('no expression (shorthand)', () => {
const node = parseWithVBind(`<div :id />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
isStatic: true
},
value: {
content: `id`,
isStatic: false
}
})
})

test('dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
const props = (node.codegenNode as VNodeCall).props as CallExpression
Expand All @@ -98,9 +152,9 @@ describe('compiler: transform v-bind', () => {
})
})

test('should error if no expression', () => {
test('should error if empty expression', () => {
const onError = vi.fn()
const node = parseWithVBind(`<div v-bind:arg />`, { onError })
const node = parseWithVBind(`<div v-bind:arg="" />`, { onError })
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
Expand All @@ -111,7 +165,7 @@ describe('compiler: transform v-bind', () => {
},
end: {
line: 1,
column: 16
column: 19
}
}
})
Expand Down Expand Up @@ -142,6 +196,21 @@ describe('compiler: transform v-bind', () => {
})
})

test('.camel modifier w/ no expression', () => {
const node = parseWithVBind(`<div v-bind:foo-bar.camel />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
isStatic: true
},
value: {
content: `fooBar`,
isStatic: false
}
})
})

test('.camel modifier w/ dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
const props = (node.codegenNode as VNodeCall).props as CallExpression
Expand Down Expand Up @@ -219,6 +288,21 @@ describe('compiler: transform v-bind', () => {
})
})

test('.prop modifier w/ no expression', () => {
const node = parseWithVBind(`<div v-bind:fooBar.prop />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
isStatic: true
},
value: {
content: `fooBar`,
isStatic: false
}
})
})

test('.prop modifier w/ dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
const props = (node.codegenNode as VNodeCall).props as CallExpression
Expand Down Expand Up @@ -296,6 +380,21 @@ describe('compiler: transform v-bind', () => {
})
})

test('.prop modifier (shortband) w/ no expression', () => {
const node = parseWithVBind(`<div .fooBar />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
isStatic: true
},
value: {
content: `fooBar`,
isStatic: false
}
})
})

test('.attr modifier', () => {
const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
Expand All @@ -310,4 +409,19 @@ describe('compiler: transform v-bind', () => {
}
})
})

test('.attr modifier w/ no expression', () => {
const node = parseWithVBind(`<div v-bind:foo-bar.attr />`)
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `^foo-bar`,
isStatic: true
},
value: {
content: `fooBar`,
isStatic: false
}
})
})
})
16 changes: 15 additions & 1 deletion packages/compiler-core/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ import {
createObjectProperty,
createSimpleExpression,
ExpressionNode,
locStub,
NodeTypes
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression'

// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { exp, modifiers, loc } = dir
const { modifiers, loc } = dir
const arg = dir.arg!

if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
Expand Down Expand Up @@ -46,6 +48,18 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}
}

// :arg is replaced by :arg="arg"
let { exp } = dir
if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
const propName = camelize(arg.loc.source)
const simpleExpression = createSimpleExpression(propName, false, {
...locStub,
source: propName
})

exp = dir.exp = processExpression(simpleExpression, context)
}

if (
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ exports[`source map 1`] = `
exports[`template errors 1`] = `
[
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
[SyntaxError: v-bind is missing expression.],
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
]
`;
2 changes: 1 addition & 1 deletion packages/compiler-sfc/__tests__/compileTemplate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ test('source map', () => {
test('template errors', () => {
const result = compile({
filename: 'example.vue',
source: `<div :foo
source: `<div
:bar="a[" v-model="baz"/>`
})
expect(result.errors).toMatchSnapshot()
Expand Down