Skip to content

Commit 26399aa

Browse files
authoredNov 2, 2023
feat(compiler-core): support v-bind shorthand for key and value with the same name (#9451)
1 parent 48b47a1 commit 26399aa

File tree

4 files changed

+133
-6
lines changed

4 files changed

+133
-6
lines changed
 

‎packages/compiler-core/__tests__/transforms/vBind.spec.ts

+117-3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,60 @@ describe('compiler: transform v-bind', () => {
7272
})
7373
})
7474

75+
test('no expression', () => {
76+
const node = parseWithVBind(`<div v-bind:id />`)
77+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
78+
expect(props.properties[0]).toMatchObject({
79+
key: {
80+
content: `id`,
81+
isStatic: true,
82+
loc: {
83+
start: {
84+
line: 1,
85+
column: 13,
86+
offset: 12
87+
},
88+
end: {
89+
line: 1,
90+
column: 15,
91+
offset: 14
92+
}
93+
}
94+
},
95+
value: {
96+
content: `id`,
97+
isStatic: false,
98+
loc: {
99+
start: {
100+
line: 1,
101+
column: 1,
102+
offset: 0
103+
},
104+
end: {
105+
line: 1,
106+
column: 1,
107+
offset: 0
108+
}
109+
}
110+
}
111+
})
112+
})
113+
114+
test('no expression (shorthand)', () => {
115+
const node = parseWithVBind(`<div :id />`)
116+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
117+
expect(props.properties[0]).toMatchObject({
118+
key: {
119+
content: `id`,
120+
isStatic: true
121+
},
122+
value: {
123+
content: `id`,
124+
isStatic: false
125+
}
126+
})
127+
})
128+
75129
test('dynamic arg', () => {
76130
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
77131
const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -98,9 +152,9 @@ describe('compiler: transform v-bind', () => {
98152
})
99153
})
100154

101-
test('should error if no expression', () => {
155+
test('should error if empty expression', () => {
102156
const onError = vi.fn()
103-
const node = parseWithVBind(`<div v-bind:arg />`, { onError })
157+
const node = parseWithVBind(`<div v-bind:arg="" />`, { onError })
104158
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
105159
expect(onError.mock.calls[0][0]).toMatchObject({
106160
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
@@ -111,7 +165,7 @@ describe('compiler: transform v-bind', () => {
111165
},
112166
end: {
113167
line: 1,
114-
column: 16
168+
column: 19
115169
}
116170
}
117171
})
@@ -142,6 +196,21 @@ describe('compiler: transform v-bind', () => {
142196
})
143197
})
144198

199+
test('.camel modifier w/ no expression', () => {
200+
const node = parseWithVBind(`<div v-bind:foo-bar.camel />`)
201+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
202+
expect(props.properties[0]).toMatchObject({
203+
key: {
204+
content: `fooBar`,
205+
isStatic: true
206+
},
207+
value: {
208+
content: `fooBar`,
209+
isStatic: false
210+
}
211+
})
212+
})
213+
145214
test('.camel modifier w/ dynamic arg', () => {
146215
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
147216
const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -219,6 +288,21 @@ describe('compiler: transform v-bind', () => {
219288
})
220289
})
221290

291+
test('.prop modifier w/ no expression', () => {
292+
const node = parseWithVBind(`<div v-bind:fooBar.prop />`)
293+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
294+
expect(props.properties[0]).toMatchObject({
295+
key: {
296+
content: `.fooBar`,
297+
isStatic: true
298+
},
299+
value: {
300+
content: `fooBar`,
301+
isStatic: false
302+
}
303+
})
304+
})
305+
222306
test('.prop modifier w/ dynamic arg', () => {
223307
const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
224308
const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -296,6 +380,21 @@ describe('compiler: transform v-bind', () => {
296380
})
297381
})
298382

383+
test('.prop modifier (shortband) w/ no expression', () => {
384+
const node = parseWithVBind(`<div .fooBar />`)
385+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
386+
expect(props.properties[0]).toMatchObject({
387+
key: {
388+
content: `.fooBar`,
389+
isStatic: true
390+
},
391+
value: {
392+
content: `fooBar`,
393+
isStatic: false
394+
}
395+
})
396+
})
397+
299398
test('.attr modifier', () => {
300399
const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
301400
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
@@ -310,4 +409,19 @@ describe('compiler: transform v-bind', () => {
310409
}
311410
})
312411
})
412+
413+
test('.attr modifier w/ no expression', () => {
414+
const node = parseWithVBind(`<div v-bind:foo-bar.attr />`)
415+
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
416+
expect(props.properties[0]).toMatchObject({
417+
key: {
418+
content: `^foo-bar`,
419+
isStatic: true
420+
},
421+
value: {
422+
content: `fooBar`,
423+
isStatic: false
424+
}
425+
})
426+
})
313427
})

‎packages/compiler-core/src/transforms/vBind.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ import {
33
createObjectProperty,
44
createSimpleExpression,
55
ExpressionNode,
6+
locStub,
67
NodeTypes
78
} from '../ast'
89
import { createCompilerError, ErrorCodes } from '../errors'
910
import { camelize } from '@vue/shared'
1011
import { CAMELIZE } from '../runtimeHelpers'
12+
import { processExpression } from './transformExpression'
1113

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

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

51+
// :arg is replaced by :arg="arg"
52+
let { exp } = dir
53+
if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
54+
const propName = camelize(arg.loc.source)
55+
const simpleExpression = createSimpleExpression(propName, false, {
56+
...locStub,
57+
source: propName
58+
})
59+
60+
exp = dir.exp = processExpression(simpleExpression, context)
61+
}
62+
4963
if (
5064
!exp ||
5165
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())

‎packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap

-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ exports[`source map 1`] = `
7979
exports[`template errors 1`] = `
8080
[
8181
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
82-
[SyntaxError: v-bind is missing expression.],
8382
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
8483
]
8584
`;

‎packages/compiler-sfc/__tests__/compileTemplate.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ test('source map', () => {
124124
test('template errors', () => {
125125
const result = compile({
126126
filename: 'example.vue',
127-
source: `<div :foo
127+
source: `<div
128128
:bar="a[" v-model="baz"/>`
129129
})
130130
expect(result.errors).toMatchSnapshot()

0 commit comments

Comments
 (0)
Please sign in to comment.