]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): handle prop keys that need escaping (#7803)
author白雾三语 <32354856+baiwusanyu-c@users.noreply.github.com>
Fri, 12 May 2023 10:24:59 +0000 (18:24 +0800)
committerGitHub <noreply@github.com>
Fri, 12 May 2023 10:24:59 +0000 (18:24 +0800)
close #8291

packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
packages/compiler-sfc/src/script/defineProps.ts
packages/compiler-sfc/src/style/cssVars.ts

index 35926709cec67949e756f4b3d6c2b16ba4973137..721ef7eaa545768b93eb5d9603730e4d3e0c01c5 100644 (file)
@@ -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'
 
index b8912092afd41407c25ed91179ce53e7a1707888..a2941872fd2a1deb6348b0d8eae86278e81a158b 100644 (file)
@@ -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(`
@@ -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(
       `
index 2a0882284fbe6642af5e3e280319c84da8d62d71..38968527c9a4e9c6abef922b32507b85b42afb7c 100644 (file)
@@ -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` : ``
             }`
           )
       }
@@ -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',
@@ -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} }` : `{}`}`
   }
 }
 
@@ -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
+}
index f232d09695d9a9461850e0b8b25068a73b97157e..a3e2104999a2fe62bd5653d0d12527df5359d450 100644 (file)
@@ -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'
 
@@ -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}`
     )}`
   }