From 9728bb2ba831d7d70e82f457ac83346d0872fdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timon=20G=C3=A4rtner?= Date: Thu, 29 Oct 2026 02:24:26 +0100 Subject: [PATCH] Report an error for class declarations in statement position A class declaration used directly as the body of an if, else, loop or labeled statement is a syntax error at runtime, but was accepted without error. Report TS1156, as is already done for interface and type alias declarations. Fixes #22682 --- tsc/internal/checker/checker.go | 3 + ...sDeclarationInStatementPosition.errors.txt | 66 +++++++++++++ .../classDeclarationInStatementPosition.js | 97 +++++++++++++++++++ ...lassDeclarationInStatementPosition.symbols | 82 ++++++++++++++++ .../classDeclarationInStatementPosition.types | 93 ++++++++++++++++++ .../classDeclarationInStatementPosition.ts | 39 ++++++++ 6 files changed, 380 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.js create mode 100644 tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.types create mode 100644 tsc/testdata/tests/cases/compiler/classDeclarationInStatementPosition.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d689a6453e8a6..086779ceb115a 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -4343,6 +4343,9 @@ func (c *Checker) checkClassDeclaration(node *ast.Node) { if node.Name() == nil && !ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) { c.grammarErrorOnFirstToken(node, diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name) } + if !c.containerAllowsBlockScopedVariable(node.Parent) { + c.grammarErrorOnNode(node, diagnostics.X_0_declarations_can_only_be_declared_inside_a_block, "class") + } c.checkClassLikeDeclaration(node) c.checkSourceElements(node.Members()) c.checkConstructorDeclaredProperties(node) diff --git a/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.errors.txt b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.errors.txt new file mode 100644 index 0000000000000..709cbdefdad27 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.errors.txt @@ -0,0 +1,66 @@ +a.ts(3,17): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(4,25): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(5,20): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(6,10): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(7,16): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(8,27): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(9,27): error TS1156: 'class' declarations can only be declared inside a block. +a.ts(10,11): error TS1344: A label is not allowed here. +a.ts(10,24): error TS1156: 'class' declarations can only be declared inside a block. +b.js(1,26): error TS1156: 'class' declarations can only be declared inside a block. + + +==== a.ts (9 errors) ==== + declare const cond: boolean; + + if (cond) class C1 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + if (cond) {} else class C2 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + while (cond) class C3 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + do class C4 {} while (cond); + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + for (;;) class C5 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + for (const k in {}) class C6 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + for (const v of []) class C7 {} + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + if (cond) label: class C8 {} + ~~~~~ +!!! error TS1344: A label is not allowed here. + ~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + + // OK + if (cond) { + class C10 {} + } + class C11 {} + switch (0) { + case 0: + class C12 {} + } + namespace N { + class C13 {} + } + function f() { + class C14 {} + return class C15 {}; + } + let x; + if (cond) x = class C16 {}; + +==== b.js (1 errors) ==== + if (Math.random()) class C17 {} + ~~~ +!!! error TS1156: 'class' declarations can only be declared inside a block. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.js b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.js new file mode 100644 index 0000000000000..160a4976bfb20 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.js @@ -0,0 +1,97 @@ +//// [tests/cases/compiler/classDeclarationInStatementPosition.ts] //// + +//// [a.ts] +declare const cond: boolean; + +if (cond) class C1 {} +if (cond) {} else class C2 {} +while (cond) class C3 {} +do class C4 {} while (cond); +for (;;) class C5 {} +for (const k in {}) class C6 {} +for (const v of []) class C7 {} +if (cond) label: class C8 {} + +// OK +if (cond) { + class C10 {} +} +class C11 {} +switch (0) { + case 0: + class C12 {} +} +namespace N { + class C13 {} +} +function f() { + class C14 {} + return class C15 {}; +} +let x; +if (cond) x = class C16 {}; + +//// [b.js] +if (Math.random()) class C17 {} + + +//// [a.js] +"use strict"; +if (cond) + class C1 { + } +if (cond) { } +else + class C2 { + } +while (cond) + class C3 { + } +do + class C4 { + } +while (cond); +for (;;) + class C5 { + } +for (const k in {}) + class C6 { + } +for (const v of []) + class C7 { + } +if (cond) + label: class C8 { + } +// OK +if (cond) { + class C10 { + } +} +class C11 { +} +switch (0) { + case 0: + class C12 { + } +} +var N; +(function (N) { + class C13 { + } +})(N || (N = {})); +function f() { + class C14 { + } + return class C15 { + }; +} +let x; +if (cond) + x = class C16 { + }; +//// [b.js] +"use strict"; +if (Math.random()) + class C17 { + } diff --git a/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.symbols b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.symbols new file mode 100644 index 0000000000000..9ae14066a31ab --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.symbols @@ -0,0 +1,82 @@ +//// [tests/cases/compiler/classDeclarationInStatementPosition.ts] //// + +=== a.ts === +declare const cond: boolean; +>cond : Symbol(cond, Decl(a.ts, 0, 13)) + +if (cond) class C1 {} +>cond : Symbol(cond, Decl(a.ts, 0, 13)) +>C1 : Symbol(C1, Decl(a.ts, 2, 9)) + +if (cond) {} else class C2 {} +>cond : Symbol(cond, Decl(a.ts, 0, 13)) +>C2 : Symbol(C2, Decl(a.ts, 3, 17)) + +while (cond) class C3 {} +>cond : Symbol(cond, Decl(a.ts, 0, 13)) +>C3 : Symbol(C3, Decl(a.ts, 4, 12)) + +do class C4 {} while (cond); +>C4 : Symbol(C4, Decl(a.ts, 5, 2)) +>cond : Symbol(cond, Decl(a.ts, 0, 13)) + +for (;;) class C5 {} +>C5 : Symbol(C5, Decl(a.ts, 6, 8)) + +for (const k in {}) class C6 {} +>k : Symbol(k, Decl(a.ts, 7, 10)) +>C6 : Symbol(C6, Decl(a.ts, 7, 19)) + +for (const v of []) class C7 {} +>v : Symbol(v, Decl(a.ts, 8, 10)) +>C7 : Symbol(C7, Decl(a.ts, 8, 19)) + +if (cond) label: class C8 {} +>cond : Symbol(cond, Decl(a.ts, 0, 13)) +>C8 : Symbol(C8, Decl(a.ts, 9, 16)) + +// OK +if (cond) { +>cond : Symbol(cond, Decl(a.ts, 0, 13)) + + class C10 {} +>C10 : Symbol(C10, Decl(a.ts, 12, 11)) +} +class C11 {} +>C11 : Symbol(C11, Decl(a.ts, 14, 1)) + +switch (0) { + case 0: + class C12 {} +>C12 : Symbol(C12, Decl(a.ts, 17, 11)) +} +namespace N { +>N : Symbol(N, Decl(a.ts, 19, 1)) + + class C13 {} +>C13 : Symbol(C13, Decl(a.ts, 20, 13)) +} +function f() { +>f : Symbol(f, Decl(a.ts, 22, 1)) + + class C14 {} +>C14 : Symbol(C14, Decl(a.ts, 23, 14)) + + return class C15 {}; +>C15 : Symbol(C15, Decl(a.ts, 25, 10)) +} +let x; +>x : Symbol(x, Decl(a.ts, 27, 3)) + +if (cond) x = class C16 {}; +>cond : Symbol(cond, Decl(a.ts, 0, 13)) +>x : Symbol(x, Decl(a.ts, 27, 3)) +>C16 : Symbol(C16, Decl(a.ts, 28, 13)) + +=== b.js === +if (Math.random()) class C17 {} +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>C17 : Symbol(C17, Decl(b.js, 0, 18)) + diff --git a/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.types b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.types new file mode 100644 index 0000000000000..6d29db04862f5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/classDeclarationInStatementPosition.types @@ -0,0 +1,93 @@ +//// [tests/cases/compiler/classDeclarationInStatementPosition.ts] //// + +=== a.ts === +declare const cond: boolean; +>cond : boolean + +if (cond) class C1 {} +>cond : boolean +>C1 : C1 + +if (cond) {} else class C2 {} +>cond : boolean +>C2 : C2 + +while (cond) class C3 {} +>cond : boolean +>C3 : C3 + +do class C4 {} while (cond); +>C4 : C4 +>cond : false + +for (;;) class C5 {} +>C5 : C5 + +for (const k in {}) class C6 {} +>k : string +>{} : {} +>C6 : C6 + +for (const v of []) class C7 {} +>v : never +>[] : never[] +>C7 : C7 + +if (cond) label: class C8 {} +>cond : boolean +>label : any +>C8 : C8 + +// OK +if (cond) { +>cond : boolean + + class C10 {} +>C10 : C10 +} +class C11 {} +>C11 : C11 + +switch (0) { +>0 : 0 + + case 0: +>0 : 0 + + class C12 {} +>C12 : C12 +} +namespace N { +>N : typeof N + + class C13 {} +>C13 : C13 +} +function f() { +>f : () => typeof C15 + + class C14 {} +>C14 : C14 + + return class C15 {}; +>class C15 {} : typeof C15 +>C15 : typeof C15 +} +let x; +>x : any + +if (cond) x = class C16 {}; +>cond : boolean +>x = class C16 {} : typeof C16 +>x : any +>class C16 {} : typeof C16 +>C16 : typeof C16 + +=== b.js === +if (Math.random()) class C17 {} +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>C17 : C17 + diff --git a/tsc/testdata/tests/cases/compiler/classDeclarationInStatementPosition.ts b/tsc/testdata/tests/cases/compiler/classDeclarationInStatementPosition.ts new file mode 100644 index 0000000000000..b1a4126e5bce4 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/classDeclarationInStatementPosition.ts @@ -0,0 +1,39 @@ +// @target: es2015 +// @allowJs: true +// @outDir: out + +// https://github.com/microsoft/TypeScript/issues/22682 + +// @filename: a.ts +declare const cond: boolean; + +if (cond) class C1 {} +if (cond) {} else class C2 {} +while (cond) class C3 {} +do class C4 {} while (cond); +for (;;) class C5 {} +for (const k in {}) class C6 {} +for (const v of []) class C7 {} +if (cond) label: class C8 {} + +// OK +if (cond) { + class C10 {} +} +class C11 {} +switch (0) { + case 0: + class C12 {} +} +namespace N { + class C13 {} +} +function f() { + class C14 {} + return class C15 {}; +} +let x; +if (cond) x = class C16 {}; + +// @filename: b.js +if (Math.random()) class C17 {}