|
|
@@ -56,14 +56,67 @@ const nonIdentifierRE = /^\d|[^\$\w]/
|
|
|
export const isSimpleIdentifier = (name: string): boolean =>
|
|
|
!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 => {
|
|
|
- if (!path) return false
|
|
|
- const matched = memberExpRE.exec(path.trim())
|
|
|
- if (!matched) return false
|
|
|
- if (!matched[1]) return true
|
|
|
- if (!/[\[\]]/.test(matched[1])) return true
|
|
|
- return isMemberExpression(matched[1].trim())
|
|
|
+ // remove whitespaces around . or [ first
|
|
|
+ path = path.trim().replace(whitespaceRE, s => s.trim())
|
|
|
+
|
|
|
+ let state = MemberExpLexState.inMemberExp
|
|
|
+ let prevState = MemberExpLexState.inMemberExp
|
|
|
+ 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(
|