fix(reactivity-transform): fix props access codegen for non-identifier prop names (#5436)

fix #5425
This commit is contained in:
edison 2022-05-13 10:38:46 +08:00 committed by GitHub
parent 0c07f12541
commit 242914d938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 10 deletions

View File

@ -24,7 +24,13 @@ import {
walkIdentifiers walkIdentifiers
} from '../babelUtils' } from '../babelUtils'
import { advancePositionWithClone, isSimpleIdentifier } from '../utils' import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
import { isGloballyWhitelisted, makeMap, hasOwn, isString } from '@vue/shared' import {
isGloballyWhitelisted,
makeMap,
hasOwn,
isString,
genPropsAccessExp
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { import {
Node, Node,
@ -185,17 +191,17 @@ export function processExpression(
} else if (type === BindingTypes.PROPS) { } else if (type === BindingTypes.PROPS) {
// use __props which is generated by compileScript so in ts mode // use __props which is generated by compileScript so in ts mode
// it gets correct type // it gets correct type
return `__props.${raw}` return genPropsAccessExp(raw)
} else if (type === BindingTypes.PROPS_ALIASED) { } else if (type === BindingTypes.PROPS_ALIASED) {
// prop with a different local alias (from defineProps() destructure) // prop with a different local alias (from defineProps() destructure)
return `__props.${bindingMetadata.__propsAliases![raw]}` return genPropsAccessExp(bindingMetadata.__propsAliases![raw])
} }
} else { } else {
if (type && type.startsWith('setup')) { if (type && type.startsWith('setup')) {
// setup bindings in non-inline mode // setup bindings in non-inline mode
return `$setup.${raw}` return `$setup.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) { } else if (type === BindingTypes.PROPS_ALIASED) {
return `$props.${bindingMetadata.__propsAliases![raw]}` return `$props['${bindingMetadata.__propsAliases![raw]}']`
} else if (type) { } else if (type) {
return `$${type}.${raw}` return `$${type}.${raw}`
} }

View File

@ -134,6 +134,25 @@ return () => {}
}" }"
`; `;
exports[`sfc props transform non-identifier prop names 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"
export default {
props: { 'foo.bar': Function },
setup(__props) {
let x = __props[\\"foo.bar\\"]
return (_ctx, _cache) => {
return _toDisplayString(__props[\\"foo.bar\\"])
}
}
}"
`;
exports[`sfc props transform rest spread 1`] = ` exports[`sfc props transform rest spread 1`] = `
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue' "import { createPropsRestProxy as _createPropsRestProxy } from 'vue'

View File

@ -127,6 +127,28 @@ describe('sfc props transform', () => {
}) })
}) })
// #5425
test('non-identifier prop names', () => {
const { content, bindings } = compile(`
<script setup>
const { 'foo.bar': fooBar } = defineProps({ 'foo.bar': Function })
let x = fooBar
</script>
<template>{{ fooBar }}</template>
`)
expect(content).toMatch(`x = __props["foo.bar"]`)
expect(content).toMatch(`toDisplayString(__props["foo.bar"])`)
assertCode(content)
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_LET,
'foo.bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED,
__propsAliases: {
fooBar: 'foo.bar'
}
})
})
test('rest spread', () => { test('rest spread', () => {
const { content, bindings } = compile(` const { content, bindings } = compile(`
<script setup> <script setup>

View File

@ -431,7 +431,11 @@ export function compileScript(
prop.key prop.key
) )
} }
const propKey = (prop.key as Identifier).name
const propKey = prop.key.type === 'StringLiteral'
? prop.key.value
: (prop.key as Identifier).name
if (prop.value.type === 'AssignmentPattern') { if (prop.value.type === 'AssignmentPattern') {
// default value { foo = 123 } // default value { foo = 123 }
const { left, right } = prop.value const { left, right } = prop.value

View File

@ -21,7 +21,7 @@ import {
walkFunctionParams walkFunctionParams
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { parse, ParserPlugin } from '@babel/parser' import { parse, ParserPlugin } from '@babel/parser'
import { hasOwn, isArray, isString } from '@vue/shared' import { hasOwn, isArray, isString, genPropsAccessExp } from '@vue/shared'
const CONVERT_SYMBOL = '$' const CONVERT_SYMBOL = '$'
const ESCAPE_SYMBOL = '$$' const ESCAPE_SYMBOL = '$$'
@ -489,17 +489,17 @@ export function transformAST(
if (isProp) { if (isProp) {
if (escapeScope) { if (escapeScope) {
// prop binding in $$() // prop binding in $$()
// { prop } -> { prop: __prop_prop } // { prop } -> { prop: __props_prop }
registerEscapedPropBinding(id) registerEscapedPropBinding(id)
s.appendLeft( s.appendLeft(
id.end! + offset, id.end! + offset,
`: __props_${propsLocalToPublicMap[id.name]}` `: __props_${propsLocalToPublicMap[id.name]}`
) )
} else { } else {
// { prop } -> { prop: __prop.prop } // { prop } -> { prop: __props.prop }
s.appendLeft( s.appendLeft(
id.end! + offset, id.end! + offset,
`: __props.${propsLocalToPublicMap[id.name]}` `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
) )
} }
} else { } else {
@ -522,7 +522,7 @@ export function transformAST(
s.overwrite( s.overwrite(
id.start! + offset, id.start! + offset,
id.end! + offset, id.end! + offset,
`__props.${propsLocalToPublicMap[id.name]}` genPropsAccessExp(propsLocalToPublicMap[id.name])
) )
} }
} else { } else {

View File

@ -171,3 +171,11 @@ export const getGlobalThis = (): any => {
: {}) : {})
) )
} }
const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/
export function genPropsAccessExp(name: string) {
return identRE.test(name)
? `__props.${name}`
: `__props[${JSON.stringify(name)}]`
}