Skip to content

Commit bc100c5

Browse files
committedJun 9, 2021
fix(compiler-core): improve member expression check
fix #3910
1 parent 9a5bdb1 commit bc100c5

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed
 

‎packages/compiler-core/__tests__/utils.spec.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,25 @@ test('isMemberExpression', () => {
8181
expect(isMemberExpression('obj[arr[ret[bar]]]')).toBe(true)
8282
expect(isMemberExpression('obj[arr[ret[bar]]].baz')).toBe(true)
8383
expect(isMemberExpression('obj[1 + 1]')).toBe(true)
84-
// should warning
84+
expect(isMemberExpression(`obj[x[0]]`)).toBe(true)
85+
expect(isMemberExpression('obj[1][2]')).toBe(true)
86+
expect(isMemberExpression('obj[1][2].foo[3].bar.baz')).toBe(true)
87+
expect(isMemberExpression(`a[b[c.d]][0]`)).toBe(true)
88+
89+
// strings
90+
expect(isMemberExpression(`a['foo' + bar[baz]["qux"]]`)).toBe(true)
91+
92+
// multiline whitespaces
93+
expect(isMemberExpression('obj \n .foo \n [bar \n + baz]')).toBe(true)
94+
expect(isMemberExpression(`\n model\n.\nfoo \n`)).toBe(true)
95+
96+
// should fail
97+
expect(isMemberExpression('a \n b')).toBe(false)
8598
expect(isMemberExpression('obj[foo')).toBe(false)
8699
expect(isMemberExpression('objfoo]')).toBe(false)
87100
expect(isMemberExpression('obj[arr[0]')).toBe(false)
88101
expect(isMemberExpression('obj[arr0]]')).toBe(false)
102+
expect(isMemberExpression('123[a]')).toBe(false)
103+
expect(isMemberExpression('a + b')).toBe(false)
104+
expect(isMemberExpression('foo()')).toBe(false)
Has a conversation. Original line has a conversation.
89105
})

‎packages/compiler-core/src/transforms/vModel.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
4141
bindingType &&
4242
bindingType !== BindingTypes.SETUP_CONST
4343

44-
if (!isMemberExpression(expString) && !maybeRef) {
44+
if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
4545
context.onError(
4646
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
4747
)

‎packages/compiler-core/src/utils.ts

+60-7
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,67 @@ const nonIdentifierRE = /^\d|[^\$\w]/
5656
export const isSimpleIdentifier = (name: string): boolean =>
5757
!nonIdentifierRE.test(name)
5858

59-
const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/
59+
const enum MemberExpLexState {
60+
inMemberExp,
61+
inBrackets,
62+
inString
63+
}
64+
65+
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
66+
const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/
67+
const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g
68+
69+
/**
70+
* Simple lexer to check if an expression is a member expression. This is
71+
* lax and only checks validity at the root level (i.e. does not validate exps
72+
* inside square brackets), but it's ok since these are only used on template
73+
* expressions and false positives are invalid expressions in the first place.
74+
*/
6075
export const isMemberExpression = (path: string): boolean => {
61-
if (!path) return false
62-
const matched = memberExpRE.exec(path.trim())
63-
if (!matched) return false
64-
if (!matched[1]) return true
65-
if (!/[\[\]]/.test(matched[1])) return true
66-
return isMemberExpression(matched[1].trim())
76+
// remove whitespaces around . or [ first
77+
path = path.trim().replace(whitespaceRE, s => s.trim())
78+
79+
let state = MemberExpLexState.inMemberExp
80+
let prevState = MemberExpLexState.inMemberExp
81+
let currentOpenBracketCount = 0
82+
let currentStringType: "'" | '"' | '`' | null = null
83+
84+
for (let i = 0; i < path.length; i++) {
85+
const char = path.charAt(i)
86+
switch (state) {
87+
case MemberExpLexState.inMemberExp:
88+
if (char === '[') {
89+
prevState = state
90+
state = MemberExpLexState.inBrackets
91+
currentOpenBracketCount++
92+
} else if (
93+
!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)
94+
) {
95+
return false
96+
}
97+
break
98+
case MemberExpLexState.inBrackets:
99+
if (char === `'` || char === `"` || char === '`') {
100+
prevState = state
101+
state = MemberExpLexState.inString
102+
currentStringType = char
103+
} else if (char === `[`) {
104+
currentOpenBracketCount++
105+
} else if (char === `]`) {
106+
if (!--currentOpenBracketCount) {
107+
state = prevState
108+
}
109+
}
110+
break
111+
case MemberExpLexState.inString:
112+
if (char === currentStringType) {
113+
state = prevState
114+
currentStringType = null
115+
}
116+
break
117+
}
118+
}
119+
return !currentOpenBracketCount
67120
}
68121

69122
export function getInnerRange(

0 commit comments

Comments
 (0)
Please sign in to comment.