+import { TransformContext } from '../src'
import { Position } from '../src/ast'
import {
getInnerRange,
advancePositionWithClone,
- isMemberExpression,
+ isMemberExpressionNode,
+ isMemberExpressionBrowser,
toValidAssetId
} from '../src/utils'
})
})
-test('isMemberExpression', () => {
- // should work
- expect(isMemberExpression('obj.foo')).toBe(true)
- expect(isMemberExpression('obj[foo]')).toBe(true)
- expect(isMemberExpression('obj[arr[0]]')).toBe(true)
- 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[1 + 1]')).toBe(true)
- 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)
- expect(isMemberExpression('obj?.foo')).toBe(true)
- expect(isMemberExpression('foo().test')).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('objfoo]')).toBe(false)
- expect(isMemberExpression('obj[arr[0]')).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)
- expect(isMemberExpression('a?b:c')).toBe(false)
- expect(isMemberExpression(`state['text'] = $event`)).toBe(false)
+describe('isMemberExpression', () => {
+ function commonAssertions(fn: (str: string) => boolean) {
+ // should work
+ expect(fn('obj.foo')).toBe(true)
+ expect(fn('obj[foo]')).toBe(true)
+ expect(fn('obj[arr[0]]')).toBe(true)
+ expect(fn('obj[arr[ret.bar]]')).toBe(true)
+ expect(fn('obj[arr[ret[bar]]]')).toBe(true)
+ expect(fn('obj[arr[ret[bar]]].baz')).toBe(true)
+ expect(fn('obj[1 + 1]')).toBe(true)
+ expect(fn(`obj[x[0]]`)).toBe(true)
+ expect(fn('obj[1][2]')).toBe(true)
+ expect(fn('obj[1][2].foo[3].bar.baz')).toBe(true)
+ expect(fn(`a[b[c.d]][0]`)).toBe(true)
+ expect(fn('obj?.foo')).toBe(true)
+ expect(fn('foo().test')).toBe(true)
+
+ // strings
+ expect(fn(`a['foo' + bar[baz]["qux"]]`)).toBe(true)
+
+ // multiline whitespaces
+ expect(fn('obj \n .foo \n [bar \n + baz]')).toBe(true)
+ expect(fn(`\n model\n.\nfoo \n`)).toBe(true)
+
+ // should fail
+ expect(fn('a \n b')).toBe(false)
+ expect(fn('obj[foo')).toBe(false)
+ expect(fn('objfoo]')).toBe(false)
+ expect(fn('obj[arr[0]')).toBe(false)
+ expect(fn('obj[arr0]]')).toBe(false)
+ expect(fn('123[a]')).toBe(false)
+ expect(fn('a + b')).toBe(false)
+ expect(fn('foo()')).toBe(false)
+ expect(fn('a?b:c')).toBe(false)
+ expect(fn(`state['text'] = $event`)).toBe(false)
+ }
+
+ test('browser', () => {
+ commonAssertions(isMemberExpressionBrowser)
+ })
+
+ test('node', () => {
+ const ctx = { expressionPlugins: ['typescript'] } as any as TransformContext
+ const fn = (str: string) => isMemberExpressionNode(str, ctx)
+ commonAssertions(fn)
+
+ // TS-specific checks
+ expect(fn('foo as string')).toBe(true)
+ expect(fn(`foo.bar as string`)).toBe(true)
+ expect(fn(`foo['bar'] as string`)).toBe(true)
+ expect(fn(`foo[bar as string]`)).toBe(true)
+ expect(fn(`foo() as string`)).toBe(false)
+ expect(fn(`a + b as string`)).toBe(false)
+ })
})
test('toValidAssetId', () => {
WITH_MEMO,
OPEN_BLOCK
} from './runtimeHelpers'
-import { isString, isObject, hyphenate, extend } from '@vue/shared'
+import {
+ isString,
+ isObject,
+ hyphenate,
+ extend,
+ babelParserDefaultPlugins
+} from '@vue/shared'
import { PropsExpression } from './transforms/transformElement'
+import { parseExpression } from '@babel/parser'
+import { Expression } from '@babel/types'
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
* 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 isMemberExpressionBrowser = (path: string): boolean => {
// remove whitespaces around . or [ first
path = path.trim().replace(whitespaceRE, s => s.trim())
return !currentOpenBracketCount && !currentOpenParensCount
}
+export const isMemberExpressionNode = (
+ path: string,
+ context: TransformContext
+): boolean => {
+ path = path.trim()
+ if (!validFirstIdentCharRE.test(path[0])) {
+ return false
+ }
+ try {
+ let ret: Expression = parseExpression(path, {
+ plugins: [...context.expressionPlugins, ...babelParserDefaultPlugins]
+ })
+ if (ret.type === 'TSAsExpression' || ret.type === 'TSTypeAssertion') {
+ ret = ret.expression
+ }
+ return (
+ ret.type === 'MemberExpression' ||
+ ret.type === 'OptionalMemberExpression' ||
+ ret.type === 'Identifier'
+ )
+ } catch (e) {
+ return false
+ }
+}
+
+export const isMemberExpression = __BROWSER__
+ ? isMemberExpressionBrowser
+ : isMemberExpressionNode
+
export function getInnerRange(
loc: SourceLocation,
offset: number,