Skip to content

Commit 565741a

Browse files
authored
refactor(compiler): add separate transform for vbind shorthand (#13438)
close #13169 close #13170 close #11321 close #12298 close #12828 use tests from #13170 and #12298 and #12828
1 parent 47e628d commit 565741a

File tree

13 files changed

+125
-48
lines changed

13 files changed

+125
-48
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
helperNameMap,
1818
} from '../../src/runtimeHelpers'
1919
import { transformExpression } from '../../src/transforms/transformExpression'
20+
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
2021

2122
function parseWithVBind(
2223
template: string,
@@ -25,6 +26,7 @@ function parseWithVBind(
2526
const ast = parse(template)
2627
transform(ast, {
2728
nodeTransforms: [
29+
transformVBindShorthand,
2830
...(options.prefixIdentifiers ? [transformExpression] : []),
2931
transformElement,
3032
],

packages/compiler-core/__tests__/transforms/vFor.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { type CompilerOptions, generate } from '../../src'
2121
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
2222
import { PatchFlags } from '@vue/shared'
2323
import { createObjectMatcher } from '../testUtils'
24+
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
2425

2526
export function parseWithForTransform(
2627
template: string,
@@ -32,6 +33,7 @@ export function parseWithForTransform(
3233
const ast = parse(template, options)
3334
transform(ast, {
3435
nodeTransforms: [
36+
transformVBindShorthand,
3537
transformIf,
3638
transformFor,
3739
...(options.prefixIdentifiers ? [transformExpression] : []),

packages/compiler-core/__tests__/transforms/vIf.spec.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ import {
1717
type VNodeCall,
1818
} from '../../src/ast'
1919
import { ErrorCodes } from '../../src/errors'
20-
import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
20+
import {
21+
type CompilerOptions,
22+
TO_HANDLERS,
23+
generate,
24+
transformVBindShorthand,
25+
} from '../../src'
2126
import {
2227
CREATE_COMMENT,
2328
FRAGMENT,
@@ -35,7 +40,12 @@ function parseWithIfTransform(
3540
) {
3641
const ast = parse(template, options)
3742
transform(ast, {
38-
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
43+
nodeTransforms: [
44+
transformVBindShorthand,
45+
transformIf,
46+
transformSlotOutlet,
47+
transformElement,
48+
],
3949
...options,
4050
})
4151
if (!options.onError) {
@@ -209,6 +219,16 @@ describe('compiler: v-if', () => {
209219
content: `_ctx.ok`,
210220
})
211221
})
222+
223+
//#11321
224+
test('v-if + :key shorthand', () => {
225+
const { node } = parseWithIfTransform(`<div v-if="ok" :key></div>`)
226+
expect(node.type).toBe(NodeTypes.IF)
227+
expect(node.branches[0].userKey).toMatchObject({
228+
arg: { content: 'key' },
229+
exp: { content: 'key' },
230+
})
231+
})
212232
})
213233

214234
describe('errors', () => {

packages/compiler-core/src/compile.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { transformModel } from './transforms/vModel'
2222
import { transformFilter } from './compat/transformFilter'
2323
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
2424
import { transformMemo } from './transforms/vMemo'
25+
import { transformVBindShorthand } from './transforms/transformVBindShorthand'
2526

2627
export type TransformPreset = [
2728
NodeTransform[],
@@ -33,6 +34,7 @@ export function getBaseTransformPreset(
3334
): TransformPreset {
3435
return [
3536
[
37+
transformVBindShorthand,
3638
transformOnce,
3739
transformIf,
3840
transformMemo,

packages/compiler-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export {
6666
buildDirectiveArgs,
6767
type PropsExpression,
6868
} from './transforms/transformElement'
69+
export { transformVBindShorthand } from './transforms/transformVBindShorthand'
6970
export { processSlotOutlet } from './transforms/transformSlotOutlet'
7071
export { getConstantType } from './transforms/cacheStatic'
7172
export { generateCodeFrame } from '@vue/shared'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { camelize } from '@vue/shared'
2+
import {
3+
NodeTypes,
4+
type SimpleExpressionNode,
5+
createSimpleExpression,
6+
} from '../ast'
7+
import type { NodeTransform } from '../transform'
8+
import { ErrorCodes, createCompilerError } from '../errors'
9+
import { validFirstIdentCharRE } from '../utils'
10+
11+
export const transformVBindShorthand: NodeTransform = (node, context) => {
12+
if (node.type === NodeTypes.ELEMENT) {
13+
for (const prop of node.props) {
14+
// same-name shorthand - :arg is expanded to :arg="arg"
15+
if (
16+
prop.type === NodeTypes.DIRECTIVE &&
17+
prop.name === 'bind' &&
18+
!prop.exp
19+
) {
20+
const arg = prop.arg!
21+
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
22+
// only simple expression is allowed for same-name shorthand
23+
context.onError(
24+
createCompilerError(
25+
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
26+
arg.loc,
27+
),
28+
)
29+
prop.exp = createSimpleExpression('', true, arg.loc)
30+
} else {
31+
const propName = camelize((arg as SimpleExpressionNode).content)
32+
if (
33+
validFirstIdentCharRE.test(propName[0]) ||
34+
// allow hyphen first char for https://github.com/vuejs/language-tools/pull/3424
35+
propName[0] === '-'
36+
) {
37+
prop.exp = createSimpleExpression(propName, false, arg.loc)
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}

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

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
import type { DirectiveTransform, TransformContext } from '../transform'
1+
import type { DirectiveTransform } from '../transform'
22
import {
3-
type DirectiveNode,
43
type ExpressionNode,
54
NodeTypes,
6-
type SimpleExpressionNode,
75
createObjectProperty,
86
createSimpleExpression,
97
} from '../ast'
108
import { ErrorCodes, createCompilerError } from '../errors'
119
import { camelize } from '@vue/shared'
1210
import { CAMELIZE } from '../runtimeHelpers'
13-
import { processExpression } from './transformExpression'
1411

1512
// v-bind without arg is handled directly in ./transformElement.ts due to its affecting
1613
// codegen for the entire props object. This transform here is only for v-bind
@@ -40,27 +37,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
4037
}
4138
}
4239

43-
// same-name shorthand - :arg is expanded to :arg="arg"
44-
if (!exp) {
45-
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
46-
// only simple expression is allowed for same-name shorthand
47-
context.onError(
48-
createCompilerError(
49-
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
50-
arg.loc,
51-
),
52-
)
53-
return {
54-
props: [
55-
createObjectProperty(arg, createSimpleExpression('', true, loc)),
56-
],
57-
}
58-
}
59-
60-
transformBindShorthand(dir, context)
61-
exp = dir.exp!
62-
}
63-
6440
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
6541
arg.children.unshift(`(`)
6642
arg.children.push(`) || ""`)
@@ -92,20 +68,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
9268
}
9369

9470
return {
95-
props: [createObjectProperty(arg, exp)],
96-
}
97-
}
98-
99-
export const transformBindShorthand = (
100-
dir: DirectiveNode,
101-
context: TransformContext,
102-
): void => {
103-
const arg = dir.arg!
104-
105-
const propName = camelize((arg as SimpleExpressionNode).content)
106-
dir.exp = createSimpleExpression(propName, false, arg.loc)
107-
if (!__BROWSER__) {
108-
dir.exp = processExpression(dir.exp, context)
71+
props: [createObjectProperty(arg, exp!)],
10972
}
11073
}
11174

packages/compiler-core/src/transforms/vFor.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import {
4848
import { processExpression } from './transformExpression'
4949
import { validateBrowserExpression } from '../validateExpression'
5050
import { PatchFlags } from '@vue/shared'
51-
import { transformBindShorthand } from './vBind'
5251

5352
export const transformFor: NodeTransform = createStructuralDirectiveTransform(
5453
'for',
@@ -64,10 +63,6 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
6463
const memo = findDir(node, 'memo')
6564
const keyProp = findProp(node, `key`, false, true)
6665
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
67-
if (isDirKey && !keyProp.exp) {
68-
// resolve :key shorthand #10882
69-
transformBindShorthand(keyProp, context)
70-
}
7166
let keyExp =
7267
keyProp &&
7368
(keyProp.type === NodeTypes.ATTRIBUTE

packages/compiler-core/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ enum MemberExpLexState {
7474
inString,
7575
}
7676

77-
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
77+
export const validFirstIdentCharRE: RegExp = /[A-Za-z_$\xA0-\uFFFF]/
7878
const validIdentCharRE = /[\.\?\w$\xA0-\uFFFF]/
7979
const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g
8080

packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ return function render(_ctx, _cache) {
4848
}"
4949
`;
5050

51+
exports[`compiler: transform v-model > input with v-bind shorthand type after v-model should use dynamic model 1`] = `
52+
"const _Vue = Vue
53+
54+
return function render(_ctx, _cache) {
55+
with (_ctx) {
56+
const { vModelDynamic: _vModelDynamic, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
57+
58+
return _withDirectives((_openBlock(), _createElementBlock("input", {
59+
"onUpdate:modelValue": $event => ((model) = $event)
60+
}, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
61+
[_vModelDynamic, model]
62+
])
63+
}
64+
}"
65+
`;
66+
5167
exports[`compiler: transform v-model > modifiers > .lazy 1`] = `
5268
"const _Vue = Vue
5369

0 commit comments

Comments
 (0)