From baa03115f72163a0fc921074822a077444d49402 Mon Sep 17 00:00:00 2001 From: XiaoPi <530257315@qq.com> Date: Mon, 17 Apr 2023 12:35:16 +0800 Subject: [PATCH] fix: handle conditional breaks in nested switch statement cases (#4937) * fix: add a new InclusionContext prop existedBroken * test: tweak test * Rework using labels * Use flags over labels for simplicity and performance --------- Co-authored-by: Lukas Taegert-Atkinson --- src/ast/ExecutionContext.ts | 18 +++++----- src/ast/nodes/BreakStatement.ts | 15 ++++----- src/ast/nodes/ContinueStatement.ts | 15 ++++----- src/ast/nodes/DoWhileStatement.ts | 15 ++------- src/ast/nodes/ForInStatement.ts | 15 ++------- src/ast/nodes/ForOfStatement.ts | 5 ++- src/ast/nodes/ForStatement.ts | 18 +++------- src/ast/nodes/IfStatement.ts | 18 ++++------ src/ast/nodes/ReturnStatement.ts | 10 ++---- src/ast/nodes/SwitchStatement.ts | 33 ++++++++++--------- src/ast/nodes/ThrowStatement.ts | 4 +-- src/ast/nodes/WhileStatement.ts | 15 ++------- src/ast/nodes/shared/FunctionBase.ts | 8 ++--- src/ast/nodes/shared/loops.ts | 32 ++++++++++++++++++ test/function/samples/switch-break/_config.js | 3 ++ test/function/samples/switch-break/main.js | 21 ++++++++++++ 16 files changed, 124 insertions(+), 121 deletions(-) create mode 100644 src/ast/nodes/shared/loops.ts create mode 100644 test/function/samples/switch-break/_config.js create mode 100644 test/function/samples/switch-break/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index dfb3cefd493..3906fb7b97e 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -11,12 +11,10 @@ interface ExecutionContextIgnore { this: boolean; } -export const BROKEN_FLOW_NONE = 0; -export const BROKEN_FLOW_BREAK_CONTINUE = 1; -export const BROKEN_FLOW_ERROR_RETURN_LABEL = 2; - interface ControlFlowContext { - brokenFlow: number; + brokenFlow: boolean; + hasBreak: boolean; + hasContinue: boolean; includedLabels: Set; } @@ -27,7 +25,7 @@ export interface InclusionContext extends ControlFlowContext { export interface HasEffectsContext extends ControlFlowContext { accessed: PathTracker; assigned: PathTracker; - brokenFlow: number; + brokenFlow: boolean; called: DiscriminatedPathTracker; ignore: ExecutionContextIgnore; instantiated: DiscriminatedPathTracker; @@ -36,7 +34,9 @@ export interface HasEffectsContext extends ControlFlowContext { export function createInclusionContext(): InclusionContext { return { - brokenFlow: BROKEN_FLOW_NONE, + brokenFlow: false, + hasBreak: false, + hasContinue: false, includedCallArguments: new Set(), includedLabels: new Set() }; @@ -46,8 +46,10 @@ export function createHasEffectsContext(): HasEffectsContext { return { accessed: new PathTracker(), assigned: new PathTracker(), - brokenFlow: BROKEN_FLOW_NONE, + brokenFlow: false, called: new DiscriminatedPathTracker(), + hasBreak: false, + hasContinue: false, ignore: { breaks: false, continues: false, diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index c7d92aefa1f..836fb356c0c 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,9 +1,4 @@ -import { - BROKEN_FLOW_BREAK_CONTINUE, - BROKEN_FLOW_ERROR_RETURN_LABEL, - type HasEffectsContext, - type InclusionContext -} from '../ExecutionContext'; +import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; import type Identifier from './Identifier'; import type * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -16,11 +11,11 @@ export default class BreakStatement extends StatementBase { if (this.label) { if (!context.ignore.labels.has(this.label.name)) return true; context.includedLabels.add(this.label.name); - context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; } else { if (!context.ignore.breaks) return true; - context.brokenFlow = BROKEN_FLOW_BREAK_CONTINUE; + context.hasBreak = true; } + context.brokenFlow = true; return false; } @@ -29,7 +24,9 @@ export default class BreakStatement extends StatementBase { if (this.label) { this.label.include(); context.includedLabels.add(this.label.name); + } else { + context.hasBreak = true; } - context.brokenFlow = this.label ? BROKEN_FLOW_ERROR_RETURN_LABEL : BROKEN_FLOW_BREAK_CONTINUE; + context.brokenFlow = true; } } diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index e9405d1038f..9e137e1afc6 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -1,9 +1,4 @@ -import { - BROKEN_FLOW_BREAK_CONTINUE, - BROKEN_FLOW_ERROR_RETURN_LABEL, - type HasEffectsContext, - type InclusionContext -} from '../ExecutionContext'; +import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; import type Identifier from './Identifier'; import type * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -16,11 +11,11 @@ export default class ContinueStatement extends StatementBase { if (this.label) { if (!context.ignore.labels.has(this.label.name)) return true; context.includedLabels.add(this.label.name); - context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; } else { if (!context.ignore.continues) return true; - context.brokenFlow = BROKEN_FLOW_BREAK_CONTINUE; + context.hasContinue = true; } + context.brokenFlow = true; return false; } @@ -29,7 +24,9 @@ export default class ContinueStatement extends StatementBase { if (this.label) { this.label.include(); context.includedLabels.add(this.label.name); + } else { + context.hasContinue = true; } - context.brokenFlow = this.label ? BROKEN_FLOW_ERROR_RETURN_LABEL : BROKEN_FLOW_BREAK_CONTINUE; + context.brokenFlow = true; } } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index e8d807069f0..7b74b9ad71b 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -6,6 +6,7 @@ import { StatementBase, type StatementNode } from './shared/Node'; +import { hasLoopBodyEffects, includeLoopBody } from './shared/loops'; export default class DoWhileStatement extends StatementBase { declare body: StatementNode; @@ -14,22 +15,12 @@ export default class DoWhileStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; - const { brokenFlow, ignore } = context; - const { breaks, continues } = ignore; - ignore.breaks = true; - ignore.continues = true; - if (this.body.hasEffects(context)) return true; - ignore.breaks = breaks; - ignore.continues = continues; - context.brokenFlow = brokenFlow; - return false; + return hasLoopBodyEffects(context, this.body); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.test.include(context, includeChildrenRecursively); - const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); - context.brokenFlow = brokenFlow; + includeLoopBody(context, this.body, includeChildrenRecursively); } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index a250c1a11b7..c18361512a3 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -15,6 +15,7 @@ import { type StatementNode } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; +import { hasLoopBodyEffects, includeLoopBody } from './shared/loops'; export default class ForInStatement extends StatementBase { declare body: StatementNode; @@ -30,15 +31,7 @@ export default class ForInStatement extends StatementBase { const { body, deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); if (left.hasEffectsAsAssignmentTarget(context, false) || right.hasEffects(context)) return true; - const { brokenFlow, ignore } = context; - const { breaks, continues } = ignore; - ignore.breaks = true; - ignore.continues = true; - if (body.hasEffects(context)) return true; - ignore.breaks = breaks; - ignore.continues = continues; - context.brokenFlow = brokenFlow; - return false; + return hasLoopBodyEffects(context, body); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { @@ -47,9 +40,7 @@ export default class ForInStatement extends StatementBase { this.included = true; left.includeAsAssignmentTarget(context, includeChildrenRecursively || true, false); right.include(context, includeChildrenRecursively); - const { brokenFlow } = context; - body.include(context, includeChildrenRecursively, { asSingleStatement: true }); - context.brokenFlow = brokenFlow; + includeLoopBody(context, body, includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index af66634f63c..b3e9e63244b 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -15,6 +15,7 @@ import { type StatementNode } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; +import { includeLoopBody } from './shared/loops'; export default class ForOfStatement extends StatementBase { declare await: boolean; @@ -39,9 +40,7 @@ export default class ForOfStatement extends StatementBase { this.included = true; left.includeAsAssignmentTarget(context, includeChildrenRecursively || true, false); right.include(context, includeChildrenRecursively); - const { brokenFlow } = context; - body.include(context, includeChildrenRecursively, { asSingleStatement: true }); - context.brokenFlow = brokenFlow; + includeLoopBody(context, body, includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index db4e9414728..4a88250c203 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -11,6 +11,7 @@ import { StatementBase, type StatementNode } from './shared/Node'; +import { hasLoopBodyEffects, includeLoopBody } from './shared/loops'; export default class ForStatement extends StatementBase { declare body: StatementNode; @@ -28,27 +29,18 @@ export default class ForStatement extends StatementBase { this.init?.hasEffects(context) || this.test?.hasEffects(context) || this.update?.hasEffects(context) - ) + ) { return true; - const { brokenFlow, ignore } = context; - const { breaks, continues } = ignore; - ignore.breaks = true; - ignore.continues = true; - if (this.body.hasEffects(context)) return true; - ignore.breaks = breaks; - ignore.continues = continues; - context.brokenFlow = brokenFlow; - return false; + } + return hasLoopBodyEffects(context, this.body); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.init?.include(context, includeChildrenRecursively, { asSingleStatement: true }); this.test?.include(context, includeChildrenRecursively); - const { brokenFlow } = context; this.update?.include(context, includeChildrenRecursively); - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); - context.brokenFlow = brokenFlow; + includeLoopBody(context, this.body, includeChildrenRecursively); } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 2049d253aba..47c43aa198e 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -1,11 +1,7 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { - BROKEN_FLOW_NONE, - type HasEffectsContext, - type InclusionContext -} from '../ExecutionContext'; +import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; import TrackingScope from '../scopes/TrackingScope'; import { EMPTY_PATH, SHARED_RECURSION_TRACKER } from '../utils/PathTracker'; import BlockStatement from './BlockStatement'; @@ -49,9 +45,8 @@ export default class IfStatement extends StatementBase implements DeoptimizableE context.brokenFlow = brokenFlow; if (this.alternate === null) return false; if (this.alternate.hasEffects(context)) return true; - context.brokenFlow = - // eslint-disable-next-line unicorn/consistent-destructuring - context.brokenFlow < consequentBrokenFlow ? context.brokenFlow : consequentBrokenFlow; + // eslint-disable-next-line unicorn/consistent-destructuring + context.brokenFlow = context.brokenFlow && consequentBrokenFlow; return false; } return testValue ? this.consequent.hasEffects(context) : !!this.alternate?.hasEffects(context); @@ -168,7 +163,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE private includeUnknownTest(context: InclusionContext) { this.test.include(context, false); const { brokenFlow } = context; - let consequentBrokenFlow = BROKEN_FLOW_NONE; + let consequentBrokenFlow = false; if (this.consequent.shouldBeIncluded(context)) { this.consequent.include(context, false, { asSingleStatement: true }); // eslint-disable-next-line unicorn/consistent-destructuring @@ -177,9 +172,8 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } if (this.alternate?.shouldBeIncluded(context)) { this.alternate.include(context, false, { asSingleStatement: true }); - context.brokenFlow = - // eslint-disable-next-line unicorn/consistent-destructuring - context.brokenFlow < consequentBrokenFlow ? context.brokenFlow : consequentBrokenFlow; + // eslint-disable-next-line unicorn/consistent-destructuring + context.brokenFlow = context.brokenFlow && consequentBrokenFlow; } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index eaa99bab74e..829900cc27d 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,10 +1,6 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; -import { - BROKEN_FLOW_ERROR_RETURN_LABEL, - type HasEffectsContext, - type InclusionContext -} from '../ExecutionContext'; +import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; import type * as NodeType from './NodeType'; import { UNKNOWN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, type IncludeChildren, StatementBase } from './shared/Node'; @@ -15,14 +11,14 @@ export default class ReturnStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (!context.ignore.returnYield || this.argument?.hasEffects(context)) return true; - context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; + context.brokenFlow = true; return false; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.argument?.include(context, includeChildrenRecursively); - context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; + context.brokenFlow = true; } initialise(): void { diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 19eabc09149..b506a82110a 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,7 +1,6 @@ import type MagicString from 'magic-string'; import { type RenderOptions, renderStatementList } from '../../utils/renderHelpers'; import { - BROKEN_FLOW_BREAK_CONTINUE, createHasEffectsContext, type HasEffectsContext, type InclusionContext @@ -25,28 +24,32 @@ export default class SwitchStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (this.discriminant.hasEffects(context)) return true; - const { brokenFlow, ignore } = context; + const { brokenFlow, hasBreak, ignore } = context; const { breaks } = ignore; - let minBrokenFlow = Infinity; ignore.breaks = true; + context.hasBreak = false; + let onlyHasBrokenFlow = true; for (const switchCase of this.cases) { if (switchCase.hasEffects(context)) return true; // eslint-disable-next-line unicorn/consistent-destructuring - minBrokenFlow = context.brokenFlow < minBrokenFlow ? context.brokenFlow : minBrokenFlow; + onlyHasBrokenFlow &&= context.brokenFlow && !context.hasBreak; + context.hasBreak = false; context.brokenFlow = brokenFlow; } - if (this.defaultCase !== null && !(minBrokenFlow === BROKEN_FLOW_BREAK_CONTINUE)) { - context.brokenFlow = minBrokenFlow; + if (this.defaultCase !== null) { + context.brokenFlow = onlyHasBrokenFlow; } ignore.breaks = breaks; + context.hasBreak = hasBreak; return false; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.discriminant.include(context, includeChildrenRecursively); - const { brokenFlow } = context; - let minBrokenFlow = Infinity; + const { brokenFlow, hasBreak } = context; + context.hasBreak = false; + let onlyHasBrokenFlow = true; let isCaseIncluded = includeChildrenRecursively || (this.defaultCase !== null && this.defaultCase < this.cases.length - 1); @@ -63,19 +66,17 @@ export default class SwitchStatement extends StatementBase { if (isCaseIncluded) { switchCase.include(context, includeChildrenRecursively); // eslint-disable-next-line unicorn/consistent-destructuring - minBrokenFlow = minBrokenFlow < context.brokenFlow ? minBrokenFlow : context.brokenFlow; + onlyHasBrokenFlow &&= context.brokenFlow && !context.hasBreak; + context.hasBreak = false; context.brokenFlow = brokenFlow; } else { - minBrokenFlow = brokenFlow; + onlyHasBrokenFlow = brokenFlow; } } - if ( - isCaseIncluded && - this.defaultCase !== null && - !(minBrokenFlow === BROKEN_FLOW_BREAK_CONTINUE) - ) { - context.brokenFlow = minBrokenFlow; + if (isCaseIncluded && this.defaultCase !== null) { + context.brokenFlow = onlyHasBrokenFlow; } + context.hasBreak = hasBreak; } initialise(): void { diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 41d346438ea..2d9956e820b 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; -import { BROKEN_FLOW_ERROR_RETURN_LABEL, type InclusionContext } from '../ExecutionContext'; +import { type InclusionContext } from '../ExecutionContext'; import type * as NodeType from './NodeType'; import { type ExpressionNode, type IncludeChildren, StatementBase } from './shared/Node'; @@ -15,7 +15,7 @@ export default class ThrowStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.argument.include(context, includeChildrenRecursively); - context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; + context.brokenFlow = true; } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index a65a67a94f0..a710564cd21 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -6,6 +6,7 @@ import { StatementBase, type StatementNode } from './shared/Node'; +import { hasLoopBodyEffects, includeLoopBody } from './shared/loops'; export default class WhileStatement extends StatementBase { declare body: StatementNode; @@ -14,22 +15,12 @@ export default class WhileStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; - const { brokenFlow, ignore } = context; - const { breaks, continues } = ignore; - ignore.breaks = true; - ignore.continues = true; - if (this.body.hasEffects(context)) return true; - ignore.breaks = breaks; - ignore.continues = continues; - context.brokenFlow = brokenFlow; - return false; + return hasLoopBodyEffects(context, this.body); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; this.test.include(context, includeChildrenRecursively); - const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); - context.brokenFlow = brokenFlow; + includeLoopBody(context, this.body, includeChildrenRecursively); } } diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 2f735b7970b..f1298c0a2f6 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -1,10 +1,6 @@ import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { - BROKEN_FLOW_NONE, - type HasEffectsContext, - type InclusionContext -} from '../../ExecutionContext'; +import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; import type { NodeInteraction, NodeInteractionCalled } from '../../NodeInteractions'; import { INTERACTION_CALLED, @@ -152,7 +148,7 @@ export default abstract class FunctionBase extends NodeBase { if (!this.deoptimized) this.applyDeoptimizations(); this.included = true; const { brokenFlow } = context; - context.brokenFlow = BROKEN_FLOW_NONE; + context.brokenFlow = false; this.body.include(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } diff --git a/src/ast/nodes/shared/loops.ts b/src/ast/nodes/shared/loops.ts new file mode 100644 index 00000000000..dcc2dc6b41d --- /dev/null +++ b/src/ast/nodes/shared/loops.ts @@ -0,0 +1,32 @@ +import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import type { StatementNode } from './Node'; + +export function hasLoopBodyEffects(context: HasEffectsContext, body: StatementNode): boolean { + const { brokenFlow, hasBreak, hasContinue, ignore } = context; + const { breaks, continues } = ignore; + ignore.breaks = true; + ignore.continues = true; + context.hasBreak = false; + context.hasContinue = false; + if (body.hasEffects(context)) return true; + ignore.breaks = breaks; + ignore.continues = continues; + context.hasBreak = hasBreak; + context.hasContinue = hasContinue; + context.brokenFlow = brokenFlow; + return false; +} + +export function includeLoopBody( + context: InclusionContext, + body: StatementNode, + includeChildrenRecursively: boolean | 'variables' +) { + const { brokenFlow, hasBreak, hasContinue } = context; + context.hasBreak = false; + context.hasContinue = false; + body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + context.hasBreak = hasBreak; + context.hasContinue = hasContinue; + context.brokenFlow = brokenFlow; +} diff --git a/test/function/samples/switch-break/_config.js b/test/function/samples/switch-break/_config.js new file mode 100644 index 00000000000..9e60c85325e --- /dev/null +++ b/test/function/samples/switch-break/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'if switch does not always throw an error, retain the following break' +}; diff --git a/test/function/samples/switch-break/main.js b/test/function/samples/switch-break/main.js new file mode 100644 index 00000000000..7643a17a0ff --- /dev/null +++ b/test/function/samples/switch-break/main.js @@ -0,0 +1,21 @@ +function issue(obj) { + switch (obj.field1) { + case 'baz': + switch (obj.field2) { + case 'value': { + if (obj.field1) { + if (obj.field1) { + break; + } + } + throw new Error(`error 1`); + } + default: + throw new Error(`error 2`); + } + break; // retained + default: + throw new Error('error 3'); + } +} +issue({ field1: 'baz', field2: 'value' });