Skip to content

Commit 97aba45

Browse files
authored
Emit unused identifier suggestion diagnostics in declaration files and ambient nodes (microsoft#35119)
1 parent 7c14aff commit 97aba45

File tree

4 files changed

+94
-23
lines changed

4 files changed

+94
-23
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -589,21 +589,16 @@ namespace ts {
589589
Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked));
590590

591591
diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName));
592-
if (!file.isDeclarationFile && (!unusedIsError(UnusedKind.Local) || !unusedIsError(UnusedKind.Parameter))) {
593-
addUnusedDiagnostics();
594-
}
595-
return diagnostics || emptyArray;
596-
}
597-
finally {
598-
cancellationToken = undefined;
599-
}
600-
601-
function addUnusedDiagnostics() {
602592
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => {
603-
if (!containsParseError(containingNode) && !unusedIsError(kind)) {
593+
if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) {
604594
(diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion });
605595
}
606596
});
597+
598+
return diagnostics || emptyArray;
599+
}
600+
finally {
601+
cancellationToken = undefined;
607602
}
608603
},
609604

@@ -29611,7 +29606,7 @@ namespace ts {
2961129606

2961229607
function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void {
2961329608
// May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`.
29614-
if (produceDiagnostics && !(node.flags & NodeFlags.Ambient)) {
29609+
if (produceDiagnostics) {
2961529610
const sourceFile = getSourceFileOfNode(node);
2961629611
let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path);
2961729612
if (!potentiallyUnusedIdentifiers) {
@@ -29778,8 +29773,6 @@ namespace ts {
2977829773
}
2977929774

2978029775
function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void {
29781-
if (nodeWithLocals.flags & NodeFlags.Ambient) return;
29782-
2978329776
// Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value.
2978429777
const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>();
2978529778
const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>();
@@ -33151,7 +33144,10 @@ namespace ts {
3315133144
performance.measure("Check", "beforeCheck", "afterCheck");
3315233145
}
3315333146

33154-
function unusedIsError(kind: UnusedKind): boolean {
33147+
function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean {
33148+
if (isAmbient) {
33149+
return false;
33150+
}
3315533151
switch (kind) {
3315633152
case UnusedKind.Local:
3315733153
return !!compilerOptions.noUnusedLocals;
@@ -33191,7 +33187,7 @@ namespace ts {
3319133187

3319233188
if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) {
3319333189
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => {
33194-
if (!containsParseError(containingNode) && unusedIsError(kind)) {
33190+
if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) {
3319533191
diagnostics.add(diag);
3319633192
}
3319733193
});

src/harness/fourslash.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,13 +1135,19 @@ namespace FourSlash {
11351135
}
11361136

11371137
private testDiagnostics(expected: readonly FourSlashInterface.Diagnostic[], diagnostics: readonly ts.Diagnostic[], category: string) {
1138-
assert.deepEqual(ts.realizeDiagnostics(diagnostics, "\n"), expected.map((e): ts.RealizedDiagnostic => ({
1139-
message: e.message,
1140-
category,
1141-
code: e.code,
1142-
...ts.createTextSpanFromRange(e.range || this.getRanges()[0]),
1143-
reportsUnnecessary: e.reportsUnnecessary,
1144-
})));
1138+
assert.deepEqual(ts.realizeDiagnostics(diagnostics, "\n"), expected.map((e): ts.RealizedDiagnostic => {
1139+
const range = e.range || this.getRangesInFile()[0];
1140+
if (!range) {
1141+
this.raiseError("Must provide a range for each expected diagnostic, or have one range in the fourslash source.");
1142+
}
1143+
return {
1144+
message: e.message,
1145+
category,
1146+
code: e.code,
1147+
...ts.createTextSpanFromRange(range),
1148+
reportsUnnecessary: e.reportsUnnecessary,
1149+
};
1150+
}));
11451151
}
11461152

11471153
public verifyQuickInfoAt(markerName: string | Range, expectedText: string, expectedDocumentation?: string) {
@@ -2134,6 +2140,10 @@ namespace FourSlash {
21342140
return this.testData.ranges;
21352141
}
21362142

2143+
public getRangesInFile(fileName = this.activeFile.fileName) {
2144+
return this.getRanges().filter(r => r.fileName === fileName);
2145+
}
2146+
21372147
public rangesByText(): ts.Map<Range[]> {
21382148
if (this.testData.rangesByText) return this.testData.rangesByText;
21392149
const result = ts.createMultiMap<Range>();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.ts
4+
////export {};
5+
////declare var [|a|]: string;
6+
7+
// @Filename: /b.d.ts
8+
////declare module 'b' {
9+
//// var [|b|]: string;
10+
//// export {};
11+
////}
12+
13+
goTo.file("/a.ts");
14+
verify.getSuggestionDiagnostics([{
15+
code: ts.Diagnostics._0_is_declared_but_its_value_is_never_read.code,
16+
message: "'a' is declared but its value is never read.",
17+
reportsUnnecessary: true
18+
}]);
19+
verify.getSemanticDiagnostics([]);
20+
21+
goTo.file("/b.d.ts");
22+
verify.getSuggestionDiagnostics([{
23+
code: ts.Diagnostics._0_is_declared_but_its_value_is_never_read.code,
24+
message: "'b' is declared but its value is never read.",
25+
reportsUnnecessary: true
26+
}]);
27+
verify.getSemanticDiagnostics([]);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters: true
5+
6+
// @Filename: /a.ts
7+
////export {};
8+
////declare var [|a|]: string;
9+
10+
// @Filename: /b.d.ts
11+
////declare module 'b' {
12+
//// var [|b|]: string;
13+
//// function [|f|]([|p|]: string): any;
14+
//// export {};
15+
////}
16+
17+
goTo.file("/a.ts");
18+
verify.getSuggestionDiagnostics([{
19+
code: ts.Diagnostics._0_is_declared_but_its_value_is_never_read.code,
20+
message: "'a' is declared but its value is never read.",
21+
reportsUnnecessary: true
22+
}]);
23+
verify.getSemanticDiagnostics([]);
24+
25+
goTo.file("/b.d.ts");
26+
verify.getSuggestionDiagnostics([{
27+
range: test.rangesByText().get("b")[0],
28+
code: ts.Diagnostics._0_is_declared_but_its_value_is_never_read.code,
29+
message: "'b' is declared but its value is never read.",
30+
reportsUnnecessary: true
31+
}, {
32+
range: test.rangesByText().get("f")[0],
33+
code: ts.Diagnostics._0_is_declared_but_its_value_is_never_read.code,
34+
message: "'f' is declared but its value is never read.",
35+
reportsUnnecessary: true
36+
}]);
37+
38+
verify.getSemanticDiagnostics([]);

0 commit comments

Comments
 (0)