mirror of https://github.com/vuejs/core.git
parent
9a5bdb15df
commit
bc100c5c48
|
@ -81,9 +81,25 @@ test('isMemberExpression', () => {
|
||||||
expect(isMemberExpression('obj[arr[ret[bar]]]')).toBe(true)
|
expect(isMemberExpression('obj[arr[ret[bar]]]')).toBe(true)
|
||||||
expect(isMemberExpression('obj[arr[ret[bar]]].baz')).toBe(true)
|
expect(isMemberExpression('obj[arr[ret[bar]]].baz')).toBe(true)
|
||||||
expect(isMemberExpression('obj[1 + 1]')).toBe(true)
|
expect(isMemberExpression('obj[1 + 1]')).toBe(true)
|
||||||
// should warning
|
expect(isMemberExpression(`obj[x[0]]`)).toBe(true)
|
||||||
|
expect(isMemberExpression('obj[1][2]')).toBe(true)
|
||||||
|
expect(isMemberExpression('obj[1][2].foo[3].bar.baz')).toBe(true)
|
||||||
|
expect(isMemberExpression(`a[b[c.d]][0]`)).toBe(true)
|
||||||
|
|
||||||
|
// strings
|
||||||
|
expect(isMemberExpression(`a['foo' + bar[baz]["qux"]]`)).toBe(true)
|
||||||
|
|
||||||
|
// multiline whitespaces
|
||||||
|
expect(isMemberExpression('obj \n .foo \n [bar \n + baz]')).toBe(true)
|
||||||
|
expect(isMemberExpression(`\n model\n.\nfoo \n`)).toBe(true)
|
||||||
|
|
||||||
|
// should fail
|
||||||
|
expect(isMemberExpression('a \n b')).toBe(false)
|
||||||
expect(isMemberExpression('obj[foo')).toBe(false)
|
expect(isMemberExpression('obj[foo')).toBe(false)
|
||||||
expect(isMemberExpression('objfoo]')).toBe(false)
|
expect(isMemberExpression('objfoo]')).toBe(false)
|
||||||
expect(isMemberExpression('obj[arr[0]')).toBe(false)
|
expect(isMemberExpression('obj[arr[0]')).toBe(false)
|
||||||
expect(isMemberExpression('obj[arr0]]')).toBe(false)
|
expect(isMemberExpression('obj[arr0]]')).toBe(false)
|
||||||
|
expect(isMemberExpression('123[a]')).toBe(false)
|
||||||
|
expect(isMemberExpression('a + b')).toBe(false)
|
||||||
|
expect(isMemberExpression('foo()')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
bindingType &&
|
bindingType &&
|
||||||
bindingType !== BindingTypes.SETUP_CONST
|
bindingType !== BindingTypes.SETUP_CONST
|
||||||
|
|
||||||
if (!isMemberExpression(expString) && !maybeRef) {
|
if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
|
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,14 +56,67 @@ const nonIdentifierRE = /^\d|[^\$\w]/
|
||||||
export const isSimpleIdentifier = (name: string): boolean =>
|
export const isSimpleIdentifier = (name: string): boolean =>
|
||||||
!nonIdentifierRE.test(name)
|
!nonIdentifierRE.test(name)
|
||||||
|
|
||||||
const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/
|
const enum MemberExpLexState {
|
||||||
|
inMemberExp,
|
||||||
|
inBrackets,
|
||||||
|
inString
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
|
||||||
|
const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/
|
||||||
|
const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple lexer to check if an expression is a member expression. This is
|
||||||
|
* lax and only checks validity at the root level (i.e. does not validate exps
|
||||||
|
* inside square brackets), but it's ok since these are only used on template
|
||||||
|
* expressions and false positives are invalid expressions in the first place.
|
||||||
|
*/
|
||||||
export const isMemberExpression = (path: string): boolean => {
|
export const isMemberExpression = (path: string): boolean => {
|
||||||
if (!path) return false
|
// remove whitespaces around . or [ first
|
||||||
const matched = memberExpRE.exec(path.trim())
|
path = path.trim().replace(whitespaceRE, s => s.trim())
|
||||||
if (!matched) return false
|
|
||||||
if (!matched[1]) return true
|
let state = MemberExpLexState.inMemberExp
|
||||||
if (!/[\[\]]/.test(matched[1])) return true
|
let prevState = MemberExpLexState.inMemberExp
|
||||||
return isMemberExpression(matched[1].trim())
|
let currentOpenBracketCount = 0
|
||||||
|
let currentStringType: "'" | '"' | '`' | null = null
|
||||||
|
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const char = path.charAt(i)
|
||||||
|
switch (state) {
|
||||||
|
case MemberExpLexState.inMemberExp:
|
||||||
|
if (char === '[') {
|
||||||
|
prevState = state
|
||||||
|
state = MemberExpLexState.inBrackets
|
||||||
|
currentOpenBracketCount++
|
||||||
|
} else if (
|
||||||
|
!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case MemberExpLexState.inBrackets:
|
||||||
|
if (char === `'` || char === `"` || char === '`') {
|
||||||
|
prevState = state
|
||||||
|
state = MemberExpLexState.inString
|
||||||
|
currentStringType = char
|
||||||
|
} else if (char === `[`) {
|
||||||
|
currentOpenBracketCount++
|
||||||
|
} else if (char === `]`) {
|
||||||
|
if (!--currentOpenBracketCount) {
|
||||||
|
state = prevState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case MemberExpLexState.inString:
|
||||||
|
if (char === currentStringType) {
|
||||||
|
state = prevState
|
||||||
|
currentStringType = null
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !currentOpenBracketCount
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInnerRange(
|
export function getInnerRange(
|
||||||
|
|
Loading…
Reference in New Issue