diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
index 818076a5..3eed83cc 100644
--- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
@@ -351,5 +351,17 @@ describe('compiler: transform v-model', () => {
})
)
})
+
+ test('mal-formed expression', () => {
+ const onError = jest.fn()
+ parseWithVModel('', { onError })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
+ })
+ )
+ })
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
index 114fd529..41bd6677 100644
--- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
@@ -175,6 +175,34 @@ describe('compiler: transform v-on', () => {
})
})
+ test('should NOT wrap as function if expression is complex member expression', () => {
+ const node = parseWithVOn(`
`)
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `a['b' + c]`
+ }
+ })
+ })
+
+ test('complex member expression w/ prefixIdentifiers: true', () => {
+ const node = parseWithVOn(``, {
+ prefixIdentifiers: true
+ })
+ const props = (node.codegenNode as CallExpression)
+ .arguments[1] as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [{ content: `_ctx.a` }, `['b' + `, { content: `_ctx.c` }, `]`]
+ }
+ })
+ })
+
test('function expression w/ prefixIdentifiers: true', () => {
const node = parseWithVOn(` foo(e)"/>`, {
prefixIdentifiers: true
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index d422fc59..9da05bb8 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -519,11 +519,12 @@ export function createInterpolation(
}
export function createCompoundExpression(
- children: CompoundExpressionNode['children']
+ children: CompoundExpressionNode['children'],
+ loc: SourceLocation = locStub
): CompoundExpressionNode {
return {
type: NodeTypes.COMPOUND_EXPRESSION,
- loc: locStub,
+ loc,
children
}
}
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 759f3457..24b2a107 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -170,7 +170,7 @@ export const errorMessages: { [code: number]: string } = {
`These children will be ignored.`,
[ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or tags.`,
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
- [ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model has invalid expression.`,
+ [ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
// generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts
index e1c967fe..9e8343e6 100644
--- a/packages/compiler-core/src/index.ts
+++ b/packages/compiler-core/src/index.ts
@@ -14,6 +14,7 @@ import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
import { optimizeText } from './transforms/optimizeText'
import { transformOnce } from './transforms/vOnce'
+import { transformModel } from './transforms/vModel'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
@@ -62,6 +63,7 @@ export function baseCompile(
on: transformOn,
bind: transformBind,
once: transformOnce,
+ model: transformModel,
...(options.directiveTransforms || {}) // user transforms
}
})
diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts
index fa3ef6b3..e3c2c028 100644
--- a/packages/compiler-core/src/transforms/transformExpression.ts
+++ b/packages/compiler-core/src/transforms/transformExpression.ts
@@ -203,7 +203,7 @@ export function processExpression(
let ret
if (children.length) {
- ret = createCompoundExpression(children)
+ ret = createCompoundExpression(children, node.loc)
} else {
ret = node
}
diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts
index 3be84226..812bab7f 100644
--- a/packages/compiler-core/src/transforms/vModel.ts
+++ b/packages/compiler-core/src/transforms/vModel.ts
@@ -7,21 +7,23 @@ import {
Property
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
-import { isEmptyExpression } from '../utils'
+import { isMemberExpression } from '../utils'
export const transformModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir
if (!exp) {
- context.onError(createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION))
-
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc)
+ )
return createTransformProps()
}
- if (isEmptyExpression(exp)) {
+ const expString =
+ exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : exp.loc.source
+ if (!isMemberExpression(expString)) {
context.onError(
- createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION)
+ createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
)
-
return createTransformProps()
}
diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts
index 521d37df..d4a21ba9 100644
--- a/packages/compiler-core/src/transforms/vOn.ts
+++ b/packages/compiler-core/src/transforms/vOn.ts
@@ -10,9 +10,9 @@ import {
import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
+import { isMemberExpression } from '../utils'
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
-const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
// v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
@@ -49,7 +49,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
// skipped by transformExpression as a special case.
let exp: ExpressionNode = dir.exp as SimpleExpressionNode
const isInlineStatement = !(
- simplePathRE.test(exp.content) || fnExpRE.test(exp.content)
+ isMemberExpression(exp.content) || fnExpRE.test(exp.content)
)
// process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) {
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index f6f28c68..0f873e3a 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -63,8 +63,13 @@ export const walkJS: typeof walk = (ast, walker) => {
return walk(ast, walker)
}
+const nonIdentifierRE = /^\d|[^\$\w]/
export const isSimpleIdentifier = (name: string): boolean =>
- !/^\d|[^\$\w]/.test(name)
+ !nonIdentifierRE.test(name)
+
+const memberExpRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])*$/
+export const isMemberExpression = (path: string): boolean =>
+ memberExpRE.test(path)
export function getInnerRange(
loc: SourceLocation,