mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support deleting all unused type parameters in a list, and deleting @template tag (#25748)
* Support deleting all unused type parameters in a list, and deleting @template tag * Support type parameter in 'infer'
This commit is contained in:
+39
-9
@@ -22613,6 +22613,7 @@ namespace ts {
|
||||
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
|
||||
}
|
||||
checkSourceElement(node.typeParameter);
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
|
||||
function checkImportType(node: ImportTypeNode) {
|
||||
@@ -23641,7 +23642,8 @@ namespace ts {
|
||||
type PotentiallyUnusedIdentifier =
|
||||
| SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration
|
||||
| Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement
|
||||
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration;
|
||||
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration
|
||||
| InferTypeNode;
|
||||
|
||||
function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: ReadonlyArray<PotentiallyUnusedIdentifier>, addDiagnostic: AddUnusedDiagnostic) {
|
||||
for (const node of potentiallyUnusedIdentifiers) {
|
||||
@@ -23681,6 +23683,7 @@ namespace ts {
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.InferType:
|
||||
checkUnusedTypeParameters(node, addDiagnostic);
|
||||
break;
|
||||
default:
|
||||
@@ -23734,21 +23737,48 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function checkUnusedTypeParameters(
|
||||
node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration,
|
||||
addDiagnostic: AddUnusedDiagnostic,
|
||||
): void {
|
||||
function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void {
|
||||
// Only report errors on the last declaration for the type parameter container;
|
||||
// this ensures that all uses have been accounted for.
|
||||
const typeParameters = getEffectiveTypeParameterDeclarations(node);
|
||||
if (!(node.flags & NodeFlags.Ambient) && last(getSymbolOfNode(node).declarations) === node) {
|
||||
if (node.flags & NodeFlags.Ambient || node.kind !== SyntaxKind.InferType && last(getSymbolOfNode(node).declarations) !== node) return;
|
||||
|
||||
if (node.kind === SyntaxKind.InferType) {
|
||||
const { typeParameter } = node;
|
||||
if (isTypeParameterUnused(typeParameter)) {
|
||||
addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const typeParameters = getEffectiveTypeParameterDeclarations(node);
|
||||
const seenParentsWithEveryUnused = new NodeSet<DeclarationWithTypeParameterChildren>();
|
||||
|
||||
for (const typeParameter of typeParameters) {
|
||||
if (!(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name)) {
|
||||
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol)));
|
||||
if (!isTypeParameterUnused(typeParameter)) continue;
|
||||
|
||||
const name = idText(typeParameter.name);
|
||||
const { parent } = typeParameter;
|
||||
if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) {
|
||||
if (seenParentsWithEveryUnused.tryAdd(parent)) {
|
||||
const range = isJSDocTemplateTag(parent)
|
||||
// Whole @template tag
|
||||
? rangeOfNode(parent)
|
||||
// Include the `<>` in the error message
|
||||
: rangeOfTypeParameters(parent.typeParameters!);
|
||||
const only = typeParameters.length === 1;
|
||||
const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused;
|
||||
const arg0 = only ? name : undefined;
|
||||
addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0));
|
||||
}
|
||||
}
|
||||
else {
|
||||
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean {
|
||||
return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name);
|
||||
}
|
||||
|
||||
function addToGroup<K, V>(map: Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void {
|
||||
const keyString = String(getKey(key));
|
||||
|
||||
@@ -2093,7 +2093,7 @@ namespace ts {
|
||||
return arg => f(arg) || g(arg);
|
||||
}
|
||||
|
||||
export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty
|
||||
export function assertType<T>(_: T): void { } // tslint:disable-line no-empty
|
||||
|
||||
export function singleElementArray<T>(t: T | undefined): T[] | undefined {
|
||||
return t === undefined ? undefined : [t];
|
||||
|
||||
@@ -3655,6 +3655,10 @@
|
||||
"category": "Message",
|
||||
"code": 6204
|
||||
},
|
||||
"All type parameters are unused": {
|
||||
"category": "Error",
|
||||
"code": 6205
|
||||
},
|
||||
|
||||
"Projects to reference": {
|
||||
"category": "Message",
|
||||
@@ -4208,6 +4212,14 @@
|
||||
"category": "Message",
|
||||
"code": 90010
|
||||
},
|
||||
"Remove template tag": {
|
||||
"category": "Message",
|
||||
"code": 90011
|
||||
},
|
||||
"Remove type parameters": {
|
||||
"category": "Message",
|
||||
"code": 90012
|
||||
},
|
||||
"Import '{0}' from module \"{1}\"": {
|
||||
"category": "Message",
|
||||
"code": 90013
|
||||
@@ -4276,6 +4288,14 @@
|
||||
"category": "Message",
|
||||
"code": 90029
|
||||
},
|
||||
"Replace 'infer {0}' with 'unknown'": {
|
||||
"category": "Message",
|
||||
"code": 90030
|
||||
},
|
||||
"Replace all unused 'infer' with 'unknown'": {
|
||||
"category": "Message",
|
||||
"code": 90031
|
||||
},
|
||||
"Convert function to an ES2015 class": {
|
||||
"category": "Message",
|
||||
"code": 95001
|
||||
|
||||
@@ -7030,8 +7030,8 @@ namespace ts {
|
||||
skipWhitespace();
|
||||
const typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
|
||||
typeParameter.name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces);
|
||||
skipWhitespace();
|
||||
finishNode(typeParameter);
|
||||
skipWhitespace();
|
||||
typeParameters.push(typeParameter);
|
||||
} while (parseOptionalJsdoc(SyntaxKind.CommaToken));
|
||||
|
||||
|
||||
@@ -1268,7 +1268,7 @@ namespace ts {
|
||||
// Don't report status on "solution" projects
|
||||
break;
|
||||
default:
|
||||
assertTypeIsNever(status);
|
||||
assertType<never>(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,6 +676,19 @@ namespace ts {
|
||||
|
||||
export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters;
|
||||
export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.JSDocCallbackTag:
|
||||
case SyntaxKind.JSDocTypedefTag:
|
||||
case SyntaxKind.JSDocSignature:
|
||||
return true;
|
||||
default:
|
||||
assertType<DeclarationWithTypeParameterChildren>(node);
|
||||
return isDeclarationWithTypeParameterChildren(node);
|
||||
}
|
||||
}
|
||||
|
||||
export function isDeclarationWithTypeParameterChildren(node: Node): node is DeclarationWithTypeParameterChildren;
|
||||
export function isDeclarationWithTypeParameterChildren(node: DeclarationWithTypeParameterChildren): node is DeclarationWithTypeParameterChildren {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
@@ -696,12 +709,9 @@ namespace ts {
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.JSDocCallbackTag:
|
||||
case SyntaxKind.JSDocTypedefTag:
|
||||
case SyntaxKind.JSDocSignature:
|
||||
return true;
|
||||
default:
|
||||
assertTypeIsNever(node);
|
||||
assertType<never>(node);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -786,7 +796,7 @@ namespace ts {
|
||||
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
|
||||
export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
|
||||
const start = skipTrivia(sourceFile.text, nodes.pos);
|
||||
return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2, arg3);
|
||||
}
|
||||
@@ -8128,4 +8138,75 @@ namespace ts {
|
||||
}
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
export interface ReadonlyNodeSet<TNode extends Node> {
|
||||
has(node: TNode): boolean;
|
||||
forEach(cb: (node: TNode) => void): void;
|
||||
some(pred: (node: TNode) => boolean): boolean;
|
||||
}
|
||||
|
||||
export class NodeSet<TNode extends Node> implements ReadonlyNodeSet<TNode> {
|
||||
private map = createMap<TNode>();
|
||||
|
||||
add(node: TNode): void {
|
||||
this.map.set(String(getNodeId(node)), node);
|
||||
}
|
||||
tryAdd(node: TNode): boolean {
|
||||
if (this.has(node)) return false;
|
||||
this.add(node);
|
||||
return true;
|
||||
}
|
||||
has(node: TNode): boolean {
|
||||
return this.map.has(String(getNodeId(node)));
|
||||
}
|
||||
forEach(cb: (node: TNode) => void): void {
|
||||
this.map.forEach(cb);
|
||||
}
|
||||
some(pred: (node: TNode) => boolean): boolean {
|
||||
return forEachEntry(this.map, pred) || false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReadonlyNodeMap<TNode extends Node, TValue> {
|
||||
get(node: TNode): TValue | undefined;
|
||||
has(node: TNode): boolean;
|
||||
}
|
||||
|
||||
export class NodeMap<TNode extends Node, TValue> implements ReadonlyNodeMap<TNode, TValue> {
|
||||
private map = createMap<{ node: TNode, value: TValue }>();
|
||||
|
||||
get(node: TNode): TValue | undefined {
|
||||
const res = this.map.get(String(getNodeId(node)));
|
||||
return res && res.value;
|
||||
}
|
||||
|
||||
getOrUpdate(node: TNode, setValue: () => TValue): TValue {
|
||||
const res = this.get(node);
|
||||
if (res) return res;
|
||||
const value = setValue();
|
||||
this.set(node, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set(node: TNode, value: TValue): void {
|
||||
this.map.set(String(getNodeId(node)), { node, value });
|
||||
}
|
||||
|
||||
has(node: TNode): boolean {
|
||||
return this.map.has(String(getNodeId(node)));
|
||||
}
|
||||
|
||||
forEach(cb: (value: TValue, node: TNode) => void): void {
|
||||
this.map.forEach(({ node, value }) => cb(value, node));
|
||||
}
|
||||
}
|
||||
|
||||
export function rangeOfNode(node: Node): TextRange {
|
||||
return { pos: getTokenPosOfNode(node), end: node.end };
|
||||
}
|
||||
|
||||
export function rangeOfTypeParameters(typeParameters: NodeArray<TypeParameterDeclaration>): TextRange {
|
||||
// Include the `<>`
|
||||
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user