Skip to content

Commit

Permalink
fix(compiler-sfc): handle prop keys that need escaping (#7803)
Browse files Browse the repository at this point in the history
close #8291
  • Loading branch information
baiwusanyu-c committed May 12, 2023
1 parent aa1e77d commit 690ef29
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 6 deletions.
Expand Up @@ -97,6 +97,44 @@ return () => {}
}"
`;

exports[`sfc reactive props destructure > default values w/ runtime declaration & key is string 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'
export default {
props: _mergeDefaults(['foo', 'foo:bar'], {
foo: 1,
\\"foo:bar\\": 'foo-bar'
}),
setup(__props) {
return () => {}
}
}"
`;

exports[`sfc reactive props destructure > default values w/ type declaration & key is string 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: {
foo: { type: Number, required: true, default: 1 },
bar: { type: Number, required: true, default: 2 },
\\"foo:bar\\": { type: String, required: true, default: 'foo-bar' },
\\"onUpdate:modelValue\\": { type: Function, required: true }
},
setup(__props: any) {
return () => {}
}
})"
`;

exports[`sfc reactive props destructure > default values w/ type declaration 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
Expand Down
Expand Up @@ -106,6 +106,28 @@ describe('sfc reactive props destructure', () => {
})`)
assertCode(content)
})
test('default values w/ runtime declaration & key is string', () => {
const { content, bindings } = compile(`
<script setup>
const { foo = 1, 'foo:bar': fooBar = 'foo-bar' } = defineProps(['foo', 'foo:bar'])
</script>
`)
expect(bindings).toStrictEqual({
__propsAliases: {
fooBar: 'foo:bar'
},
foo: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED
})

expect(content).toMatch(`
props: _mergeDefaults(['foo', 'foo:bar'], {
foo: 1,
"foo:bar": 'foo-bar'
}),`)
assertCode(content)
})

test('default values w/ type declaration', () => {
const { content } = compile(`
Expand All @@ -123,6 +145,37 @@ describe('sfc reactive props destructure', () => {
assertCode(content)
})

test('default values w/ type declaration & key is string', () => {
const { content, bindings } = compile(`
<script setup lang="ts">
const { foo = 1, bar = 2, 'foo:bar': fooBar = 'foo-bar' } = defineProps<{
"foo": number // double-quoted string
'bar': number // single-quoted string
'foo:bar': string // single-quoted string containing symbols
"onUpdate:modelValue": (val: number) => void // double-quoted string containing symbols
}>()
</script>
`)
expect(bindings).toStrictEqual({
__propsAliases: {
fooBar: 'foo:bar'
},
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED,
'onUpdate:modelValue': BindingTypes.PROPS
})
expect(content).toMatch(`
props: {
foo: { type: Number, required: true, default: 1 },
bar: { type: Number, required: true, default: 2 },
"foo:bar": { type: String, required: true, default: 'foo-bar' },
"onUpdate:modelValue": { type: Function, required: true }
},`)
assertCode(content)
})

test('default values w/ type declaration, prod mode', () => {
const { content } = compile(
`
Expand Down
23 changes: 18 additions & 5 deletions packages/compiler-sfc/src/script/defineProps.ts
Expand Up @@ -133,10 +133,11 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
const defaults: string[] = []
for (const key in ctx.propsDestructuredBindings) {
const d = genDestructuredDefaultValue(ctx, key)
const finalKey = getEscapedKey(key)
if (d)
defaults.push(
`${key}: ${d.valueString}${
d.needSkipFactory ? `, __skip_${key}: true` : ``
`${finalKey}: ${d.valueString}${
d.needSkipFactory ? `, __skip_${finalKey}: true` : ``
}`
)
}
Expand Down Expand Up @@ -248,8 +249,9 @@ function genRuntimePropFromType(
}
}

const finalKey = getEscapedKey(key)
if (!ctx.options.isProd) {
return `${key}: { ${concatStrings([
return `${finalKey}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`,
`required: ${required}`,
skipCheck && 'skipCheck: true',
Expand All @@ -265,13 +267,13 @@ function genRuntimePropFromType(
// #4783 for boolean, should keep the type
// #7111 for function, if default value exists or it's not static, should keep it
// in production
return `${key}: { ${concatStrings([
return `${finalKey}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`,
defaultString
])} }`
} else {
// production: checks are useless
return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
}
}

Expand Down Expand Up @@ -362,3 +364,14 @@ function inferValueType(node: Node): string | undefined {
return 'Function'
}
}

/**
* key may contain symbols
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
*/
export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
function getEscapedKey(key: string) {
return escapeSymbolsRE.test(key)
? JSON.stringify(key)
: key
}
3 changes: 2 additions & 1 deletion packages/compiler-sfc/src/style/cssVars.ts
Expand Up @@ -8,6 +8,7 @@ import {
BindingMetadata
} from '@vue/compiler-dom'
import { SFCDescriptor } from '../parse'
import { escapeSymbolsRE } from '../script/defineProps'
import { PluginCreator } from 'postcss'
import hash from 'hash-sum'

Expand All @@ -32,7 +33,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
} else {
// escape ASCII Punctuation & Symbols
return `${id}-${raw.replace(
/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g,
escapeSymbolsRE,
s => `\\${s}`
)}`
}
Expand Down

0 comments on commit 690ef29

Please sign in to comment.