diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
index 82dd4909fd6..1f27bad5c22 100644
--- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
@@ -582,5 +582,22 @@ describe('compiler: transform v-model', () => {
}),
)
})
+
+ test('used on const binding', () => {
+ const onError = vi.fn()
+ parseWithVModel('
', {
+ onError,
+ bindingMetadata: {
+ c: BindingTypes.LITERAL_CONST,
+ },
+ })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_MODEL_ON_CONST,
+ }),
+ )
+ })
})
})
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 58e113ab19e..ea8e8f27048 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -88,6 +88,7 @@ export enum ErrorCodes {
X_V_MODEL_MALFORMED_EXPRESSION,
X_V_MODEL_ON_SCOPE_VARIABLE,
X_V_MODEL_ON_PROPS,
+ X_V_MODEL_ON_CONST,
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
@@ -176,6 +177,7 @@ export const errorMessages: Record = {
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
[ErrorCodes.X_V_MODEL_ON_PROPS]: `v-model cannot be used on a prop, because local prop bindings are not writable.\nUse a v-bind binding combined with a v-on listener that emits update:x event instead.`,
+ [ErrorCodes.X_V_MODEL_ON_CONST]: `v-model cannot be used on a const binding because it is not writable.`,
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: ` expects exactly one child component.`,
[ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts
index 598c1ea4387..ff722db9b54 100644
--- a/packages/compiler-core/src/transforms/vModel.ts
+++ b/packages/compiler-core/src/transforms/vModel.ts
@@ -48,6 +48,15 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
return createTransformProps()
}
+ // const bindings are not writable.
+ if (
+ bindingType === BindingTypes.LITERAL_CONST ||
+ bindingType === BindingTypes.SETUP_CONST
+ ) {
+ context.onError(createCompilerError(ErrorCodes.X_V_MODEL_ON_CONST, exp.loc))
+ return createTransformProps()
+ }
+
const maybeRef =
!__BROWSER__ &&
context.inline &&
diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts
index faf6fb56441..f7a5a44f913 100644
--- a/packages/compiler-dom/src/errors.ts
+++ b/packages/compiler-dom/src/errors.ts
@@ -21,7 +21,7 @@ export function createDOMCompilerError(
}
export enum DOMErrorCodes {
- X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
+ X_V_HTML_NO_EXPRESSION = 54 /* ErrorCodes.__EXTEND_POINT__ */,
X_V_HTML_WITH_CHILDREN,
X_V_TEXT_NO_EXPRESSION,
X_V_TEXT_WITH_CHILDREN,
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 2acac64b0fb..795f0ae8840 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -639,6 +639,44 @@ return { foo, bar, baz, y, z }
}"
`;
+exports[`SFC compile
+
+
+
+
+ `)
+
+ expect(content).toMatch(
+ `let name = reactive({ first: 'john', last: 'doe' })`,
+ )
+ expect(bindings!.name).toBe(BindingTypes.SETUP_LET)
+ expect(warnOnceMock).toHaveBeenCalledTimes(1)
+ expect(warnOnceMock).toHaveBeenCalledWith(
+ expect.stringContaining(
+ '`v-model` cannot update a `const` reactive binding',
+ ),
+ )
+ assertCode(content)
+ })
+
+ test('demote const reactive binding to let when used in v-model (inlineTemplate)', () => {
+ warnOnceMock.mockClear()
+ const { content, bindings } = compile(
+ `
+
+
+
+
+
+ `,
+ { inlineTemplate: true },
+ )
+
+ expect(content).toMatch(
+ `let name = reactive({ first: 'john', last: 'doe' })`,
+ )
+ expect(bindings!.name).toBe(BindingTypes.SETUP_LET)
+ expect(warnOnceMock).toHaveBeenCalledTimes(1)
+ expect(warnOnceMock).toHaveBeenCalledWith(
+ expect.stringContaining(
+ '`v-model` cannot update a `const` reactive binding',
+ ),
+ )
+ assertCode(content)
+ })
+
+ test('v-model should error on literal const bindings', () => {
+ expect(() =>
+ compile(
+ `
+
+
+
+
+ `,
+ { inlineTemplate: true },
+ ),
+ ).toThrow('v-model cannot be used on a const binding')
+ })
+
describe('