Skip to content

Commit d40d549

Browse files
author
Andy
authored
Support deleting all unused type parameters in a list, and deleting @template tag (microsoft#25748)
* Support deleting all unused type parameters in a list, and deleting @template tag * Support type parameter in 'infer'
1 parent 3bfe91c commit d40d549

File tree

58 files changed

+447
-183
lines changed

Some content is hidden

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

58 files changed

+447
-183
lines changed

src/compiler/checker.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22613,6 +22613,7 @@ namespace ts {
2261322613
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
2261422614
}
2261522615
checkSourceElement(node.typeParameter);
22616+
registerForUnusedIdentifiersCheck(node);
2261622617
}
2261722618

2261822619
function checkImportType(node: ImportTypeNode) {
@@ -23641,7 +23642,8 @@ namespace ts {
2364123642
type PotentiallyUnusedIdentifier =
2364223643
| SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration
2364323644
| Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement
23644-
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration;
23645+
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration
23646+
| InferTypeNode;
2364523647

2364623648
function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: ReadonlyArray<PotentiallyUnusedIdentifier>, addDiagnostic: AddUnusedDiagnostic) {
2364723649
for (const node of potentiallyUnusedIdentifiers) {
@@ -23681,6 +23683,7 @@ namespace ts {
2368123683
case SyntaxKind.FunctionType:
2368223684
case SyntaxKind.ConstructorType:
2368323685
case SyntaxKind.TypeAliasDeclaration:
23686+
case SyntaxKind.InferType:
2368423687
checkUnusedTypeParameters(node, addDiagnostic);
2368523688
break;
2368623689
default:
@@ -23734,21 +23737,48 @@ namespace ts {
2373423737
}
2373523738
}
2373623739

23737-
function checkUnusedTypeParameters(
23738-
node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration,
23739-
addDiagnostic: AddUnusedDiagnostic,
23740-
): void {
23740+
function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void {
2374123741
// Only report errors on the last declaration for the type parameter container;
2374223742
// this ensures that all uses have been accounted for.
23743-
const typeParameters = getEffectiveTypeParameterDeclarations(node);
23744-
if (!(node.flags & NodeFlags.Ambient) && last(getSymbolOfNode(node).declarations) === node) {
23743+
if (node.flags & NodeFlags.Ambient || node.kind !== SyntaxKind.InferType && last(getSymbolOfNode(node).declarations) !== node) return;
23744+
23745+
if (node.kind === SyntaxKind.InferType) {
23746+
const { typeParameter } = node;
23747+
if (isTypeParameterUnused(typeParameter)) {
23748+
addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name)));
23749+
}
23750+
}
23751+
else {
23752+
const typeParameters = getEffectiveTypeParameterDeclarations(node);
23753+
const seenParentsWithEveryUnused = new NodeSet<DeclarationWithTypeParameterChildren>();
23754+
2374523755
for (const typeParameter of typeParameters) {
23746-
if (!(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name)) {
23747-
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol)));
23756+
if (!isTypeParameterUnused(typeParameter)) continue;
23757+
23758+
const name = idText(typeParameter.name);
23759+
const { parent } = typeParameter;
23760+
if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) {
23761+
if (seenParentsWithEveryUnused.tryAdd(parent)) {
23762+
const range = isJSDocTemplateTag(parent)
23763+
// Whole @template tag
23764+
? rangeOfNode(parent)
23765+
// Include the `<>` in the error message
23766+
: rangeOfTypeParameters(parent.typeParameters!);
23767+
const only = typeParameters.length === 1;
23768+
const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused;
23769+
const arg0 = only ? name : undefined;
23770+
addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0));
23771+
}
23772+
}
23773+
else {
23774+
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
2374823775
}
2374923776
}
2375023777
}
2375123778
}
23779+
function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean {
23780+
return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name);
23781+
}
2375223782

2375323783
function addToGroup<K, V>(map: Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void {
2375423784
const keyString = String(getKey(key));

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2093,7 +2093,7 @@ namespace ts {
20932093
return arg => f(arg) || g(arg);
20942094
}
20952095

2096-
export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty
2096+
export function assertType<T>(_: T): void { } // tslint:disable-line no-empty
20972097

20982098
export function singleElementArray<T>(t: T | undefined): T[] | undefined {
20992099
return t === undefined ? undefined : [t];

src/compiler/diagnosticMessages.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3655,6 +3655,10 @@
36553655
"category": "Message",
36563656
"code": 6204
36573657
},
3658+
"All type parameters are unused": {
3659+
"category": "Error",
3660+
"code": 6205
3661+
},
36583662

36593663
"Projects to reference": {
36603664
"category": "Message",
@@ -4208,6 +4212,14 @@
42084212
"category": "Message",
42094213
"code": 90010
42104214
},
4215+
"Remove template tag": {
4216+
"category": "Message",
4217+
"code": 90011
4218+
},
4219+
"Remove type parameters": {
4220+
"category": "Message",
4221+
"code": 90012
4222+
},
42114223
"Import '{0}' from module \"{1}\"": {
42124224
"category": "Message",
42134225
"code": 90013
@@ -4276,6 +4288,14 @@
42764288
"category": "Message",
42774289
"code": 90029
42784290
},
4291+
"Replace 'infer {0}' with 'unknown'": {
4292+
"category": "Message",
4293+
"code": 90030
4294+
},
4295+
"Replace all unused 'infer' with 'unknown'": {
4296+
"category": "Message",
4297+
"code": 90031
4298+
},
42794299
"Convert function to an ES2015 class": {
42804300
"category": "Message",
42814301
"code": 95001

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7030,8 +7030,8 @@ namespace ts {
70307030
skipWhitespace();
70317031
const typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
70327032
typeParameter.name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
7033-
skipWhitespace();
70347033
finishNode(typeParameter);
7034+
skipWhitespace();
70357035
typeParameters.push(typeParameter);
70367036
} while (parseOptionalJsdoc(SyntaxKind.CommaToken));
70377037

src/compiler/tsbuild.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ namespace ts {
12681268
// Don't report status on "solution" projects
12691269
break;
12701270
default:
1271-
assertTypeIsNever(status);
1271+
assertType<never>(status);
12721272
}
12731273
}
12741274
}

src/compiler/utilities.ts

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,19 @@ namespace ts {
676676

677677
export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters;
678678
export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters {
679+
switch (node.kind) {
680+
case SyntaxKind.JSDocCallbackTag:
681+
case SyntaxKind.JSDocTypedefTag:
682+
case SyntaxKind.JSDocSignature:
683+
return true;
684+
default:
685+
assertType<DeclarationWithTypeParameterChildren>(node);
686+
return isDeclarationWithTypeParameterChildren(node);
687+
}
688+
}
689+
690+
export function isDeclarationWithTypeParameterChildren(node: Node): node is DeclarationWithTypeParameterChildren;
691+
export function isDeclarationWithTypeParameterChildren(node: DeclarationWithTypeParameterChildren): node is DeclarationWithTypeParameterChildren {
679692
switch (node.kind) {
680693
case SyntaxKind.CallSignature:
681694
case SyntaxKind.ConstructSignature:
@@ -696,12 +709,9 @@ namespace ts {
696709
case SyntaxKind.SetAccessor:
697710
case SyntaxKind.FunctionExpression:
698711
case SyntaxKind.ArrowFunction:
699-
case SyntaxKind.JSDocCallbackTag:
700-
case SyntaxKind.JSDocTypedefTag:
701-
case SyntaxKind.JSDocSignature:
702712
return true;
703713
default:
704-
assertTypeIsNever(node);
714+
assertType<never>(node);
705715
return false;
706716
}
707717
}
@@ -786,7 +796,7 @@ namespace ts {
786796
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3);
787797
}
788798

789-
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
799+
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
790800
const start = skipTrivia(sourceFile.text, nodes.pos);
791801
return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2, arg3);
792802
}
@@ -8128,4 +8138,75 @@ namespace ts {
81288138
}
81298139
return { min, max };
81308140
}
8141+
8142+
export interface ReadonlyNodeSet<TNode extends Node> {
8143+
has(node: TNode): boolean;
8144+
forEach(cb: (node: TNode) => void): void;
8145+
some(pred: (node: TNode) => boolean): boolean;
8146+
}
8147+
8148+
export class NodeSet<TNode extends Node> implements ReadonlyNodeSet<TNode> {
8149+
private map = createMap<TNode>();
8150+
8151+
add(node: TNode): void {
8152+
this.map.set(String(getNodeId(node)), node);
8153+
}
8154+
tryAdd(node: TNode): boolean {
8155+
if (this.has(node)) return false;
8156+
this.add(node);
8157+
return true;
8158+
}
8159+
has(node: TNode): boolean {
8160+
return this.map.has(String(getNodeId(node)));
8161+
}
8162+
forEach(cb: (node: TNode) => void): void {
8163+
this.map.forEach(cb);
8164+
}
8165+
some(pred: (node: TNode) => boolean): boolean {
8166+
return forEachEntry(this.map, pred) || false;
8167+
}
8168+
}
8169+
8170+
export interface ReadonlyNodeMap<TNode extends Node, TValue> {
8171+
get(node: TNode): TValue | undefined;
8172+
has(node: TNode): boolean;
8173+
}
8174+
8175+
export class NodeMap<TNode extends Node, TValue> implements ReadonlyNodeMap<TNode, TValue> {
8176+
private map = createMap<{ node: TNode, value: TValue }>();
8177+
8178+
get(node: TNode): TValue | undefined {
8179+
const res = this.map.get(String(getNodeId(node)));
8180+
return res && res.value;
8181+
}
8182+
8183+
getOrUpdate(node: TNode, setValue: () => TValue): TValue {
8184+
const res = this.get(node);
8185+
if (res) return res;
8186+
const value = setValue();
8187+
this.set(node, value);
8188+
return value;
8189+
}
8190+
8191+
set(node: TNode, value: TValue): void {
8192+
this.map.set(String(getNodeId(node)), { node, value });
8193+
}
8194+
8195+
has(node: TNode): boolean {
8196+
return this.map.has(String(getNodeId(node)));
8197+
}
8198+
8199+
forEach(cb: (value: TValue, node: TNode) => void): void {
8200+
this.map.forEach(({ node, value }) => cb(value, node));
8201+
}
8202+
}
8203+
8204+
export function rangeOfNode(node: Node): TextRange {
8205+
return { pos: getTokenPosOfNode(node), end: node.end };
8206+
}
8207+
8208+
export function rangeOfTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration>): TextRange {
8209+
// Include the `<>`
8210+
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
8211+
}
81318212
}

src/harness/fourslash.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,8 +1496,8 @@ Actual: ${stringify(fullActual)}`);
14961496
const actualTags = selectedItem.tags;
14971497
assert.equal(actualTags.length, (options.tags || ts.emptyArray).length, this.assertionMessageAtLastKnownMarker("signature help tags"));
14981498
ts.zipWith((options.tags || ts.emptyArray), actualTags, (expectedTag, actualTag) => {
1499-
assert.equal(expectedTag.name, actualTag.name);
1500-
assert.equal(expectedTag.text, actualTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
1499+
assert.equal(actualTag.name, expectedTag.name);
1500+
assert.equal(actualTag.text, expectedTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
15011501
});
15021502

15031503
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [

0 commit comments

Comments
 (0)