Skip to content

Commit 3e08d24

Browse files
authored
fix(compiler-sfc): consistently escape type-only prop names (#8654)
close #8635 close #8910 close vitejs/vite-plugin-vue#184
1 parent 9e1b74b commit 3e08d24

File tree

5 files changed

+158
-8
lines changed

5 files changed

+158
-8
lines changed

packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({
4646
4747
const { foo } = __props
4848
49+
return { }
50+
}
51+
52+
})"
53+
`;
54+
55+
exports[`defineProps > should escape names w/ special symbols 1`] = `
56+
"import { defineComponent as _defineComponent } from 'vue'
57+
58+
export default /*#__PURE__*/_defineComponent({
59+
props: {
60+
\\"spa ce\\": { type: null, required: true },
61+
\\"exclamation!mark\\": { type: null, required: true },
62+
\\"double\\\\\\"quote\\": { type: null, required: true },
63+
\\"hash#tag\\": { type: null, required: true },
64+
\\"dollar$sign\\": { type: null, required: true },
65+
\\"percentage%sign\\": { type: null, required: true },
66+
\\"amper&sand\\": { type: null, required: true },
67+
\\"single'quote\\": { type: null, required: true },
68+
\\"round(brack)ets\\": { type: null, required: true },
69+
\\"aste*risk\\": { type: null, required: true },
70+
\\"pl+us\\": { type: null, required: true },
71+
\\"com,ma\\": { type: null, required: true },
72+
\\"do.t\\": { type: null, required: true },
73+
\\"sla/sh\\": { type: null, required: true },
74+
\\"co:lon\\": { type: null, required: true },
75+
\\"semi;colon\\": { type: null, required: true },
76+
\\"angle<brack>ets\\": { type: null, required: true },
77+
\\"equal=sign\\": { type: null, required: true },
78+
\\"question?mark\\": { type: null, required: true },
79+
\\"at@sign\\": { type: null, required: true },
80+
\\"square[brack]ets\\": { type: null, required: true },
81+
\\"back\\\\\\\\slash\\": { type: null, required: true },
82+
\\"ca^ret\\": { type: null, required: true },
83+
\\"back\`tick\\": { type: null, required: true },
84+
\\"curly{bra}ces\\": { type: null, required: true },
85+
\\"pi|pe\\": { type: null, required: true },
86+
\\"til~de\\": { type: null, required: true },
87+
\\"da-sh\\": { type: null, required: true }
88+
},
89+
setup(__props: any, { expose: __expose }) {
90+
__expose();
91+
92+
93+
4994
return { }
5095
}
5196

packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,4 +611,103 @@ const props = defineProps({ foo: String })
611611
}).toThrow(`cannot accept both type and non-type arguments`)
612612
})
613613
})
614+
615+
test('should escape names w/ special symbols', () => {
616+
const { content, bindings } = compile(`
617+
<script setup lang="ts">
618+
defineProps<{
619+
'spa ce': unknown
620+
'exclamation!mark': unknown
621+
'double"quote': unknown
622+
'hash#tag': unknown
623+
'dollar$sign': unknown
624+
'percentage%sign': unknown
625+
'amper&sand': unknown
626+
"single'quote": unknown
627+
'round(brack)ets': unknown
628+
'aste*risk': unknown
629+
'pl+us': unknown
630+
'com,ma': unknown
631+
'do.t': unknown
632+
'sla/sh': unknown
633+
'co:lon': unknown
634+
'semi;colon': unknown
635+
'angle<brack>ets': unknown
636+
'equal=sign': unknown
637+
'question?mark': unknown
638+
'at@sign': unknown
639+
'square[brack]ets': unknown
640+
'back\\\\slash': unknown
641+
'ca^ret': unknown
642+
'back\`tick': unknown
643+
'curly{bra}ces': unknown
644+
'pi|pe': unknown
645+
'til~de': unknown
646+
'da-sh': unknown
647+
}>()
648+
</script>`)
649+
assertCode(content)
650+
expect(content).toMatch(`"spa ce": { type: null, required: true }`)
651+
expect(content).toMatch(
652+
`"exclamation!mark": { type: null, required: true }`
653+
)
654+
expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
655+
expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
656+
expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
657+
expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
658+
expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
659+
expect(content).toMatch(`"single'quote": { type: null, required: true }`)
660+
expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
661+
expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
662+
expect(content).toMatch(`"pl+us": { type: null, required: true }`)
663+
expect(content).toMatch(`"com,ma": { type: null, required: true }`)
664+
expect(content).toMatch(`"do.t": { type: null, required: true }`)
665+
expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
666+
expect(content).toMatch(`"co:lon": { type: null, required: true }`)
667+
expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
668+
expect(content).toMatch(`"angle<brack>ets": { type: null, required: true }`)
669+
expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
670+
expect(content).toMatch(`"question?mark": { type: null, required: true }`)
671+
expect(content).toMatch(`"at@sign": { type: null, required: true }`)
672+
expect(content).toMatch(
673+
`"square[brack]ets": { type: null, required: true }`
674+
)
675+
expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
676+
expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
677+
expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
678+
expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
679+
expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
680+
expect(content).toMatch(`"til~de": { type: null, required: true }`)
681+
expect(content).toMatch(`"da-sh": { type: null, required: true }`)
682+
expect(bindings).toStrictEqual({
683+
'spa ce': BindingTypes.PROPS,
684+
'exclamation!mark': BindingTypes.PROPS,
685+
'double"quote': BindingTypes.PROPS,
686+
'hash#tag': BindingTypes.PROPS,
687+
dollar$sign: BindingTypes.PROPS,
688+
'percentage%sign': BindingTypes.PROPS,
689+
'amper&sand': BindingTypes.PROPS,
690+
"single'quote": BindingTypes.PROPS,
691+
'round(brack)ets': BindingTypes.PROPS,
692+
'aste*risk': BindingTypes.PROPS,
693+
'pl+us': BindingTypes.PROPS,
694+
'com,ma': BindingTypes.PROPS,
695+
'do.t': BindingTypes.PROPS,
696+
'sla/sh': BindingTypes.PROPS,
697+
'co:lon': BindingTypes.PROPS,
698+
'semi;colon': BindingTypes.PROPS,
699+
'angle<brack>ets': BindingTypes.PROPS,
700+
'equal=sign': BindingTypes.PROPS,
701+
'question?mark': BindingTypes.PROPS,
702+
'at@sign': BindingTypes.PROPS,
703+
'square[brack]ets': BindingTypes.PROPS,
704+
'back\\slash': BindingTypes.PROPS,
705+
'ca^ret': BindingTypes.PROPS,
706+
'back`tick': BindingTypes.PROPS,
707+
'curly{bra}ces': BindingTypes.PROPS,
708+
'pi|pe': BindingTypes.PROPS,
709+
'til~de': BindingTypes.PROPS,
710+
'da-sh': BindingTypes.PROPS
711+
})
712+
})
614713
})

packages/compiler-sfc/src/script/defineProps.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
isCallOf,
1818
unwrapTSNode,
1919
toRuntimeTypeString,
20-
getEscapedKey
20+
getEscapedPropName
2121
} from './utils'
2222
import { genModelProps } from './defineModel'
2323
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
@@ -135,7 +135,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
135135
const defaults: string[] = []
136136
for (const key in ctx.propsDestructuredBindings) {
137137
const d = genDestructuredDefaultValue(ctx, key)
138-
const finalKey = getEscapedKey(key)
138+
const finalKey = getEscapedPropName(key)
139139
if (d)
140140
defaults.push(
141141
`${finalKey}: ${d.valueString}${
@@ -251,7 +251,7 @@ function genRuntimePropFromType(
251251
}
252252
}
253253

254-
const finalKey = getEscapedKey(key)
254+
const finalKey = getEscapedPropName(key)
255255
if (!ctx.options.isProd) {
256256
return `${finalKey}: { ${concatStrings([
257257
`type: ${toRuntimeTypeString(type)}`,

packages/compiler-sfc/src/script/utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,14 @@ export const joinPaths = (path.posix || path).join
113113
* key may contain symbols
114114
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
115115
*/
116-
export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
116+
export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
117117

118-
export function getEscapedKey(key: string) {
119-
return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key
118+
export function getEscapedPropName(key: string) {
119+
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
120+
}
121+
122+
export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
123+
124+
export function getEscapedCssVarName(key: string) {
125+
return key.replace(cssVarNameEscapeSymbolsRE, s => `\\${s}`)
120126
}

packages/compiler-sfc/src/style/cssVars.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
BindingMetadata
99
} from '@vue/compiler-dom'
1010
import { SFCDescriptor } from '../parse'
11-
import { escapeSymbolsRE } from '../script/utils'
11+
import { getEscapedCssVarName } from '../script/utils'
1212
import { PluginCreator } from 'postcss'
1313
import hash from 'hash-sum'
1414

@@ -32,7 +32,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
3232
return hash(id + raw)
3333
} else {
3434
// escape ASCII Punctuation & Symbols
35-
return `${id}-${raw.replace(escapeSymbolsRE, s => `\\${s}`)}`
35+
return `${id}-${getEscapedCssVarName(raw)}`
3636
}
3737
}
3838

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy