diff --git a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
index 1b259f7ca68..887d60239dc 100644
--- a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
@@ -1,56 +1,75 @@
-import { baseCompile } from '../../src'
-
-describe('compiler: v-memo transform', () => {
- function compile(content: string) {
- return baseCompile(`
${content}
`, {
- mode: 'module',
- prefixIdentifiers: true
- }).code
- }
-
- test('on root element', () => {
- expect(
- baseCompile(``, {
- mode: 'module',
- prefixIdentifiers: true
- }).code
- ).toMatchSnapshot()
- })
+import { baseCompile, CompilerOptions, ErrorCodes } from '../../src'
- test('on normal element', () => {
- expect(compile(``)).toMatchSnapshot()
- })
+function compile(content: string, options: CompilerOptions = {}) {
+ return baseCompile(`${content}
`, {
+ mode: 'module',
+ prefixIdentifiers: true,
+ ...options
+ }).code
+}
- test('on component', () => {
- expect(compile(``)).toMatchSnapshot()
- })
+describe('compiler: v-memo', () => {
+ describe('transform', () => {
+ test('on root element', () => {
+ expect(
+ baseCompile(``, {
+ mode: 'module',
+ prefixIdentifiers: true
+ }).code
+ ).toMatchSnapshot()
+ })
- test('on v-if', () => {
- expect(
- compile(
- `foobar
- `
- )
- ).toMatchSnapshot()
- })
+ test('on normal element', () => {
+ expect(compile(``)).toMatchSnapshot()
+ })
- test('on v-for', () => {
- expect(
- compile(
- `
- foobar
-
`
- )
- ).toMatchSnapshot()
+ test('on component', () => {
+ expect(compile(``)).toMatchSnapshot()
+ })
+
+ test('on v-if', () => {
+ expect(
+ compile(
+ `foobar
+ `
+ )
+ ).toMatchSnapshot()
+ })
+
+ test('on v-for', () => {
+ expect(
+ compile(
+ `
+ foobar
+
`
+ )
+ ).toMatchSnapshot()
+ })
+
+ test('on template v-for', () => {
+ expect(
+ compile(
+ `
+ foobar
+ `
+ )
+ ).toMatchSnapshot()
+ })
})
- test('on template v-for', () => {
- expect(
- compile(
- `
- foobar
- `
+ describe('errors', () => {
+ test('inside v-for', () => {
+ const onError = jest.fn()
+ compile(``, {
+ onError
+ })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_MEMO_INSIDE_FOR
+ })
)
- ).toMatchSnapshot()
+ })
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
index 553ac1dfac4..3dba263387d 100644
--- a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
@@ -4,7 +4,8 @@ import {
NodeTypes,
generate,
CompilerOptions,
- getBaseTransformPreset
+ getBaseTransformPreset,
+ ErrorCodes
} from '../../src'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
@@ -19,127 +20,145 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
return ast
}
-describe('compiler: v-once transform', () => {
- test('as root node', () => {
- const root = transformWithOnce(``)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect(root.codegenNode).toMatchObject({
- type: NodeTypes.JS_CACHE_EXPRESSION,
- index: 0,
- value: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+describe('compiler: v-once', () => {
+ describe('transform', () => {
+ test('as root node', () => {
+ const root = transformWithOnce(``)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect(root.codegenNode).toMatchObject({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
+ value: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
})
- expect(generate(root).code).toMatchSnapshot()
- })
- test('on nested plain element', () => {
- const root = transformWithOnce(``)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
- type: NodeTypes.JS_CACHE_EXPRESSION,
- index: 0,
- value: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ test('on nested plain element', () => {
+ const root = transformWithOnce(``)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
+ value: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
})
- expect(generate(root).code).toMatchSnapshot()
- })
- test('on component', () => {
- const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
- type: NodeTypes.JS_CACHE_EXPRESSION,
- index: 0,
- value: {
- type: NodeTypes.VNODE_CALL,
- tag: `_component_Comp`
- }
+ test('on component', () => {
+ const root = transformWithOnce(`
`)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
+ value: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `_component_Comp`
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
})
- expect(generate(root).code).toMatchSnapshot()
- })
- test('on slot outlet', () => {
- const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
- type: NodeTypes.JS_CACHE_EXPRESSION,
- index: 0,
- value: {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ test('on slot outlet', () => {
+ const root = transformWithOnce(`
`)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
+ value: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_SLOT
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
})
- expect(generate(root).code).toMatchSnapshot()
- })
-
- // v-once inside v-once should not be cached
- test('inside v-once', () => {
- const root = transformWithOnce(``)
- expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
- })
- // cached nodes should be ignored by hoistStatic transform
- test('with hoistStatic: true', () => {
- const root = transformWithOnce(``, {
- hoistStatic: true
+ // v-once inside v-once should not be cached
+ test('inside v-once', () => {
+ const root = transformWithOnce(``)
+ expect(root.cached).not.toBe(2)
+ expect(root.cached).toBe(1)
})
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect(root.hoists.length).toBe(0)
- expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
- type: NodeTypes.JS_CACHE_EXPRESSION,
- index: 0,
- value: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
- test('with v-if/else', () => {
- const root = transformWithOnce(``)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect(root.children[0]).toMatchObject({
- type: NodeTypes.IF,
- // should cache the entire v-if/else-if/else expression, not just a single branch
- codegenNode: {
+ // cached nodes should be ignored by hoistStatic transform
+ test('with hoistStatic: true', () => {
+ const root = transformWithOnce(``, {
+ hoistStatic: true
+ })
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect(root.hoists.length).toBe(0)
+ expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
value: {
- type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
- consequent: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- },
- alternate: {
- type: NodeTypes.VNODE_CALL,
- tag: `"p"`
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`
+ }
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('with v-if/else', () => {
+ const root = transformWithOnce(``)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect(root.children[0]).toMatchObject({
+ type: NodeTypes.IF,
+ // should cache the entire v-if/else-if/else expression, not just a single branch
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ consequent: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`
+ },
+ alternate: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"p"`
+ }
}
}
- }
+ })
+ })
+
+ test('with v-for', () => {
+ const root = transformWithOnce(``)
+ expect(root.cached).toBe(1)
+ expect(root.helpers).toContain(SET_BLOCK_TRACKING)
+ expect(root.children[0]).toMatchObject({
+ type: NodeTypes.FOR,
+ // should cache the entire v-for expression, not just a single branch
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION
+ }
+ })
})
})
- test('with v-for', () => {
- const root = transformWithOnce(``)
- expect(root.cached).toBe(1)
- expect(root.helpers).toContain(SET_BLOCK_TRACKING)
- expect(root.children[0]).toMatchObject({
- type: NodeTypes.FOR,
- // should cache the entire v-for expression, not just a single branch
- codegenNode: {
- type: NodeTypes.JS_CACHE_EXPRESSION
- }
+ describe('errors', () => {
+ test('inside v-for', () => {
+ const onError = jest.fn()
+ transformWithOnce(``, {
+ onError
+ })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_ONCE_INSIDE_FOR
+ })
+ )
})
})
})
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 09ec7290135..7472bb32edf 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -76,6 +76,8 @@ export const enum ErrorCodes {
X_V_FOR_NO_EXPRESSION,
X_V_FOR_MALFORMED_EXPRESSION,
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
+ X_V_ONCE_INSIDE_FOR,
+ X_V_MEMO_INSIDE_FOR,
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
@@ -148,6 +150,8 @@ export const errorMessages: Record = {
[ErrorCodes.X_V_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
[ErrorCodes.X_V_IF_SAME_KEY]: `v-if/else branches must use unique keys.`,
[ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`,
+ [ErrorCodes.X_V_MEMO_INSIDE_FOR]: `v-memo cannot be used inside v-for.`,
+ [ErrorCodes.X_V_ONCE_INSIDE_FOR]: `v-once cannot be used inside v-for.`,
[ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: ` key should be placed on the tag.`,
diff --git a/packages/compiler-core/src/transforms/vMemo.ts b/packages/compiler-core/src/transforms/vMemo.ts
index 4e150875d98..1ac38d0c1b9 100644
--- a/packages/compiler-core/src/transforms/vMemo.ts
+++ b/packages/compiler-core/src/transforms/vMemo.ts
@@ -9,6 +9,7 @@ import {
PlainElementNode
} from '../ast'
import { WITH_MEMO } from '../runtimeHelpers'
+import { createCompilerError, ErrorCodes } from '../errors'
const seen = new WeakSet()
@@ -18,6 +19,13 @@ export const transformMemo: NodeTransform = (node, context) => {
if (!dir || seen.has(node)) {
return
}
+
+ if (context.scopes.vFor > 0) {
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_MEMO_INSIDE_FOR, node.loc)
+ )
+ }
+
seen.add(node)
return () => {
const codegenNode =
diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts
index 1b58c6763b6..01dda349b2a 100644
--- a/packages/compiler-core/src/transforms/vOnce.ts
+++ b/packages/compiler-core/src/transforms/vOnce.ts
@@ -2,6 +2,7 @@ import { NodeTransform } from '../transform'
import { findDir } from '../utils'
import { ElementNode, ForNode, IfNode, NodeTypes } from '../ast'
import { SET_BLOCK_TRACKING } from '../runtimeHelpers'
+import { createCompilerError, ErrorCodes } from '../errors'
const seen = new WeakSet()
@@ -10,6 +11,13 @@ export const transformOnce: NodeTransform = (node, context) => {
if (seen.has(node) || context.inVOnce) {
return
}
+
+ if (context.scopes.vFor > 0) {
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_ONCE_INSIDE_FOR, node.loc)
+ )
+ }
+
seen.add(node)
context.inVOnce = true
context.helper(SET_BLOCK_TRACKING)