Skip to content

Commit 982c8d0

Browse files
author
Andy
authored
Add suggestion diagnostics for unused label and unreachable code (#24261)
* Add suggestion diagnostics for unused label and unreachable code * Always error on unused left hand side of comma
1 parent f52c4af commit 982c8d0

File tree

66 files changed

+164
-582
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+164
-582
lines changed

src/compiler/binder.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ namespace ts {
12081208
bind(node.statement);
12091209
popActiveLabel();
12101210
if (!activeLabel.referenced && !options.allowUnusedLabels) {
1211-
file.bindDiagnostics.push(createDiagnosticForNode(node.label, Diagnostics.Unused_label));
1211+
errorOrSuggestionOnFirstToken(unusedLabelIsError(options), node, Diagnostics.Unused_label);
12121212
}
12131213
if (!node.statement || node.statement.kind !== SyntaxKind.DoStatement) {
12141214
// do statement sets current flow inside bindDoStatement
@@ -1914,6 +1914,17 @@ namespace ts {
19141914
file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2));
19151915
}
19161916

1917+
function errorOrSuggestionOnFirstToken(isError: boolean, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) {
1918+
const span = getSpanOfTokenAtPosition(file, node.pos);
1919+
const diag = createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2);
1920+
if (isError) {
1921+
file.bindDiagnostics.push(diag);
1922+
}
1923+
else {
1924+
file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion });
1925+
}
1926+
}
1927+
19171928
function bind(node: Node): void {
19181929
if (!node) {
19191930
return;
@@ -2730,26 +2741,26 @@ namespace ts {
27302741
if (reportError) {
27312742
currentFlow = reportedUnreachableFlow;
27322743

2733-
// unreachable code is reported if
2734-
// - user has explicitly asked about it AND
2735-
// - statement is in not ambient context (statements in ambient context is already an error
2736-
// so we should not report extras) AND
2737-
// - node is not variable statement OR
2738-
// - node is block scoped variable statement OR
2739-
// - node is not block scoped variable statement and at least one variable declaration has initializer
2740-
// Rationale: we don't want to report errors on non-initialized var's since they are hoisted
2741-
// On the other side we do want to report errors on non-initialized 'lets' because of TDZ
2742-
const reportUnreachableCode =
2743-
!options.allowUnreachableCode &&
2744-
!(node.flags & NodeFlags.Ambient) &&
2745-
(
2746-
node.kind !== SyntaxKind.VariableStatement ||
2747-
getCombinedNodeFlags((<VariableStatement>node).declarationList) & NodeFlags.BlockScoped ||
2748-
forEach((<VariableStatement>node).declarationList.declarations, d => d.initializer)
2749-
);
2750-
2751-
if (reportUnreachableCode) {
2752-
errorOnFirstToken(node, Diagnostics.Unreachable_code_detected);
2744+
if (!options.allowUnreachableCode) {
2745+
// unreachable code is reported if
2746+
// - user has explicitly asked about it AND
2747+
// - statement is in not ambient context (statements in ambient context is already an error
2748+
// so we should not report extras) AND
2749+
// - node is not variable statement OR
2750+
// - node is block scoped variable statement OR
2751+
// - node is not block scoped variable statement and at least one variable declaration has initializer
2752+
// Rationale: we don't want to report errors on non-initialized var's since they are hoisted
2753+
// On the other side we do want to report errors on non-initialized 'lets' because of TDZ
2754+
const isError =
2755+
unreachableCodeIsError(options) &&
2756+
!(node.flags & NodeFlags.Ambient) &&
2757+
(
2758+
!isVariableStatement(node) ||
2759+
!!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) ||
2760+
node.declarationList.declarations.some(d => !!d.initializer)
2761+
);
2762+
2763+
errorOrSuggestionOnFirstToken(isError, node, Diagnostics.Unreachable_code_detected);
27532764
}
27542765
}
27552766
}

src/compiler/core.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,14 @@ namespace ts {
19911991
return moduleResolution;
19921992
}
19931993

1994+
export function unreachableCodeIsError(options: CompilerOptions): boolean {
1995+
return options.allowUnreachableCode === false;
1996+
}
1997+
1998+
export function unusedLabelIsError(options: CompilerOptions): boolean {
1999+
return options.allowUnusedLabels === false;
2000+
}
2001+
19942002
export function getAreDeclarationMapsEnabled(options: CompilerOptions) {
19952003
return !!(options.declaration && options.declarationMap);
19962004
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,7 +2238,8 @@
22382238
},
22392239
"Left side of comma operator is unused and has no side effects.": {
22402240
"category": "Error",
2241-
"code": 2695
2241+
"code": 2695,
2242+
"reportsUnnecessary": true
22422243
},
22432244
"The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?": {
22442245
"category": "Error",
@@ -3673,7 +3674,8 @@
36733674
},
36743675
"Unreachable code detected.": {
36753676
"category": "Error",
3676-
"code": 7027
3677+
"code": 7027,
3678+
"reportsUnnecessary": true
36773679
},
36783680
"Unused label.": {
36793681
"category": "Error",

src/compiler/factory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2463,6 +2463,7 @@ namespace ts {
24632463
if (node.symbolCount !== undefined) updated.symbolCount = node.symbolCount;
24642464
if (node.parseDiagnostics !== undefined) updated.parseDiagnostics = node.parseDiagnostics;
24652465
if (node.bindDiagnostics !== undefined) updated.bindDiagnostics = node.bindDiagnostics;
2466+
if (node.bindSuggestionDiagnostics !== undefined) updated.bindSuggestionDiagnostics = node.bindSuggestionDiagnostics;
24662467
if (node.lineMap !== undefined) updated.lineMap = node.lineMap;
24672468
if (node.classifiableNames !== undefined) updated.classifiableNames = node.classifiableNames;
24682469
if (node.resolvedModules !== undefined) updated.resolvedModules = node.resolvedModules;

src/compiler/parser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ namespace ts {
912912

913913
sourceFile.text = sourceText;
914914
sourceFile.bindDiagnostics = [];
915+
sourceFile.bindSuggestionDiagnostics = undefined;
915916
sourceFile.languageVersion = languageVersion;
916917
sourceFile.fileName = normalizePath(fileName);
917918
sourceFile.languageVariant = getLanguageVariant(scriptKind);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,6 +2603,7 @@ namespace ts {
26032603

26042604
// File-level diagnostics reported by the binder.
26052605
/* @internal */ bindDiagnostics: Diagnostic[];
2606+
/* @internal */ bindSuggestionDiagnostics?: Diagnostic[];
26062607

26072608
// File-level JSDoc diagnostics reported by the JSDoc parser
26082609
/* @internal */ jsDocDiagnostics?: Diagnostic[];

src/harness/fourslash.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,8 +1317,13 @@ Actual: ${stringify(fullActual)}`);
13171317
}
13181318

13191319
private testDiagnostics(expected: ReadonlyArray<FourSlashInterface.Diagnostic>, diagnostics: ReadonlyArray<ts.Diagnostic>, category: string) {
1320-
assert.deepEqual(ts.realizeDiagnostics(diagnostics, ts.newLineCharacter), expected.map<ts.RealizedDiagnostic>(e => (
1321-
{ message: e.message, category, code: e.code, ...ts.createTextSpanFromRange(e.range || this.getRanges()[0]) })));
1320+
assert.deepEqual(ts.realizeDiagnostics(diagnostics, ts.newLineCharacter), expected.map((e): ts.RealizedDiagnostic => ({
1321+
message: e.message,
1322+
category,
1323+
code: e.code,
1324+
...ts.createTextSpanFromRange(e.range || this.getRanges()[0]),
1325+
reportsUnnecessary: e.reportsUnnecessary,
1326+
})));
13221327
}
13231328

13241329
public verifyQuickInfoAt(markerName: string, expectedText: string, expectedDocumentation?: string) {
@@ -4422,15 +4427,15 @@ namespace FourSlashInterface {
44224427
this.state.verifyQuickInfoDisplayParts(kind, kindModifiers, textSpan, displayParts, documentation, tags);
44234428
}
44244429

4425-
public getSyntacticDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>) {
4430+
public getSyntacticDiagnostics(expected: ReadonlyArray<Diagnostic>) {
44264431
this.state.getSyntacticDiagnostics(expected);
44274432
}
44284433

4429-
public getSemanticDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>) {
4434+
public getSemanticDiagnostics(expected: ReadonlyArray<Diagnostic>) {
44304435
this.state.getSemanticDiagnostics(expected);
44314436
}
44324437

4433-
public getSuggestionDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>) {
4438+
public getSuggestionDiagnostics(expected: ReadonlyArray<Diagnostic>) {
44344439
this.state.getSuggestionDiagnostics(expected);
44354440
}
44364441

@@ -4837,6 +4842,7 @@ namespace FourSlashInterface {
48374842
message: string;
48384843
range?: FourSlash.Range;
48394844
code: number;
4845+
reportsUnnecessary?: true;
48404846
}
48414847

48424848
export interface GetEditsForFileRenameOptions {

src/services/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ namespace ts {
548548
public syntacticDiagnostics: Diagnostic[];
549549
public parseDiagnostics: Diagnostic[];
550550
public bindDiagnostics: Diagnostic[];
551+
public bindSuggestionDiagnostics?: Diagnostic[];
551552

552553
public isDeclarationFile: boolean;
553554
public isDefaultLib: boolean;

src/services/shims.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ namespace ts {
586586
length: number;
587587
category: string;
588588
code: number;
589-
unused?: {};
589+
reportsUnnecessary?: {};
590590
}
591591
export function realizeDiagnostics(diagnostics: ReadonlyArray<Diagnostic>, newLine: string): RealizedDiagnostic[] {
592592
return diagnostics.map(d => realizeDiagnostic(d, newLine));
@@ -598,7 +598,8 @@ namespace ts {
598598
start: diagnostic.start,
599599
length: diagnostic.length,
600600
category: diagnosticCategoryName(diagnostic),
601-
code: diagnostic.code
601+
code: diagnostic.code,
602+
reportsUnnecessary: diagnostic.reportsUnnecessary,
602603
};
603604
}
604605

src/services/suggestionDiagnostics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace ts {
6060
}
6161
}
6262

63+
addRange(diags, sourceFile.bindSuggestionDiagnostics);
6364
return diags.concat(checker.getSuggestionDiagnostics(sourceFile)).sort((d1, d2) => d1.start - d2.start);
6465
}
6566

tests/baselines/reference/assignmentLHSIsValue.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(2
1313
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(30,1): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1414
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(31,1): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1515
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(32,1): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
16-
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(35,3): error TS7028: Unused label.
1716
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(35,9): error TS1128: Declaration or statement expected.
1817
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(38,2): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1918
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(38,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
@@ -39,7 +38,7 @@ tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(6
3938
tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(70,1): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
4039

4140

42-
==== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts (39 errors) ====
41+
==== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts (38 errors) ====
4342
// expected error for all the LHS of assignments
4443
var value: any;
4544

@@ -105,8 +104,6 @@ tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(7
105104

106105
// object literals
107106
{ a: 0} = value;
108-
~
109-
!!! error TS7028: Unused label.
110107
~
111108
!!! error TS1128: Declaration or statement expected.
112109

tests/baselines/reference/blockScopedBindingUsedBeforeDef.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(2,12): error TS2448: Block-scoped variable 'a' used before its declaration.
22
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,12): error TS2448: Block-scoped variable 'a' used before its declaration.
3-
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,35): error TS7027: Unreachable code detected.
43
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2448: Block-scoped variable 'b' used before its declaration.
54

65

7-
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (4 errors) ====
6+
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (3 errors) ====
87
// 1:
98
for (let {[a]: a} of [{ }]) continue;
109
~
@@ -14,8 +13,6 @@ tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2448: Bloc
1413
for (let {[a]: a} = { }; false; ) continue;
1514
~
1615
!!! error TS2448: Block-scoped variable 'a' used before its declaration.
17-
~~~~~~~~
18-
!!! error TS7027: Unreachable code detected.
1916

2017
// 3:
2118
let {[b]: b} = { };

tests/baselines/reference/cf.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ tests/cases/compiler/cf.ts(36,13): error TS7027: Unreachable code detected.
5353
}
5454
catch (e) {
5555
x++;
56-
}
56+
}
5757
finally {
5858
x+=3;
5959
}

tests/baselines/reference/cf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function f() {
3939
}
4040
catch (e) {
4141
x++;
42-
}
42+
}
4343
finally {
4444
x+=3;
4545
}

tests/baselines/reference/cf.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function f() {
7777

7878
x++;
7979
>x : Symbol(x, Decl(cf.ts, 2, 7))
80-
}
80+
}
8181
finally {
8282
x+=3;
8383
>x : Symbol(x, Decl(cf.ts, 2, 7))

tests/baselines/reference/cf.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function f() {
121121
x++;
122122
>x++ : number
123123
>x : number
124-
}
124+
}
125125
finally {
126126
x+=3;
127127
>x+=3 : number

tests/baselines/reference/compoundExponentiationAssignmentLHSIsValue.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignm
1414
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(38,1): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1515
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(39,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
1616
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(40,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
17-
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(43,3): error TS7028: Unused label.
1817
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(43,10): error TS1128: Declaration or statement expected.
1918
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(46,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
2019
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(52,15): error TS1034: 'super' must be followed by an argument list or member access.
@@ -40,7 +39,7 @@ tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignm
4039
tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts(85,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
4140

4241

43-
==== tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts (40 errors) ====
42+
==== tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignmentLHSIsValue.ts (39 errors) ====
4443
// expected error for all the LHS of compound assignments (arithmetic and addition)
4544
var value: any;
4645

@@ -116,8 +115,6 @@ tests/cases/conformance/es7/exponentiationOperator/compoundExponentiationAssignm
116115

117116
// object literals
118117
{ a: 0 } **= value;
119-
~
120-
!!! error TS7028: Unused label.
121118
~~~
122119
!!! error TS1128: Declaration or statement expected.
123120

tests/baselines/reference/constDeclarations-scopes.errors.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
tests/cases/compiler/constDeclarations-scopes.ts(12,5): error TS7027: Unreachable code detected.
2-
tests/cases/compiler/constDeclarations-scopes.ts(21,1): error TS7027: Unreachable code detected.
31
tests/cases/compiler/constDeclarations-scopes.ts(27,1): error TS2410: The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
42

53

6-
==== tests/cases/compiler/constDeclarations-scopes.ts (3 errors) ====
4+
==== tests/cases/compiler/constDeclarations-scopes.ts (1 errors) ====
75
// global
86
const c = "string";
97

@@ -16,8 +14,6 @@ tests/cases/compiler/constDeclarations-scopes.ts(27,1): error TS2410: The 'with'
1614
}
1715
else {
1816
const c = 0;
19-
~~~~~
20-
!!! error TS7027: Unreachable code detected.
2117
n = c;
2218
}
2319

@@ -27,8 +23,6 @@ tests/cases/compiler/constDeclarations-scopes.ts(27,1): error TS2410: The 'with'
2723
}
2824

2925
do {
30-
~~
31-
!!! error TS7027: Unreachable code detected.
3226
const c = 0;
3327
n = c;
3428
} while (true);

tests/baselines/reference/declarationEmitInvalidExport.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
tests/cases/compiler/declarationEmitInvalidExport.ts(2,3): error TS7027: Unreachable code detected.
21
tests/cases/compiler/declarationEmitInvalidExport.ts(4,30): error TS4081: Exported type alias 'MyClass' has or is using private name 'myClass'.
32
tests/cases/compiler/declarationEmitInvalidExport.ts(5,1): error TS1128: Declaration or statement expected.
43

54

6-
==== tests/cases/compiler/declarationEmitInvalidExport.ts (3 errors) ====
5+
==== tests/cases/compiler/declarationEmitInvalidExport.ts (2 errors) ====
76
if (false) {
87
export var myClass = 0;
9-
~~~~~~
10-
!!! error TS7027: Unreachable code detected.
118
}
129
export type MyClass = typeof myClass;
1310
~~~~~~~

0 commit comments

Comments
 (0)