From 6497417d6aedd0bdaf4400ffd4c313b003b76246 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 11:03:00 +0800 Subject: [PATCH 1/7] fix(compiler-core): Identifiers in switch-case should not be inferred as references --- packages/compiler-core/src/babelUtils.ts | 41 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 51614612b10..335d331527f 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -10,6 +10,8 @@ import type { Node, ObjectProperty, Program, + SwitchCase, + SwitchStatement, } from '@babel/types' import { walk } from 'estree-walker' @@ -80,6 +82,10 @@ export function walkIdentifiers( markScopeIdentifier(node, id, knownIds), ) } + } else if (node.type === 'SwitchStatement') { + walkSwitchStatement(node, false, id => + markScopeIdentifier(node, id, knownIds), + ) } else if (node.type === 'CatchClause' && node.param) { for (const id of extractIdentifiers(node.param)) { markScopeIdentifier(node, id, knownIds) @@ -187,10 +193,11 @@ export function walkFunctionParams( } export function walkBlockDeclarations( - block: BlockStatement | Program, + block: BlockStatement | SwitchCase | Program, onIdent: (node: Identifier) => void, ): void { - for (const stmt of block.body) { + const body = block.type === 'SwitchCase' ? block.consequent : block.body + for (const stmt of body) { if (stmt.type === 'VariableDeclaration') { if (stmt.declare) continue for (const decl of stmt.declarations) { @@ -206,6 +213,8 @@ export function walkBlockDeclarations( onIdent(stmt.id) } else if (isForStatement(stmt)) { walkForStatement(stmt, true, onIdent) + } else if (stmt.type == 'SwitchStatement') { + walkSwitchStatement(stmt, true, onIdent) } } } @@ -239,6 +248,34 @@ function walkForStatement( } } +function walkSwitchStatement( + stmt: SwitchStatement, + isVar: boolean, + onIdent: (id: Identifier) => void, +) { + for (const cs of stmt.cases) { + if (cs.test) { + for (const id of extractIdentifiers(cs.test)) { + onIdent(id) + } + } + + for (const stmt of cs.consequent) { + if ( + stmt.type === 'VariableDeclaration' && + (stmt.kind === 'var' ? isVar : !isVar) + ) { + for (const decl of stmt.declarations) { + for (const id of extractIdentifiers(decl.id)) { + onIdent(id) + } + } + } + } + walkBlockDeclarations(cs, onIdent) + } +} + export function extractIdentifiers( param: Node, nodes: Identifier[] = [], From 2f4cdd8ed693da0e5bd8112f1d957aed93aff7aa Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 11:17:05 +0800 Subject: [PATCH 2/7] fix(compiler-core): Add switch-case test --- .../transforms/transformExpressions.spec.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index c92814089ef..96792679660 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -716,4 +716,51 @@ describe('compiler: expression transform', () => { }) }) }) + + // Test for switch case variable declarations bug fix + describe('switch case variable declarations', () => { + test('should handle const declarations in switch case without braces', () => { + const node = parseWithExpressionTransform( + `{{ (() => { switch (1) { case 1: const foo = "bar"; return \`\${foo}\`; } })() }}`, + ) as InterpolationNode + + // The variable 'foo' should be recognized as local and not prefixed with _ctx + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + }) + + // Check that 'foo' is not prefixed with '_ctx.' + const children = (node.content as any).children + const codeStr = children + .map((c: any) => (typeof c === 'string' ? c : c.content)) + .join('') + expect(codeStr).not.toContain('_ctx.foo') + expect(codeStr).toContain('foo') + }) + + test('should handle const declarations in switch case with braces (existing behavior)', () => { + const node = parseWithExpressionTransform( + `{{ (() => { + switch (true) { + case true: { + const foo = "bar"; + return \`\${foo}\`; + } + } + })() }}`, + ) as InterpolationNode + + // This should work correctly even before our fix + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + }) + + const children = (node.content as any).children + const codeStr = children + .map((c: any) => (typeof c === 'string' ? c : c.content)) + .join('') + expect(codeStr).not.toContain('_ctx.foo') + expect(codeStr).toContain('foo') + }) + }) }) From c60c2d52542b390a14c62c9ec86e6ec894dc3629 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 13:49:50 +0800 Subject: [PATCH 3/7] fix(compiler-core): Remove unnecessary identifier extraction from switch-case tests --- packages/compiler-core/src/babelUtils.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 335d331527f..2246a780e0f 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -254,12 +254,6 @@ function walkSwitchStatement( onIdent: (id: Identifier) => void, ) { for (const cs of stmt.cases) { - if (cs.test) { - for (const id of extractIdentifiers(cs.test)) { - onIdent(id) - } - } - for (const stmt of cs.consequent) { if ( stmt.type === 'VariableDeclaration' && From a543ec2ef9822e5cbc50e41e74dece295b992332 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 13:55:50 +0800 Subject: [PATCH 4/7] fix(compiler-core): Enhance walkIdentifiers to handle scopeIds in SwitchStatement --- packages/compiler-core/src/babelUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 2246a780e0f..f490e8aa41c 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -83,6 +83,10 @@ export function walkIdentifiers( ) } } else if (node.type === 'SwitchStatement') { + if (node.scopeIds) { + node.scopeIds.forEach(id => markKnownIds(id, knownIds)) + } + // record switch case block-level local variables walkSwitchStatement(node, false, id => markScopeIdentifier(node, id, knownIds), ) From f5f832f0bba6157a5a41c5135c7f3463a434b9e9 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 14:13:57 +0800 Subject: [PATCH 5/7] fix(compiler-core): Update switch statement condition check to use strict equality --- .../transforms/transformExpressions.spec.ts | 44 ++++++++----------- packages/compiler-core/src/babelUtils.ts | 2 +- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 96792679660..37e1d32c515 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -720,26 +720,17 @@ describe('compiler: expression transform', () => { // Test for switch case variable declarations bug fix describe('switch case variable declarations', () => { test('should handle const declarations in switch case without braces', () => { - const node = parseWithExpressionTransform( + const { code } = compile( `{{ (() => { switch (1) { case 1: const foo = "bar"; return \`\${foo}\`; } })() }}`, - ) as InterpolationNode - - // The variable 'foo' should be recognized as local and not prefixed with _ctx - expect(node.content).toMatchObject({ - type: NodeTypes.COMPOUND_EXPRESSION, - }) + ) - // Check that 'foo' is not prefixed with '_ctx.' - const children = (node.content as any).children - const codeStr = children - .map((c: any) => (typeof c === 'string' ? c : c.content)) - .join('') - expect(codeStr).not.toContain('_ctx.foo') - expect(codeStr).toContain('foo') + expect(code).toMatch(`const foo = "bar";`) + expect(code).toMatch(`return \`\${foo}\`;`) + expect(code).not.toMatch(`_ctx.foo`) }) test('should handle const declarations in switch case with braces (existing behavior)', () => { - const node = parseWithExpressionTransform( + const { code } = compile( `{{ (() => { switch (true) { case true: { @@ -748,19 +739,20 @@ describe('compiler: expression transform', () => { } } })() }}`, - ) as InterpolationNode + ) - // This should work correctly even before our fix - expect(node.content).toMatchObject({ - type: NodeTypes.COMPOUND_EXPRESSION, - }) + expect(code).toMatch(`const foo = "bar";`) + expect(code).toMatch(`return \`\${foo}\`;`) + expect(code).not.toMatch(`_ctx.foo`) + }) + + test('should parse switch case test as local scoped variables', () => { + const { code } = compile( + `{{ (() => { switch (foo) { case bar: return \`\${bar}\`; } })() }}`, + ) - const children = (node.content as any).children - const codeStr = children - .map((c: any) => (typeof c === 'string' ? c : c.content)) - .join('') - expect(codeStr).not.toContain('_ctx.foo') - expect(codeStr).toContain('foo') + expect(code).toMatch('_ctx.foo') + expect(code).toMatch(`_ctx.bar`) }) }) }) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index f490e8aa41c..6ec60515c85 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -217,7 +217,7 @@ export function walkBlockDeclarations( onIdent(stmt.id) } else if (isForStatement(stmt)) { walkForStatement(stmt, true, onIdent) - } else if (stmt.type == 'SwitchStatement') { + } else if (stmt.type === 'SwitchStatement') { walkSwitchStatement(stmt, true, onIdent) } } From 47c7af9b956cc22a8e87194deaba9564ca192a30 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Wed, 24 Sep 2025 14:29:21 +0800 Subject: [PATCH 6/7] fix(compiler-core): Refactor walkIdentifiers to handle SwitchStatement scopeIds correctly --- packages/compiler-core/src/babelUtils.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 6ec60515c85..1ddb0ef9e62 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -85,11 +85,12 @@ export function walkIdentifiers( } else if (node.type === 'SwitchStatement') { if (node.scopeIds) { node.scopeIds.forEach(id => markKnownIds(id, knownIds)) + } else { + // record switch case block-level local variables + walkSwitchStatement(node, false, id => + markScopeIdentifier(node, id, knownIds), + ) } - // record switch case block-level local variables - walkSwitchStatement(node, false, id => - markScopeIdentifier(node, id, knownIds), - ) } else if (node.type === 'CatchClause' && node.param) { for (const id of extractIdentifiers(node.param)) { markScopeIdentifier(node, id, knownIds) From b457df0203c99278f4a979f1950a74b19cd88238 Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 24 Sep 2025 21:30:48 +0800 Subject: [PATCH 7/7] chore: reuse analyzed scopeIds when possible --- .../transforms/transformExpressions.spec.ts | 1 - packages/compiler-core/src/babelUtils.ts | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 37e1d32c515..2f1e6eb6412 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -717,7 +717,6 @@ describe('compiler: expression transform', () => { }) }) - // Test for switch case variable declarations bug fix describe('switch case variable declarations', () => { test('should handle const declarations in switch case without braces', () => { const { code } = compile( diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 1ddb0ef9e62..0ce257ca53f 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -92,13 +92,21 @@ export function walkIdentifiers( ) } } else if (node.type === 'CatchClause' && node.param) { - for (const id of extractIdentifiers(node.param)) { - markScopeIdentifier(node, id, knownIds) + if (node.scopeIds) { + node.scopeIds.forEach(id => markKnownIds(id, knownIds)) + } else { + for (const id of extractIdentifiers(node.param)) { + markScopeIdentifier(node, id, knownIds) + } } } else if (isForStatement(node)) { - walkForStatement(node, false, id => - markScopeIdentifier(node, id, knownIds), - ) + if (node.scopeIds) { + node.scopeIds.forEach(id => markKnownIds(id, knownIds)) + } else { + walkForStatement(node, false, id => + markScopeIdentifier(node, id, knownIds), + ) + } } }, leave(node: Node & { scopeIds?: Set }, parent: Node | null) {