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

66 files changed

Lines changed: 164 additions & 582 deletions

File tree

Some content is hidden

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

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

0 commit comments

Comments
 (0)