]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): consistently escape type-only prop names (#8654)
authorauvred <61150013+auvred@users.noreply.github.com>
Fri, 10 Nov 2023 08:23:47 +0000 (11:23 +0300)
committerGitHub <noreply@github.com>
Fri, 10 Nov 2023 08:23:47 +0000 (16:23 +0800)
close #8635
close #8910
close vitejs/vite-plugin-vue#184

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

index 5b0f96c50147515369c4897693c2566059cfc0c4..158b5c8f5553f1636a4161eed66711e38592c789 100644 (file)
@@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({
 
       const { foo } = __props
       
+return {  }
+}
+
+})"
+`;
+
+exports[`defineProps > should escape names w/ special symbols 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  props: {
+    \\"spa ce\\": { type: null, required: true },
+    \\"exclamation!mark\\": { type: null, required: true },
+    \\"double\\\\\\"quote\\": { type: null, required: true },
+    \\"hash#tag\\": { type: null, required: true },
+    \\"dollar$sign\\": { type: null, required: true },
+    \\"percentage%sign\\": { type: null, required: true },
+    \\"amper&sand\\": { type: null, required: true },
+    \\"single'quote\\": { type: null, required: true },
+    \\"round(brack)ets\\": { type: null, required: true },
+    \\"aste*risk\\": { type: null, required: true },
+    \\"pl+us\\": { type: null, required: true },
+    \\"com,ma\\": { type: null, required: true },
+    \\"do.t\\": { type: null, required: true },
+    \\"sla/sh\\": { type: null, required: true },
+    \\"co:lon\\": { type: null, required: true },
+    \\"semi;colon\\": { type: null, required: true },
+    \\"angle<brack>ets\\": { type: null, required: true },
+    \\"equal=sign\\": { type: null, required: true },
+    \\"question?mark\\": { type: null, required: true },
+    \\"at@sign\\": { type: null, required: true },
+    \\"square[brack]ets\\": { type: null, required: true },
+    \\"back\\\\\\\\slash\\": { type: null, required: true },
+    \\"ca^ret\\": { type: null, required: true },
+    \\"back\`tick\\": { type: null, required: true },
+    \\"curly{bra}ces\\": { type: null, required: true },
+    \\"pi|pe\\": { type: null, required: true },
+    \\"til~de\\": { type: null, required: true },
+    \\"da-sh\\": { type: null, required: true }
+  },
+  setup(__props: any, { expose: __expose }) {
+  __expose();
+
+    
+    
 return {  }
 }
 
index 674d697a5978db96c893c64825116f5d785654b6..23d6a806f0d574b8c913b2e6735b8739c137c81a 100644 (file)
@@ -611,4 +611,103 @@ const props = defineProps({ foo: String })
       }).toThrow(`cannot accept both type and non-type arguments`)
     })
   })
+
+  test('should escape names w/ special symbols', () => {
+    const { content, bindings } = compile(`
+    <script setup lang="ts">
+    defineProps<{
+      'spa ce': unknown
+      'exclamation!mark': unknown
+      'double"quote': unknown
+      'hash#tag': unknown
+      'dollar$sign': unknown
+      'percentage%sign': unknown
+      'amper&sand': unknown
+      "single'quote": unknown
+      'round(brack)ets': unknown
+      'aste*risk': unknown
+      'pl+us': unknown
+      'com,ma': unknown
+      'do.t': unknown
+      'sla/sh': unknown
+      'co:lon': unknown
+      'semi;colon': unknown
+      'angle<brack>ets': unknown
+      'equal=sign': unknown
+      'question?mark': unknown
+      'at@sign': unknown
+      'square[brack]ets': unknown
+      'back\\\\slash': unknown
+      'ca^ret': unknown
+      'back\`tick': unknown
+      'curly{bra}ces': unknown
+      'pi|pe': unknown
+      'til~de': unknown
+      'da-sh': unknown
+    }>()
+    </script>`)
+    assertCode(content)
+    expect(content).toMatch(`"spa ce": { type: null, required: true }`)
+    expect(content).toMatch(
+      `"exclamation!mark": { type: null, required: true }`
+    )
+    expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
+    expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
+    expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
+    expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
+    expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
+    expect(content).toMatch(`"single'quote": { type: null, required: true }`)
+    expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
+    expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
+    expect(content).toMatch(`"pl+us": { type: null, required: true }`)
+    expect(content).toMatch(`"com,ma": { type: null, required: true }`)
+    expect(content).toMatch(`"do.t": { type: null, required: true }`)
+    expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
+    expect(content).toMatch(`"co:lon": { type: null, required: true }`)
+    expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
+    expect(content).toMatch(`"angle<brack>ets": { type: null, required: true }`)
+    expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
+    expect(content).toMatch(`"question?mark": { type: null, required: true }`)
+    expect(content).toMatch(`"at@sign": { type: null, required: true }`)
+    expect(content).toMatch(
+      `"square[brack]ets": { type: null, required: true }`
+    )
+    expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
+    expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
+    expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
+    expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
+    expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
+    expect(content).toMatch(`"til~de": { type: null, required: true }`)
+    expect(content).toMatch(`"da-sh": { type: null, required: true }`)
+    expect(bindings).toStrictEqual({
+      'spa ce': BindingTypes.PROPS,
+      'exclamation!mark': BindingTypes.PROPS,
+      'double"quote': BindingTypes.PROPS,
+      'hash#tag': BindingTypes.PROPS,
+      dollar$sign: BindingTypes.PROPS,
+      'percentage%sign': BindingTypes.PROPS,
+      'amper&sand': BindingTypes.PROPS,
+      "single'quote": BindingTypes.PROPS,
+      'round(brack)ets': BindingTypes.PROPS,
+      'aste*risk': BindingTypes.PROPS,
+      'pl+us': BindingTypes.PROPS,
+      'com,ma': BindingTypes.PROPS,
+      'do.t': BindingTypes.PROPS,
+      'sla/sh': BindingTypes.PROPS,
+      'co:lon': BindingTypes.PROPS,
+      'semi;colon': BindingTypes.PROPS,
+      'angle<brack>ets': BindingTypes.PROPS,
+      'equal=sign': BindingTypes.PROPS,
+      'question?mark': BindingTypes.PROPS,
+      'at@sign': BindingTypes.PROPS,
+      'square[brack]ets': BindingTypes.PROPS,
+      'back\\slash': BindingTypes.PROPS,
+      'ca^ret': BindingTypes.PROPS,
+      'back`tick': BindingTypes.PROPS,
+      'curly{bra}ces': BindingTypes.PROPS,
+      'pi|pe': BindingTypes.PROPS,
+      'til~de': BindingTypes.PROPS,
+      'da-sh': BindingTypes.PROPS
+    })
+  })
 })
index 5004e314da1bd4b4967bcdf5be44d3107f680b6d..9de15b92b768bcce39ca903dd0f91452736c924e 100644 (file)
@@ -17,7 +17,7 @@ import {
   isCallOf,
   unwrapTSNode,
   toRuntimeTypeString,
-  getEscapedKey
+  getEscapedPropName
 } from './utils'
 import { genModelProps } from './defineModel'
 import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
@@ -135,7 +135,7 @@ 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)
+        const finalKey = getEscapedPropName(key)
         if (d)
           defaults.push(
             `${finalKey}: ${d.valueString}${
@@ -251,7 +251,7 @@ function genRuntimePropFromType(
     }
   }
 
-  const finalKey = getEscapedKey(key)
+  const finalKey = getEscapedPropName(key)
   if (!ctx.options.isProd) {
     return `${finalKey}: { ${concatStrings([
       `type: ${toRuntimeTypeString(type)}`,
index 42c4718e3a87c1201a0fcb5bd6cf1ad6aa8298ca..8afadf63ee457d0ea9ba5d33ce9fa7ad73e93864 100644 (file)
@@ -113,8 +113,14 @@ export const joinPaths = (path.posix || path).join
  * key may contain symbols
  * e.g. onUpdate:modelValue -> "onUpdate:modelValue"
  */
-export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
+export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
 
-export function getEscapedKey(key: string) {
-  return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key
+export function getEscapedPropName(key: string) {
+  return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
+}
+
+export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
+
+export function getEscapedCssVarName(key: string) {
+  return key.replace(cssVarNameEscapeSymbolsRE, s => `\\${s}`)
 }
index 2380959b81993473c20243df943eca2c6c30ef1b..9fe727bc5dcbed537316c2922d8744491c3a4045 100644 (file)
@@ -8,7 +8,7 @@ import {
   BindingMetadata
 } from '@vue/compiler-dom'
 import { SFCDescriptor } from '../parse'
-import { escapeSymbolsRE } from '../script/utils'
+import { getEscapedCssVarName } from '../script/utils'
 import { PluginCreator } from 'postcss'
 import hash from 'hash-sum'
 
@@ -32,7 +32,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
     return hash(id + raw)
   } else {
     // escape ASCII Punctuation & Symbols
-    return `${id}-${raw.replace(escapeSymbolsRE, s => `\\${s}`)}`
+    return `${id}-${getEscapedCssVarName(raw)}`
   }
 }