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:
Andy 2018-07-27 11:55:31 -07:00 committed by GitHub
parent 3bfe91cdd8
commit d40d54984e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 447 additions and 183 deletions

View file

@ -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));

View file

@ -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];

View file

@ -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

View file

@ -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));

View file

@ -1268,7 +1268,7 @@ namespace ts {
// Don't report status on "solution" projects
break;
default:
assertTypeIsNever(status);
assertType<never>(status);
}
}
}

View file

@ -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 };
}
}

View file

@ -1496,8 +1496,8 @@ Actual: ${stringify(fullActual)}`);
const actualTags = selectedItem.tags;
assert.equal(actualTags.length, (options.tags || ts.emptyArray).length, this.assertionMessageAtLastKnownMarker("signature help tags"));
ts.zipWith((options.tags || ts.emptyArray), actualTags, (expectedTag, actualTag) => {
assert.equal(expectedTag.name, actualTag.name);
assert.equal(expectedTag.text, actualTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
assert.equal(actualTag.name, expectedTag.name);
assert.equal(actualTag.text, expectedTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
});
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [

View file

@ -3,6 +3,7 @@ namespace ts.codefix {
const fixName = "unusedIdentifier";
const fixIdPrefix = "unusedIdentifier_prefix";
const fixIdDelete = "unusedIdentifier_delete";
const fixIdInfer = "unusedIdentifier_infer";
const errorCodes = [
Diagnostics._0_is_declared_but_its_value_is_never_read.code,
Diagnostics._0_is_declared_but_never_used.code,
@ -10,6 +11,7 @@ namespace ts.codefix {
Diagnostics.All_imports_in_import_declaration_are_unused.code,
Diagnostics.All_destructured_elements_are_unused.code,
Diagnostics.All_variables_are_unused.code,
Diagnostics.All_type_parameters_are_unused.code,
];
registerCodeFix({
@ -20,28 +22,42 @@ namespace ts.codefix {
const sourceFiles = program.getSourceFiles();
const token = getTokenAtPosition(sourceFile, context.span.start);
if (isJSDocTemplateTag(token)) {
return [createDeleteFix(textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, token)), Diagnostics.Remove_template_tag)];
}
if (token.kind === SyntaxKind.LessThanToken) {
const changes = textChanges.ChangeTracker.with(context, t => deleteTypeParameters(t, sourceFile, token));
return [createDeleteFix(changes, Diagnostics.Remove_type_parameters)];
}
const importDecl = tryGetFullImport(token);
if (importDecl) {
const changes = textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, importDecl));
return [createCodeFixAction(fixName, changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)])];
}
const delDestructure = textChanges.ChangeTracker.with(context, t =>
tryDeleteFullDestructure(token, t, sourceFile, checker, sourceFiles, /*isFixAll*/ false));
if (delDestructure.length) {
return [createCodeFixAction(fixName, delDestructure, Diagnostics.Remove_destructuring, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(delDestructure, Diagnostics.Remove_destructuring)];
}
const delVar = textChanges.ChangeTracker.with(context, t => tryDeleteFullVariableStatement(sourceFile, token, t));
if (delVar.length) {
return [createCodeFixAction(fixName, delVar, Diagnostics.Remove_variable_statement, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
return [createDeleteFix(delVar, Diagnostics.Remove_variable_statement)];
}
const result: CodeFixAction[] = [];
const deletion = textChanges.ChangeTracker.with(context, t =>
tryDeleteDeclaration(sourceFile, token, t, checker, sourceFiles, /*isFixAll*/ false));
if (deletion.length) {
const name = isComputedPropertyName(token.parent) ? token.parent : token;
result.push(createCodeFixAction(fixName, deletion, [Diagnostics.Remove_declaration_for_Colon_0, name.getText(sourceFile)], fixIdDelete, Diagnostics.Delete_all_unused_declarations));
if (token.kind === SyntaxKind.InferKeyword) {
const changes = textChanges.ChangeTracker.with(context, t => changeInferToUnknown(t, sourceFile, token));
const name = cast(token.parent, isInferTypeNode).typeParameter.name.text;
result.push(createCodeFixAction(fixName, changes, [Diagnostics.Replace_infer_0_with_unknown, name], fixIdInfer, Diagnostics.Replace_all_unused_infer_with_unknown));
}
else {
const deletion = textChanges.ChangeTracker.with(context, t =>
tryDeleteDeclaration(sourceFile, token, t, checker, sourceFiles, /*isFixAll*/ false));
if (deletion.length) {
const name = isComputedPropertyName(token.parent) ? token.parent : token;
result.push(createDeleteFix(deletion, [Diagnostics.Remove_declaration_for_Colon_0, name.getText(sourceFile)]));
}
}
const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, errorCode, sourceFile, token));
@ -51,7 +67,7 @@ namespace ts.codefix {
return result;
},
fixIds: [fixIdPrefix, fixIdDelete],
fixIds: [fixIdPrefix, fixIdDelete, fixIdInfer],
getAllCodeActions: context => {
const { sourceFile, program } = context;
const checker = program.getTypeChecker();
@ -60,21 +76,31 @@ namespace ts.codefix {
const token = getTokenAtPosition(sourceFile, diag.start);
switch (context.fixId) {
case fixIdPrefix:
if (isIdentifier(token) && canPrefix(token)) {
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
}
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
break;
case fixIdDelete: {
if (token.kind === SyntaxKind.InferKeyword) break; // Can't delete
const importDecl = tryGetFullImport(token);
if (importDecl) {
changes.delete(sourceFile, importDecl);
}
else if (isJSDocTemplateTag(token)) {
changes.delete(sourceFile, token);
}
else if (token.kind === SyntaxKind.LessThanToken) {
deleteTypeParameters(changes, sourceFile, token);
}
else if (!tryDeleteFullDestructure(token, changes, sourceFile, checker, sourceFiles, /*isFixAll*/ true) &&
!tryDeleteFullVariableStatement(sourceFile, token, changes)) {
tryDeleteDeclaration(sourceFile, token, changes, checker, sourceFiles, /*isFixAll*/ true);
}
break;
}
case fixIdInfer:
if (token.kind === SyntaxKind.InferKeyword) {
changeInferToUnknown(changes, sourceFile, token);
}
break;
default:
Debug.fail(JSON.stringify(context.fixId));
}
@ -82,6 +108,18 @@ namespace ts.codefix {
},
});
function changeInferToUnknown(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
changes.replaceNode(sourceFile, token.parent, createKeywordTypeNode(SyntaxKind.UnknownKeyword));
}
function createDeleteFix(changes: FileTextChanges[], diag: DiagnosticAndArguments): CodeFixAction {
return createCodeFixAction(fixName, changes, diag, fixIdDelete, Diagnostics.Delete_all_unused_declarations);
}
function deleteTypeParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
changes.delete(sourceFile, Debug.assertDefined(cast(token.parent, isDeclarationWithTypeParameterChildren).typeParameters));
}
// Sometimes the diagnostic span is an entire ImportDeclaration, so we should remove the whole thing.
function tryGetFullImport(token: Node): ImportDeclaration | undefined {
return token.kind === SyntaxKind.ImportKeyword ? tryCast(token.parent, isImportDeclaration) : undefined;
@ -110,7 +148,11 @@ namespace ts.codefix {
function tryPrefixDeclaration(changes: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, token: Node): void {
// Don't offer to prefix a property.
if (errorCode !== Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code && isIdentifier(token) && canPrefix(token)) {
if (errorCode === Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code) return;
if (token.kind === SyntaxKind.InferKeyword) {
token = cast(token.parent, isInferTypeNode).typeParameter.name;
}
if (isIdentifier(token) && canPrefix(token)) {
changes.replaceNode(sourceFile, token, createIdentifier(`_${token.text}`));
}
}
@ -118,6 +160,7 @@ namespace ts.codefix {
function canPrefix(token: Identifier): boolean {
switch (token.parent.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.TypeParameter:
return true;
case SyntaxKind.VariableDeclaration: {
const varDecl = token.parent as VariableDeclaration;

View file

@ -354,12 +354,15 @@ namespace ts.formatting {
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
return getListIfStartEndIsInListRange((<ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration>node.parent).typeParameters, node.getStart(sourceFile), end);
case SyntaxKind.JSDocTemplateTag: {
const { typeParameters } = <ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag>node.parent;
return getListIfStartEndIsInListRange(typeParameters, node.getStart(sourceFile), end);
}
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression: {
const start = node.getStart(sourceFile);
return getListIfStartEndIsInListRange((<CallExpression>node.parent).typeArguments, start, end) ||
getListIfStartEndIsInListRange((<CallExpression>node.parent).arguments, start, end);
return getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).typeArguments, start, end) ||
getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).arguments, start, end);
}
case SyntaxKind.VariableDeclarationList:
return getListIfStartEndIsInListRange((<VariableDeclarationList>node.parent).declarations, node.getStart(sourceFile), end);

View file

@ -1240,7 +1240,7 @@ namespace ts.refactor.extractSymbol {
return scope.members;
}
else {
assertTypeIsNever(scope);
assertType<never>(scope);
}
return emptyArray;

View file

@ -213,7 +213,7 @@ namespace ts.textChanges {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node }[] = [];
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
public static fromContext(context: TextChangesContext): ChangeTracker {
return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext);
@ -233,8 +233,8 @@ namespace ts.textChanges {
return this;
}
delete(sourceFile: SourceFile, node: Node): void {
this.deletedNodes.push({ sourceFile, node, });
delete(sourceFile: SourceFile, node: Node | NodeArray<TypeParameterDeclaration>): void {
this.deletedNodes.push({ sourceFile, node });
}
public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void {
@ -661,7 +661,12 @@ namespace ts.textChanges {
const deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
for (const { sourceFile, node } of this.deletedNodes) {
if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) {
deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node);
if (isArray(node)) {
this.deleteRange(sourceFile, rangeOfTypeParameters(node));
}
else {
deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node);
}
}
}
@ -1001,7 +1006,7 @@ namespace ts.textChanges {
}
namespace deleteDeclaration {
export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: Node): void {
switch (node.kind) {
case SyntaxKind.Parameter: {
const oldFunction = node.parent;
@ -1052,33 +1057,9 @@ namespace ts.textChanges {
deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration);
break;
case SyntaxKind.TypeParameter: {
const typeParam = node as TypeParameterDeclaration;
switch (typeParam.parent.kind) {
case SyntaxKind.JSDocTemplateTag:
changes.deleteRange(sourceFile, getRangeToDeleteJsDocTag(typeParam.parent, sourceFile));
break;
case SyntaxKind.InferType:
// TODO: GH#25594
break;
default: {
const typeParameters = getEffectiveTypeParameterDeclarations(typeParam.parent);
if (typeParameters.length === 1) {
const { pos, end } = cast(typeParameters, isNodeArray);
const previousToken = getTokenAtPosition(sourceFile, pos - 1);
const nextToken = getTokenAtPosition(sourceFile, end);
Debug.assert(previousToken.kind === SyntaxKind.LessThanToken);
Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken);
changes.deleteNodeRange(sourceFile, previousToken, nextToken);
}
else {
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
}
}
case SyntaxKind.TypeParameter:
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
break;
}
case SyntaxKind.ImportSpecifier:
const namedImports = (node as ImportSpecifier).parent;
@ -1144,7 +1125,7 @@ namespace ts.textChanges {
}
}
function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: VariableDeclaration): void {
function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: VariableDeclaration): void {
const { parent } = node;
if (parent.kind === SyntaxKind.CatchClause) {
@ -1177,12 +1158,6 @@ namespace ts.textChanges {
Debug.assertNever(gp);
}
}
function getRangeToDeleteJsDocTag(node: JSDocTag, sourceFile: SourceFile): TextRange {
const { parent } = node;
const toDelete = parent.kind === SyntaxKind.JSDocComment && parent.comment === undefined && parent.tags!.length === 1 ? parent : node;
return createTextRangeFromNode(toDelete, sourceFile);
}
}
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
@ -1193,7 +1168,7 @@ namespace ts.textChanges {
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet<Node>, sourceFile: SourceFile, node: Node): void {
const containingList = Debug.assertDefined(formatting.SmartIndenter.getContainingList(node, sourceFile));
const index = indexOfNode(containingList, node);
Debug.assert(index !== -1);

View file

@ -374,7 +374,7 @@ namespace ts {
case SpecialPropertyAssignmentKind.Prototype:
return ScriptElementKind.localClassElement;
default: {
assertTypeIsNever(kind);
assertType<never>(kind);
return ScriptElementKind.unknown;
}
}
@ -1358,63 +1358,6 @@ namespace ts {
return getPropertySymbolsFromBaseTypes(memberSymbol.parent!, memberSymbol.name, checker, _ => true) || false;
}
export interface ReadonlyNodeSet {
has(node: Node): boolean;
forEach(cb: (node: Node) => void): void;
some(pred: (node: Node) => boolean): boolean;
}
export class NodeSet implements ReadonlyNodeSet {
private map = createMap<Node>();
add(node: Node): void {
this.map.set(String(getNodeId(node)), node);
}
has(node: Node): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (node: Node) => void): void {
this.map.forEach(cb);
}
some(pred: (node: Node) => 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 getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined {
if (!node) return undefined;

View file

@ -455,7 +455,7 @@ namespace ts.server {
break;
}
default:
assertTypeIsNever(response);
assertType<never>(response);
}
}

View file

@ -22,7 +22,7 @@
"0": {
"kind": "TypeParameter",
"pos": 18,
"end": 20,
"end": 19,
"name": {
"kind": "Identifier",
"pos": 18,

View file

@ -22,7 +22,7 @@
"0": {
"kind": "TypeParameter",
"pos": 18,
"end": 20,
"end": 19,
"name": {
"kind": "Identifier",
"pos": 18,

View file

@ -22,7 +22,7 @@
"0": {
"kind": "TypeParameter",
"pos": 18,
"end": 20,
"end": 19,
"name": {
"kind": "Identifier",
"pos": 18,
@ -33,7 +33,7 @@
"1": {
"kind": "TypeParameter",
"pos": 22,
"end": 24,
"end": 23,
"name": {
"kind": "Identifier",
"pos": 22,

View file

@ -1,18 +1,18 @@
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(1,18): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(1,17): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(1,21): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(3,19): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(3,18): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts(7,26): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/noUnusedLocals_typeParameterMergedWithParameter.ts (4 errors) ====
function useNone<T>(T: number) {}
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
~
!!! error TS6133: 'T' is declared but its value is never read.
function useParam<T>(T: number) {
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
return T;
}

View file

@ -78,7 +78,7 @@
},
{
"name": "template",
"text": "T A template"
"text": "T A template"
},
{
"name": "type",

View file

@ -1,9 +1,9 @@
tests/cases/compiler/unusedTypeParameterInFunction1.ts(1,13): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameterInFunction1.ts(1,12): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameterInFunction1.ts (1 errors) ====
function f1<T>() {
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
}

View file

@ -1,9 +1,9 @@
tests/cases/compiler/unusedTypeParameterInInterface1.ts(1,15): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameterInInterface1.ts(1,14): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameterInInterface1.ts (1 errors) ====
interface int<T> {
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
}

View file

@ -1,11 +1,11 @@
tests/cases/compiler/unusedTypeParameterInLambda1.ts(3,17): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameterInLambda1.ts(3,16): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameterInLambda1.ts (1 errors) ====
class A {
public f1() {
return <T>() => {
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
}

View file

@ -1,10 +1,10 @@
tests/cases/compiler/unusedTypeParameterInMethod4.ts(2,15): error TS6133: 'X' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameterInMethod4.ts(2,14): error TS6133: 'X' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameterInMethod4.ts (1 errors) ====
class A {
public f1<X>() {
~
~~~
!!! error TS6133: 'X' is declared but its value is never read.
}

View file

@ -1,10 +1,10 @@
tests/cases/compiler/unusedTypeParameterInMethod5.ts(2,26): error TS6133: 'X' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameterInMethod5.ts(2,25): error TS6133: 'X' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameterInMethod5.ts (1 errors) ====
class A {
public f1 = function<X>() {
~
~~~
!!! error TS6133: 'X' is declared but its value is never read.
}

View file

@ -1,9 +1,9 @@
tests/cases/compiler/unusedTypeParameters1.ts(1,15): error TS6133: 'typeparameter1' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameters1.ts(1,14): error TS6133: 'typeparameter1' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameters1.ts (1 errors) ====
class greeter<typeparameter1> {
~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~
!!! error TS6133: 'typeparameter1' is declared but its value is never read.
}

View file

@ -1,9 +1,9 @@
tests/cases/compiler/unusedTypeParameters10.ts(1,12): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParameters10.ts(1,11): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameters10.ts (1 errors) ====
type Alias<T> = { };
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
type Alias2<T> = { x: T };

View file

@ -1,4 +1,4 @@
tests/cases/compiler/b.ts(1,13): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/b.ts(1,12): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/a.ts (0 errors) ====
@ -6,5 +6,5 @@ tests/cases/compiler/b.ts(1,13): error TS6133: 'T' is declared but its value is
==== tests/cases/compiler/b.ts (1 errors) ====
interface C<T> { }
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.

View file

@ -1,33 +1,33 @@
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(1,12): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(3,8): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(5,13): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(7,9): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(8,14): error TS6133: 'V' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(11,10): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(1,11): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(3,7): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(5,12): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(7,8): error TS6133: 'T' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(8,13): error TS6133: 'V' is declared but its value is never read.
tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(11,9): error TS6133: 'T' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts (6 errors) ====
function f<T>() { }
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
type T<T> = { };
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
interface I<T> { };
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
class C<T> {
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.
public m<V>() { }
~
~~~
!!! error TS6133: 'V' is declared but its value is never read.
};
let l = <T>() => { };
~
~~~
!!! error TS6133: 'T' is declared but its value is never read.

View file

@ -0,0 +1,8 @@
tests/cases/compiler/unusedTypeParameters_infer.ts(1,38): error TS6133: 'U' is declared but its value is never read.
==== tests/cases/compiler/unusedTypeParameters_infer.ts (1 errors) ====
type Length<T> = T extends ArrayLike<infer U> ? number : never;
~~~~~~~
!!! error TS6133: 'U' is declared but its value is never read.

View file

@ -0,0 +1,5 @@
//// [unusedTypeParameters_infer.ts]
type Length<T> = T extends ArrayLike<infer U> ? number : never;
//// [unusedTypeParameters_infer.js]

View file

@ -0,0 +1,8 @@
=== tests/cases/compiler/unusedTypeParameters_infer.ts ===
type Length<T> = T extends ArrayLike<infer U> ? number : never;
>Length : Symbol(Length, Decl(unusedTypeParameters_infer.ts, 0, 0))
>T : Symbol(T, Decl(unusedTypeParameters_infer.ts, 0, 12))
>T : Symbol(T, Decl(unusedTypeParameters_infer.ts, 0, 12))
>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --))
>U : Symbol(U, Decl(unusedTypeParameters_infer.ts, 0, 42))

View file

@ -0,0 +1,8 @@
=== tests/cases/compiler/unusedTypeParameters_infer.ts ===
type Length<T> = T extends ArrayLike<infer U> ? number : never;
>Length : Length<T>
>T : T
>T : T
>ArrayLike : ArrayLike<T>
>U : U

View file

@ -0,0 +1,9 @@
/a.js(1,5): error TS6133: 'T' is declared but its value is never read.
==== /a.js (1 errors) ====
/** @template T */
~~~~~~~~~~~
!!! error TS6133: 'T' is declared but its value is never read.
function f() {}

View file

@ -0,0 +1,5 @@
=== /a.js ===
/** @template T */
function f() {}
>f : Symbol(f, Decl(a.js, 0, 0))

View file

@ -0,0 +1,5 @@
=== /a.js ===
/** @template T */
function f() {}
>f : <T>() => void

View file

@ -0,0 +1,3 @@
// @noUnusedParameters: true
type Length<T> = T extends ArrayLike<infer U> ? number : never;

View file

@ -0,0 +1,8 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @noUnusedParameters:true
// @Filename: /a.js
/** @template T */
function f() {}

View file

@ -41,6 +41,8 @@
////export type First<T, U> = T;
////export interface ISecond<T, U> { u: U; }
////export const cls = class<T, U> { u: U; };
////export class Ctu<T, U> {}
////export type Length<T> = T extends ArrayLike<infer U> ? number : never; // Not affected, can't delete
verify.codeFixAll({
fixId: "unusedIdentifier_delete",
@ -78,5 +80,7 @@ for (const {} in {}) {}
export type First<T> = T;
export interface ISecond<U> { u: U; }
export const cls = class<U> { u: U; };`,
export const cls = class<U> { u: U; };
export class Ctu {}
export type Length<T> = T extends ArrayLike<infer U> ? number : never; // Not affected, can't delete`,
});

View file

@ -19,6 +19,12 @@
//// */
////function h() {}
////
/////**
//// * Doc
//// * @template T,U Comment
//// */
////function h2() {}
////
/////** @template T Comment @return {void} */
////function i() {}
////
@ -34,7 +40,7 @@ verify.codeFixAll({
fixId: "unusedIdentifier_delete",
fixAllDescription: "Delete all unused declarations",
newFileContent:
`
`/** Parameter doc comment */
function f() {}
/**
@ -50,6 +56,12 @@ function g() {}
*/
function h() {}
/**
* Doc
* Comment
*/
function h2() {}
/** Comment @return {void} */
function i() {}

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
////type Length<T> = T extends ArrayLike<infer U> ? number : never;
////type Indexer<T> = T extends ArrayLike<infer U> ? number : never;
////function f(p) {} // Ignored
verify.codeFixAll({
fixId: "unusedIdentifier_infer",
fixAllDescription: "Replace all unused 'infer' with 'unknown'",
newFileContent:
`type Length<T> = T extends ArrayLike<unknown> ? number : never;
type Indexer<T> = T extends ArrayLike<unknown> ? number : never;
function f(p) {} // Ignored`,
});

View file

@ -6,6 +6,7 @@
////function f(a, b) {
//// const x = 0; // Can't be prefixed, ignored
////}
////type Length<T> = T extends ArrayLike<infer U> ? number : never;
verify.codeFixAll({
fixId: "unusedIdentifier_prefix",
@ -13,5 +14,6 @@ verify.codeFixAll({
newFileContent:
`function f(_a, _b) {
const x = 0; // Can't be prefixed, ignored
}`,
}
type Length<T> = T extends ArrayLike<infer _U> ? number : never;`,
});

View file

@ -0,0 +1,61 @@
/// <reference path='fourslash.ts' />
// @allowJs: true
// @Filename: /first.js
/////**
//// * Doc
//// * @template T,U Comment
//// * @param {U} p
//// */
////function first(p) { return p; }
goTo.file("/first.js");
verify.codeFix({
index: 0,
description: "Remove declaration for: 'T'",
newFileContent:
`/**
* Doc
* @template U Comment
* @param {U} p
*/
function first(p) { return p; }`,
});
// @Filename: /second.js
/////**
//// * Doc
//// * @template T,U Comment
//// * @param {T} p
//// */
////function second(p) { return p; }
goTo.file("/second.js");
verify.codeFix({
description: "Remove declaration for: 'U'",
index: 0,
newFileContent:
`/**
* Doc
* @template T Comment
* @param {T} p
*/
function second(p) { return p; }`,
});
// @Filename: /both.js
/////**
//// * @template T,U Comment
//// */
////function both() {}
goTo.file("/both.js");
verify.codeFix({
description: "Remove template tag",
newFileContent:
`/**
* Comment
*/
function both() {}`,
});

View file

@ -0,0 +1,9 @@
/// <reference path='fourslash.ts' />
////type Length<T> = T extends ArrayLike<infer U> ? number : never;
verify.codeFix({
description: "Replace 'infer U' with 'unknown'",
index: 0,
newFileContent: "type Length<T> = T extends ArrayLike<unknown> ? number : never;",
});

View file

@ -9,7 +9,7 @@
////export const x = 0;
verify.codeFix({
description: "Remove declaration for: 'T'",
description: "Remove type parameters",
newFileContent:
`/**
* @type {() => void}

View file

@ -17,8 +17,7 @@ verify.signatureHelp({
text: "find(l: any[], x: any): any",
docComment: "Find an item",
tags: [
// TODO: GH#24130 (see PR #24600's commits for potential fix)
{ name: "template", text: "T\n " },
{ name: "template", text: "T" },
{ name: "param", text: "l" },
{ name: "param", text: "x" },
{ name: "returns", text: "The names of the found item(s)." },

View file

@ -5,6 +5,6 @@
////}
verify.codeFix({
description: "Remove declaration for: 'T'",
description: "Remove type parameters",
newRangeContent: "class greeter ",
});

View file

@ -7,5 +7,6 @@
verify.codeFix({
description: "Remove declaration for: 'Y'",
index: 0,
newRangeContent: "class greeter<X> ",
});

View file

@ -9,5 +9,6 @@
verify.codeFix({
description: "Remove declaration for: 'Y'",
index: 0,
newRangeContent: "class greeter<X, Z> ",
});

View file

@ -4,6 +4,6 @@
//// [|function f1<T>() {}|]
verify.codeFix({
description: "Remove declaration for: 'T'",
description: "Remove type parameters",
newRangeContent: "function f1() {}",
});

View file

@ -5,5 +5,6 @@
verify.codeFix({
description: "Remove declaration for: 'Y'",
index: 0,
newRangeContent: "function f1<X>(a: X) {a}",
});

View file

@ -5,5 +5,6 @@
verify.codeFix({
description: "Remove declaration for: 'Y'",
index: 0,
newRangeContent: "function f1<X, Z>(a: X) {a;var b:Z;b}",
});

View file

@ -5,6 +5,6 @@
//// [|interface I<T> {}|]
verify.codeFix({
description: "Remove declaration for: 'T'",
description: "Remove type parameters",
newRangeContent: "interface I {}",
});

View file

@ -7,6 +7,6 @@
//// }
verify.codeFix({
description: "Remove declaration for: 'T'",
newRangeContent: "return(x:number) => {x}",
description: "Remove type parameters",
newRangeContent: "return (x:number) => {x}",
});

View file

@ -8,5 +8,6 @@
verify.codeFix({
description: "Remove declaration for: 'U'",
index: 0,
newRangeContent: "new <T>(a: T): void;",
});

View file

@ -9,5 +9,6 @@
verify.codeFix({
description: "Remove declaration for: 'K'",
index: 0,
newRangeContent: "new <T, U>(a: T): A<U>;",
});

View file

@ -7,6 +7,7 @@
//// [|var y: new <T,U>(a:T)=>void;|]
verify.codeFix({
index: 0,
description: "Remove declaration for: 'U'",
newRangeContent: "var y: new <T>(a:T)=>void;",
});

View file

@ -6,6 +6,6 @@
//// }
verify.codeFix({
description: "Remove declaration for: 'T'",
description: "Remove type parameters",
newRangeContent: "f1()",
});

View file

@ -6,6 +6,7 @@
//// }
verify.codeFix({
index: 0,
description: "Remove declaration for: 'T'",
newRangeContent: "f1<U>(a: U)",
});

View file

@ -6,6 +6,7 @@
//// }
verify.codeFix({
index: 0,
description: "Remove declaration for: 'Y'",
newRangeContent: "public f1<X, Z>(a: X)",
});