Skip to content

Commit 242914d

Browse files
authored
fix(reactivity-transform): fix props access codegen for non-identifier prop names (#5436)
fix #5425
1 parent 0c07f12 commit 242914d

File tree

6 files changed

+69
-10
lines changed

6 files changed

+69
-10
lines changed

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import {
2424
walkIdentifiers
2525
} from '../babelUtils'
2626
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
27-
import { isGloballyWhitelisted, makeMap, hasOwn, isString } from '@vue/shared'
27+
import {
28+
isGloballyWhitelisted,
29+
makeMap,
30+
hasOwn,
31+
isString,
32+
genPropsAccessExp
33+
} from '@vue/shared'
2834
import { createCompilerError, ErrorCodes } from '../errors'
2935
import {
3036
Node,
@@ -185,17 +191,17 @@ export function processExpression(
185191
} else if (type === BindingTypes.PROPS) {
186192
// use __props which is generated by compileScript so in ts mode
187193
// it gets correct type
188-
return `__props.${raw}`
194+
return genPropsAccessExp(raw)
189195
} else if (type === BindingTypes.PROPS_ALIASED) {
190196
// prop with a different local alias (from defineProps() destructure)
191-
return `__props.${bindingMetadata.__propsAliases![raw]}`
197+
return genPropsAccessExp(bindingMetadata.__propsAliases![raw])
192198
}
193199
} else {
194200
if (type && type.startsWith('setup')) {
195201
// setup bindings in non-inline mode
196202
return `$setup.${raw}`
197203
} else if (type === BindingTypes.PROPS_ALIASED) {
198-
return `$props.${bindingMetadata.__propsAliases![raw]}`
204+
return `$props['${bindingMetadata.__propsAliases![raw]}']`
199205
} else if (type) {
200206
return `$${type}.${raw}`
201207
}

packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap

+19
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,25 @@ return () => {}
134134
}"
135135
`;
136136

137+
exports[`sfc props transform non-identifier prop names 1`] = `
138+
"import { toDisplayString as _toDisplayString } from \\"vue\\"
139+
140+
141+
export default {
142+
props: { 'foo.bar': Function },
143+
setup(__props) {
144+
145+
146+
let x = __props[\\"foo.bar\\"]
147+
148+
return (_ctx, _cache) => {
149+
return _toDisplayString(__props[\\"foo.bar\\"])
150+
}
151+
}
152+
153+
}"
154+
`;
155+
137156
exports[`sfc props transform rest spread 1`] = `
138157
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
139158

packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts

+22
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ describe('sfc props transform', () => {
127127
})
128128
})
129129

130+
// #5425
131+
test('non-identifier prop names', () => {
132+
const { content, bindings } = compile(`
133+
<script setup>
134+
const { 'foo.bar': fooBar } = defineProps({ 'foo.bar': Function })
135+
let x = fooBar
136+
</script>
137+
<template>{{ fooBar }}</template>
138+
`)
139+
expect(content).toMatch(`x = __props["foo.bar"]`)
140+
expect(content).toMatch(`toDisplayString(__props["foo.bar"])`)
141+
assertCode(content)
142+
expect(bindings).toStrictEqual({
143+
x: BindingTypes.SETUP_LET,
144+
'foo.bar': BindingTypes.PROPS,
145+
fooBar: BindingTypes.PROPS_ALIASED,
146+
__propsAliases: {
147+
fooBar: 'foo.bar'
148+
}
149+
})
150+
})
151+
130152
test('rest spread', () => {
131153
const { content, bindings } = compile(`
132154
<script setup>

packages/compiler-sfc/src/compileScript.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,11 @@ export function compileScript(
431431
prop.key
432432
)
433433
}
434-
const propKey = (prop.key as Identifier).name
434+
435+
const propKey = prop.key.type === 'StringLiteral'
436+
? prop.key.value
437+
: (prop.key as Identifier).name
438+
435439
if (prop.value.type === 'AssignmentPattern') {
436440
// default value { foo = 123 }
437441
const { left, right } = prop.value

packages/reactivity-transform/src/reactivityTransform.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
walkFunctionParams
2222
} from '@vue/compiler-core'
2323
import { parse, ParserPlugin } from '@babel/parser'
24-
import { hasOwn, isArray, isString } from '@vue/shared'
24+
import { hasOwn, isArray, isString, genPropsAccessExp } from '@vue/shared'
2525

2626
const CONVERT_SYMBOL = '$'
2727
const ESCAPE_SYMBOL = '$$'
@@ -489,17 +489,17 @@ export function transformAST(
489489
if (isProp) {
490490
if (escapeScope) {
491491
// prop binding in $$()
492-
// { prop } -> { prop: __prop_prop }
492+
// { prop } -> { prop: __props_prop }
493493
registerEscapedPropBinding(id)
494494
s.appendLeft(
495495
id.end! + offset,
496496
`: __props_${propsLocalToPublicMap[id.name]}`
497497
)
498498
} else {
499-
// { prop } -> { prop: __prop.prop }
499+
// { prop } -> { prop: __props.prop }
500500
s.appendLeft(
501501
id.end! + offset,
502-
`: __props.${propsLocalToPublicMap[id.name]}`
502+
`: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
503503
)
504504
}
505505
} else {
@@ -522,7 +522,7 @@ export function transformAST(
522522
s.overwrite(
523523
id.start! + offset,
524524
id.end! + offset,
525-
`__props.${propsLocalToPublicMap[id.name]}`
525+
genPropsAccessExp(propsLocalToPublicMap[id.name])
526526
)
527527
}
528528
} else {

packages/shared/src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,11 @@ export const getGlobalThis = (): any => {
171171
: {})
172172
)
173173
}
174+
175+
const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/
176+
177+
export function genPropsAccessExp(name: string) {
178+
return identRE.test(name)
179+
? `__props.${name}`
180+
: `__props[${JSON.stringify(name)}]`
181+
}

0 commit comments

Comments
 (0)