diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1e47abc3210..e3fcc4624cf 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1341,7 +1341,7 @@ namespace ts { bind(clause); fallthroughFlow = currentFlow; if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { - errorOnFirstToken(clause, Diagnostics.Fallthrough_case_in_switch); + clause.fallthroughFlowNode = currentFlow; } } clauses.transformFlags = subtreeTransformFlags | TransformFlags.HasComputedFlags; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 78f8b965ca1..ec63501af75 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -31054,10 +31054,7 @@ namespace ts { firstDefaultClause = clause; } else { - const sourceFile = getSourceFileOfNode(node); - const start = skipTrivia(sourceFile.text, clause.pos); - const end = clause.statements.length > 0 ? clause.statements[0].pos : clause.end; - grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); + grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); hasDuplicateDefaultClause = true; } } @@ -31079,6 +31076,9 @@ namespace ts { } } forEach(clause.statements, checkSourceElement); + if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); + } }); if (node.caseBlock.locals) { registerForUnusedIdentifiersCheck(node.caseBlock); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9053bbc1126..5615568bb1f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2250,12 +2250,14 @@ namespace ts { parent: CaseBlock; expression: Expression; statements: NodeArray; + /* @internal */ fallthroughFlowNode?: FlowNode; } export interface DefaultClause extends Node { kind: SyntaxKind.DefaultClause; parent: CaseBlock; statements: NodeArray; + /* @internal */ fallthroughFlowNode?: FlowNode; } export type CaseOrDefaultClause = CaseClause | DefaultClause; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 342cb6989cd..2fb1173deef 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -961,6 +961,11 @@ namespace ts { break; case SyntaxKind.ArrowFunction: return getErrorSpanForArrowFunction(sourceFile, node); + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + const start = skipTrivia(sourceFile.text, (node).pos); + const end = (node).statements.length > 0 ? (node).statements[0].pos : (node).end; + return createTextSpanFromBounds(start, end); } if (errorNode === undefined) { diff --git a/tests/baselines/reference/fallFromLastCase2.errors.txt b/tests/baselines/reference/fallFromLastCase2.errors.txt index fcf3251ac3f..5800e4c7d88 100644 --- a/tests/baselines/reference/fallFromLastCase2.errors.txt +++ b/tests/baselines/reference/fallFromLastCase2.errors.txt @@ -11,7 +11,7 @@ tests/cases/compiler/fallFromLastCase2.ts(21,9): error TS7029: Fallthrough case use("1"); break; case 2: - ~~~~ + ~~~~~~~ !!! error TS7029: Fallthrough case in switch. use("2"); case 3: @@ -26,7 +26,7 @@ tests/cases/compiler/fallFromLastCase2.ts(21,9): error TS7029: Fallthrough case use("1"); break; default: - ~~~~~~~ + ~~~~~~~~ !!! error TS7029: Fallthrough case in switch. use("2"); case 2: diff --git a/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt b/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt index c2c572aa304..efd505800d9 100644 --- a/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt +++ b/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt @@ -7,7 +7,7 @@ tests/cases/compiler/a.js(19,1): error TS7028: Unused label. function foo(a, b) { switch (a) { case 10: - ~~~~ + ~~~~~~~~ !!! error TS7029: Fallthrough case in switch. if (b) { return b; diff --git a/tests/baselines/reference/reachabilityChecks4.errors.txt b/tests/baselines/reference/reachabilityChecks4.errors.txt index fdbb9d23ee8..4c20700f9dd 100644 --- a/tests/baselines/reference/reachabilityChecks4.errors.txt +++ b/tests/baselines/reference/reachabilityChecks4.errors.txt @@ -1,14 +1,15 @@ tests/cases/compiler/reachabilityChecks4.ts(6,9): error TS7029: Fallthrough case in switch. +tests/cases/compiler/reachabilityChecks4.ts(22,9): error TS7029: Fallthrough case in switch. -==== tests/cases/compiler/reachabilityChecks4.ts (1 errors) ==== +==== tests/cases/compiler/reachabilityChecks4.ts (2 errors) ==== function foo(x, y) { switch (x) { case 1: case 2: return 1; case 3: - ~~~~ + ~~~~~~~ !!! error TS7029: Fallthrough case in switch. if (y) { return 2; @@ -16,4 +17,58 @@ tests/cases/compiler/reachabilityChecks4.ts(6,9): error TS7029: Fallthrough case case 4: return 3; } - } \ No newline at end of file + } + + declare function noop(): void; + declare function fail(): never; + + function f1(x: 0 | 1 | 2) { + switch (x) { + case 0: + fail(); + case 1: + ~~~~~~~ +!!! error TS7029: Fallthrough case in switch. + noop(); + case 2: + return; + } + } + + // Repro from #34021 + + type Behavior = 'SLIDE' | 'SLIDE_OUT' + type Direction = 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' + + interface Transition { + behavior: Behavior + direction: Direction + } + + function f2(transition: Transition): any { + switch (transition.behavior) { + case 'SLIDE': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + case 'SLIDE_OUT': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks4.js b/tests/baselines/reference/reachabilityChecks4.js index de84c445cff..57175867532 100644 --- a/tests/baselines/reference/reachabilityChecks4.js +++ b/tests/baselines/reference/reachabilityChecks4.js @@ -11,7 +11,59 @@ function foo(x, y) { case 4: return 3; } -} +} + +declare function noop(): void; +declare function fail(): never; + +function f1(x: 0 | 1 | 2) { + switch (x) { + case 0: + fail(); + case 1: + noop(); + case 2: + return; + } +} + +// Repro from #34021 + +type Behavior = 'SLIDE' | 'SLIDE_OUT' +type Direction = 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' + +interface Transition { + behavior: Behavior + direction: Direction +} + +function f2(transition: Transition): any { + switch (transition.behavior) { + case 'SLIDE': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + case 'SLIDE_OUT': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + } +} + //// [reachabilityChecks4.js] function foo(x, y) { @@ -27,3 +79,39 @@ function foo(x, y) { return 3; } } +function f1(x) { + switch (x) { + case 0: + fail(); + case 1: + noop(); + case 2: + return; + } +} +function f2(transition) { + switch (transition.behavior) { + case 'SLIDE': + switch (transition.direction) { + case 'LEFT': + return []; + case 'RIGHT': + return []; + case 'TOP': + return []; + case 'BOTTOM': + return []; + } + case 'SLIDE_OUT': + switch (transition.direction) { + case 'LEFT': + return []; + case 'RIGHT': + return []; + case 'TOP': + return []; + case 'BOTTOM': + return []; + } + } +} diff --git a/tests/baselines/reference/reachabilityChecks4.symbols b/tests/baselines/reference/reachabilityChecks4.symbols index 76800cbcd86..6f68f15fda8 100644 --- a/tests/baselines/reference/reachabilityChecks4.symbols +++ b/tests/baselines/reference/reachabilityChecks4.symbols @@ -20,3 +20,93 @@ function foo(x, y) { return 3; } } + +declare function noop(): void; +>noop : Symbol(noop, Decl(reachabilityChecks4.ts, 12, 1)) + +declare function fail(): never; +>fail : Symbol(fail, Decl(reachabilityChecks4.ts, 14, 30)) + +function f1(x: 0 | 1 | 2) { +>f1 : Symbol(f1, Decl(reachabilityChecks4.ts, 15, 31)) +>x : Symbol(x, Decl(reachabilityChecks4.ts, 17, 12)) + + switch (x) { +>x : Symbol(x, Decl(reachabilityChecks4.ts, 17, 12)) + + case 0: + fail(); +>fail : Symbol(fail, Decl(reachabilityChecks4.ts, 14, 30)) + + case 1: + noop(); +>noop : Symbol(noop, Decl(reachabilityChecks4.ts, 12, 1)) + + case 2: + return; + } +} + +// Repro from #34021 + +type Behavior = 'SLIDE' | 'SLIDE_OUT' +>Behavior : Symbol(Behavior, Decl(reachabilityChecks4.ts, 26, 1)) + +type Direction = 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' +>Direction : Symbol(Direction, Decl(reachabilityChecks4.ts, 30, 37)) + +interface Transition { +>Transition : Symbol(Transition, Decl(reachabilityChecks4.ts, 31, 52)) + + behavior: Behavior +>behavior : Symbol(Transition.behavior, Decl(reachabilityChecks4.ts, 33, 22)) +>Behavior : Symbol(Behavior, Decl(reachabilityChecks4.ts, 26, 1)) + + direction: Direction +>direction : Symbol(Transition.direction, Decl(reachabilityChecks4.ts, 34, 20)) +>Direction : Symbol(Direction, Decl(reachabilityChecks4.ts, 30, 37)) +} + +function f2(transition: Transition): any { +>f2 : Symbol(f2, Decl(reachabilityChecks4.ts, 36, 1)) +>transition : Symbol(transition, Decl(reachabilityChecks4.ts, 38, 12)) +>Transition : Symbol(Transition, Decl(reachabilityChecks4.ts, 31, 52)) + + switch (transition.behavior) { +>transition.behavior : Symbol(Transition.behavior, Decl(reachabilityChecks4.ts, 33, 22)) +>transition : Symbol(transition, Decl(reachabilityChecks4.ts, 38, 12)) +>behavior : Symbol(Transition.behavior, Decl(reachabilityChecks4.ts, 33, 22)) + + case 'SLIDE': + switch (transition.direction) { +>transition.direction : Symbol(Transition.direction, Decl(reachabilityChecks4.ts, 34, 20)) +>transition : Symbol(transition, Decl(reachabilityChecks4.ts, 38, 12)) +>direction : Symbol(Transition.direction, Decl(reachabilityChecks4.ts, 34, 20)) + + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + case 'SLIDE_OUT': + switch (transition.direction) { +>transition.direction : Symbol(Transition.direction, Decl(reachabilityChecks4.ts, 34, 20)) +>transition : Symbol(transition, Decl(reachabilityChecks4.ts, 38, 12)) +>direction : Symbol(Transition.direction, Decl(reachabilityChecks4.ts, 34, 20)) + + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + } +} + diff --git a/tests/baselines/reference/reachabilityChecks4.types b/tests/baselines/reference/reachabilityChecks4.types index dbe834fb10a..27051b3550d 100644 --- a/tests/baselines/reference/reachabilityChecks4.types +++ b/tests/baselines/reference/reachabilityChecks4.types @@ -32,3 +32,130 @@ function foo(x, y) { >3 : 3 } } + +declare function noop(): void; +>noop : () => void + +declare function fail(): never; +>fail : () => never + +function f1(x: 0 | 1 | 2) { +>f1 : (x: 0 | 1 | 2) => void +>x : 0 | 1 | 2 + + switch (x) { +>x : 0 | 1 | 2 + + case 0: +>0 : 0 + + fail(); +>fail() : never +>fail : () => never + + case 1: +>1 : 1 + + noop(); +>noop() : void +>noop : () => void + + case 2: +>2 : 2 + + return; + } +} + +// Repro from #34021 + +type Behavior = 'SLIDE' | 'SLIDE_OUT' +>Behavior : Behavior + +type Direction = 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' +>Direction : Direction + +interface Transition { + behavior: Behavior +>behavior : Behavior + + direction: Direction +>direction : Direction +} + +function f2(transition: Transition): any { +>f2 : (transition: Transition) => any +>transition : Transition + + switch (transition.behavior) { +>transition.behavior : Behavior +>transition : Transition +>behavior : Behavior + + case 'SLIDE': +>'SLIDE' : "SLIDE" + + switch (transition.direction) { +>transition.direction : Direction +>transition : Transition +>direction : Direction + + case 'LEFT': +>'LEFT' : "LEFT" + + return [] +>[] : undefined[] + + case 'RIGHT': +>'RIGHT' : "RIGHT" + + return [] +>[] : undefined[] + + case 'TOP': +>'TOP' : "TOP" + + return [] +>[] : undefined[] + + case 'BOTTOM': +>'BOTTOM' : "BOTTOM" + + return [] +>[] : undefined[] + } + case 'SLIDE_OUT': +>'SLIDE_OUT' : "SLIDE_OUT" + + switch (transition.direction) { +>transition.direction : Direction +>transition : Transition +>direction : Direction + + case 'LEFT': +>'LEFT' : "LEFT" + + return [] +>[] : undefined[] + + case 'RIGHT': +>'RIGHT' : "RIGHT" + + return [] +>[] : undefined[] + + case 'TOP': +>'TOP' : "TOP" + + return [] +>[] : undefined[] + + case 'BOTTOM': +>'BOTTOM' : "BOTTOM" + + return [] +>[] : undefined[] + } + } +} + diff --git a/tests/cases/compiler/reachabilityChecks4.ts b/tests/cases/compiler/reachabilityChecks4.ts index f07395ac38e..a853a6e996a 100644 --- a/tests/cases/compiler/reachabilityChecks4.ts +++ b/tests/cases/compiler/reachabilityChecks4.ts @@ -12,4 +12,55 @@ function foo(x, y) { case 4: return 3; } -} \ No newline at end of file +} + +declare function noop(): void; +declare function fail(): never; + +function f1(x: 0 | 1 | 2) { + switch (x) { + case 0: + fail(); + case 1: + noop(); + case 2: + return; + } +} + +// Repro from #34021 + +type Behavior = 'SLIDE' | 'SLIDE_OUT' +type Direction = 'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM' + +interface Transition { + behavior: Behavior + direction: Direction +} + +function f2(transition: Transition): any { + switch (transition.behavior) { + case 'SLIDE': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + case 'SLIDE_OUT': + switch (transition.direction) { + case 'LEFT': + return [] + case 'RIGHT': + return [] + case 'TOP': + return [] + case 'BOTTOM': + return [] + } + } +}