When --noUnusedLocals/--noUnusedParameters is disabled, add suggestions instead of errors (#22361)
* When --noUnusedLocals/--noUnusedParameters is disabled, add suggestions instead of errors * Improve performance: do not add unused suggestion diagnostics unless asking for a suggestion * Add "unused" flag to diagnostics * Code review * reportsUnused -> reportsUnnecessary * Fix test
This commit is contained in:
parent
f61f12613c
commit
24842b4002
|
@ -65,7 +65,6 @@ namespace ts {
|
|||
const compilerOptions = host.getCompilerOptions();
|
||||
const languageVersion = getEmitScriptTarget(compilerOptions);
|
||||
const modulekind = getEmitModuleKind(compilerOptions);
|
||||
const noUnusedIdentifiers = !!compilerOptions.noUnusedLocals || !!compilerOptions.noUnusedParameters;
|
||||
const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions);
|
||||
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
|
||||
const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
|
||||
|
@ -313,7 +312,22 @@ namespace ts {
|
|||
return node && getTypeArgumentConstraint(node);
|
||||
},
|
||||
|
||||
getSuggestionDiagnostics: file => suggestionDiagnostics.get(file.fileName) || emptyArray,
|
||||
getSuggestionDiagnostics: file => {
|
||||
return (suggestionDiagnostics.get(file.fileName) || emptyArray).concat(getUnusedDiagnostics());
|
||||
function getUnusedDiagnostics(): ReadonlyArray<Diagnostic> {
|
||||
if (file.isDeclarationFile) return emptyArray;
|
||||
|
||||
checkSourceFile(file);
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked));
|
||||
checkUnusedIdentifiers(allPotentiallyUnusedIdentifiers.get(file.fileName)!, (kind, diag) => {
|
||||
if (!unusedIsError(kind)) {
|
||||
diagnostics.push({ ...diag, category: DiagnosticCategory.Suggestion });
|
||||
}
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const tupleTypes: GenericType[] = [];
|
||||
|
@ -422,8 +436,9 @@ namespace ts {
|
|||
let deferredGlobalTemplateStringsArrayType: ObjectType;
|
||||
|
||||
let deferredNodes: Node[];
|
||||
let deferredUnusedIdentifierNodes: Node[];
|
||||
const seenDeferredUnusedIdentifiers = createMap<true>(); // For assertion that we don't defer the same identifier twice
|
||||
const allPotentiallyUnusedIdentifiers = createMap<ReadonlyArray<PotentiallyUnusedIdentifier>>(); // key is file name
|
||||
let potentiallyUnusedIdentifiers: PotentiallyUnusedIdentifier[]; // Potentially unused identifiers in the source file currently being checked.
|
||||
const seenPotentiallyUnusedIdentifiers = createMap<true>(); // For assertion that we don't defer the same identifier twice
|
||||
|
||||
let flowLoopStart = 0;
|
||||
let flowLoopCount = 0;
|
||||
|
@ -455,17 +470,6 @@ namespace ts {
|
|||
const diagnostics = createDiagnosticCollection();
|
||||
// Suggestion diagnostics must have a file. Keyed by source file name.
|
||||
const suggestionDiagnostics = createMultiMap<Diagnostic>();
|
||||
function addSuggestionDiagnostic(diag: Diagnostic): void {
|
||||
suggestionDiagnostics.add(diag.file.fileName, { ...diag, category: DiagnosticCategory.Suggestion });
|
||||
}
|
||||
function addErrorOrSuggestionDiagnostic(isError: boolean, diag: Diagnostic): void {
|
||||
if (isError) {
|
||||
diagnostics.add(diag);
|
||||
}
|
||||
else {
|
||||
addSuggestionDiagnostic(diag);
|
||||
}
|
||||
}
|
||||
|
||||
const enum TypeFacts {
|
||||
None = 0,
|
||||
|
@ -624,6 +628,12 @@ namespace ts {
|
|||
resolvedMembers = "resolvedMembers"
|
||||
}
|
||||
|
||||
const enum UnusedKind {
|
||||
Local,
|
||||
Parameter,
|
||||
}
|
||||
type AddUnusedDiagnostic = (type: UnusedKind, diagnostic: Diagnostic) => void;
|
||||
|
||||
const builtinGlobals = createSymbolTable();
|
||||
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);
|
||||
|
||||
|
@ -818,6 +828,18 @@ namespace ts {
|
|||
diagnostics.add(diagnostic);
|
||||
}
|
||||
|
||||
function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) {
|
||||
if (isError) {
|
||||
diagnostics.add(diagnostic);
|
||||
}
|
||||
else {
|
||||
suggestionDiagnostics.add(diagnostic.file.fileName, { ...diagnostic, category: DiagnosticCategory.Suggestion });
|
||||
}
|
||||
}
|
||||
function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
|
||||
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message));
|
||||
}
|
||||
|
||||
function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) {
|
||||
symbolCount++;
|
||||
const symbol = <TransientSymbol>(new Symbol(flags | SymbolFlags.Transient, name));
|
||||
|
@ -1433,7 +1455,7 @@ namespace ts {
|
|||
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
|
||||
// If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself.
|
||||
// That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used.
|
||||
if (isUse && result && noUnusedIdentifiers && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) {
|
||||
if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) {
|
||||
result.isReferenced |= meaning;
|
||||
}
|
||||
|
||||
|
@ -2128,7 +2150,7 @@ namespace ts {
|
|||
if (sourceFile) {
|
||||
if (sourceFile.symbol) {
|
||||
if (resolvedModule.isExternalLibraryImport && !extensionIsTypeScript(resolvedModule.extension)) {
|
||||
addSuggestionDiagnostic(createModuleImplicitlyAnyDiagnostic(errorNode, resolvedModule, moduleReference));
|
||||
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
|
||||
}
|
||||
// merged symbol is module declaration symbol combined with all augmentations
|
||||
return getMergedSymbol(sourceFile.symbol);
|
||||
|
@ -2154,7 +2176,7 @@ namespace ts {
|
|||
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
|
||||
}
|
||||
else {
|
||||
addErrorOrSuggestionDiagnostic(noImplicitAny && !!moduleNotFoundError, createModuleImplicitlyAnyDiagnostic(errorNode, resolvedModule, moduleReference));
|
||||
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference);
|
||||
}
|
||||
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
|
||||
return undefined;
|
||||
|
@ -2179,12 +2201,12 @@ namespace ts {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function createModuleImplicitlyAnyDiagnostic(errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): Diagnostic {
|
||||
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
|
||||
const errorInfo = packageId && chainDiagnosticMessages(
|
||||
/*details*/ undefined,
|
||||
Diagnostics.Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
|
||||
getMangledNameForScopedPackage(packageId.name));
|
||||
return createDiagnosticForNodeFromMessageChain(errorNode, chainDiagnosticMessages(
|
||||
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
|
||||
errorInfo,
|
||||
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
|
||||
moduleReference,
|
||||
|
@ -10525,8 +10547,7 @@ namespace ts {
|
|||
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
|
||||
// that S and T are contra-variant whereas X and Y are co-variant.
|
||||
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
|
||||
const modifiersRelated = relation === comparableRelation || (
|
||||
relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
|
||||
const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
|
||||
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
|
||||
if (modifiersRelated) {
|
||||
let result: Ternary;
|
||||
|
@ -16729,7 +16750,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) {
|
||||
if (!prop || !noUnusedIdentifiers || !(prop.flags & SymbolFlags.ClassMember) || !prop.valueDeclaration || !hasModifier(prop.valueDeclaration, ModifierFlags.Private)) {
|
||||
if (!prop || !(prop.flags & SymbolFlags.ClassMember) || !prop.valueDeclaration || !hasModifier(prop.valueDeclaration, ModifierFlags.Private)) {
|
||||
return;
|
||||
}
|
||||
if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor))) {
|
||||
|
@ -19092,7 +19113,6 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
}
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20442,8 +20462,8 @@ namespace ts {
|
|||
checkAsyncFunctionReturnType(<FunctionLikeDeclaration>node);
|
||||
}
|
||||
}
|
||||
if (noUnusedIdentifiers && !(<FunctionDeclaration>node).body) {
|
||||
checkUnusedTypeParameters(node);
|
||||
if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) {
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20644,7 +20664,6 @@ namespace ts {
|
|||
if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node);
|
||||
|
||||
checkSourceElement(node.body);
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
|
||||
const symbol = getSymbolOfNode(node);
|
||||
const firstDeclaration = getDeclarationOfKind(symbol, node.kind);
|
||||
|
@ -20764,7 +20783,6 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
checkSourceElement(node.body);
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
|
||||
function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type, message: DiagnosticMessage) {
|
||||
|
@ -21915,70 +21933,72 @@ namespace ts {
|
|||
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
|
||||
}
|
||||
}
|
||||
|
||||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
|
||||
function registerForUnusedIdentifiersCheck(node: Node) {
|
||||
if (deferredUnusedIdentifierNodes) {
|
||||
function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void {
|
||||
// May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`.
|
||||
if (potentiallyUnusedIdentifiers) {
|
||||
// TODO: GH#22580
|
||||
// Debug.assert(addToSeen(seenDeferredUnusedIdentifiers, getNodeId(node)), "Deferring unused identifier check twice");
|
||||
deferredUnusedIdentifierNodes.push(node);
|
||||
// Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice");
|
||||
potentiallyUnusedIdentifiers.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
function checkUnusedIdentifiers() {
|
||||
if (deferredUnusedIdentifierNodes) {
|
||||
for (const node of deferredUnusedIdentifierNodes) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
checkUnusedModuleMembers(<ModuleDeclaration | SourceFile>node);
|
||||
break;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
checkUnusedClassMembers(<ClassDeclaration | ClassExpression>node);
|
||||
checkUnusedTypeParameters(<ClassDeclaration | ClassExpression>node);
|
||||
break;
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
checkUnusedTypeParameters(<InterfaceDeclaration>node);
|
||||
break;
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CaseBlock:
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
checkUnusedLocalsAndParameters(node);
|
||||
break;
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
if ((<FunctionLikeDeclaration>node).body) {
|
||||
checkUnusedLocalsAndParameters(<FunctionLikeDeclaration>node);
|
||||
}
|
||||
checkUnusedTypeParameters(<FunctionLikeDeclaration>node);
|
||||
break;
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
checkUnusedTypeParameters(<MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | TypeAliasDeclaration>node);
|
||||
break;
|
||||
default:
|
||||
Debug.fail("Node should not have been registered for unused identifiers check");
|
||||
}
|
||||
type PotentiallyUnusedIdentifier =
|
||||
| SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration
|
||||
| Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement
|
||||
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration;
|
||||
|
||||
function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: ReadonlyArray<PotentiallyUnusedIdentifier>, addDiagnostic: AddUnusedDiagnostic) {
|
||||
for (const node of potentiallyUnusedIdentifiers) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
checkUnusedModuleMembers(node, addDiagnostic);
|
||||
break;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
checkUnusedClassMembers(node, addDiagnostic);
|
||||
checkUnusedTypeParameters(node, addDiagnostic);
|
||||
break;
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
checkUnusedTypeParameters(node, addDiagnostic);
|
||||
break;
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CaseBlock:
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
checkUnusedLocalsAndParameters(node, addDiagnostic);
|
||||
break;
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
if (node.body) {
|
||||
checkUnusedLocalsAndParameters(node, addDiagnostic);
|
||||
}
|
||||
checkUnusedTypeParameters(node, addDiagnostic);
|
||||
break;
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
checkUnusedTypeParameters(node, addDiagnostic);
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(node, "Node should not have been registered for unused identifiers check");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkUnusedLocalsAndParameters(node: Node): void {
|
||||
if (noUnusedIdentifiers && !(node.flags & NodeFlags.Ambient)) {
|
||||
function checkUnusedLocalsAndParameters(node: Node, addDiagnostic: AddUnusedDiagnostic): void {
|
||||
if (!(node.flags & NodeFlags.Ambient)) {
|
||||
node.locals.forEach(local => {
|
||||
// If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`.
|
||||
// If it's a type parameter merged with a parameter, check if the parameter-side is used.
|
||||
|
@ -21986,15 +22006,12 @@ namespace ts {
|
|||
if (local.valueDeclaration && getRootDeclaration(local.valueDeclaration).kind === SyntaxKind.Parameter) {
|
||||
const parameter = <ParameterDeclaration>getRootDeclaration(local.valueDeclaration);
|
||||
const name = getNameOfDeclaration(local.valueDeclaration);
|
||||
if (compilerOptions.noUnusedParameters &&
|
||||
!isParameterPropertyDeclaration(parameter) &&
|
||||
!parameterIsThisKeyword(parameter) &&
|
||||
!parameterNameStartsWithUnderscore(name)) {
|
||||
error(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local));
|
||||
if (!isParameterPropertyDeclaration(parameter) && !parameterIsThisKeyword(parameter) && !parameterNameStartsWithUnderscore(name)) {
|
||||
addDiagnostic(UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local)));
|
||||
}
|
||||
}
|
||||
else if (compilerOptions.noUnusedLocals) {
|
||||
forEach(local.declarations, d => errorUnusedLocal(d, symbolName(local)));
|
||||
else {
|
||||
forEach(local.declarations, d => errorUnusedLocal(d, symbolName(local), addDiagnostic));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -22009,7 +22026,7 @@ namespace ts {
|
|||
return false;
|
||||
}
|
||||
|
||||
function errorUnusedLocal(declaration: Declaration, name: string) {
|
||||
function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) {
|
||||
const node = getNameOfDeclaration(declaration) || declaration;
|
||||
if (isIdentifierThatStartsWithUnderScore(node)) {
|
||||
const declaration = getRootDeclaration(node.parent);
|
||||
|
@ -22020,7 +22037,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
if (!isRemovedPropertyFromObjectSpread(node.kind === SyntaxKind.Identifier ? node.parent : node)) {
|
||||
diagnostics.add(createDiagnosticForNodeSpan(getSourceFileOfNode(declaration), declaration, node, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
|
||||
addDiagnostic(UnusedKind.Local, createDiagnosticForNodeSpan(getSourceFileOfNode(declaration), declaration, node, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22032,8 +22049,8 @@ namespace ts {
|
|||
return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._;
|
||||
}
|
||||
|
||||
function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression): void {
|
||||
if (compilerOptions.noUnusedLocals && !(node.flags & NodeFlags.Ambient)) {
|
||||
function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void {
|
||||
if (!(node.flags & NodeFlags.Ambient)) {
|
||||
for (const member of node.members) {
|
||||
switch (member.kind) {
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
|
@ -22046,13 +22063,13 @@ namespace ts {
|
|||
}
|
||||
const symbol = getSymbolOfNode(member);
|
||||
if (!symbol.isReferenced && hasModifier(member, ModifierFlags.Private)) {
|
||||
error(member.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol));
|
||||
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(member.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol)));
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.Constructor:
|
||||
for (const parameter of (<ConstructorDeclaration>member).parameters) {
|
||||
if (!parameter.symbol.isReferenced && hasModifier(parameter, ModifierFlags.Private)) {
|
||||
error(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol));
|
||||
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -22067,27 +22084,23 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function checkUnusedTypeParameters(node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration) {
|
||||
if (compilerOptions.noUnusedParameters && !(node.flags & NodeFlags.Ambient)) {
|
||||
if (node.typeParameters) {
|
||||
// Only report errors on the last declaration for the type parameter container;
|
||||
// this ensures that all uses have been accounted for.
|
||||
const symbol = getSymbolOfNode(node);
|
||||
const lastDeclaration = symbol && symbol.declarations && lastOrUndefined(symbol.declarations);
|
||||
if (lastDeclaration !== node) {
|
||||
return;
|
||||
}
|
||||
for (const typeParameter of node.typeParameters) {
|
||||
if (!(getMergedSymbol(typeParameter.symbol).isReferenced & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderScore(typeParameter.name)) {
|
||||
error(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol));
|
||||
}
|
||||
function checkUnusedTypeParameters(
|
||||
node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration,
|
||||
addDiagnostic: AddUnusedDiagnostic,
|
||||
): void {
|
||||
// Only report errors on the last declaration for the type parameter container;
|
||||
// this ensures that all uses have been accounted for.
|
||||
if (!(node.flags & NodeFlags.Ambient) && node.typeParameters && last(getSymbolOfNode(node)!.declarations) === node) {
|
||||
for (const typeParameter of node.typeParameters) {
|
||||
if (!(getMergedSymbol(typeParameter.symbol).isReferenced & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderScore(typeParameter.name)) {
|
||||
addDiagnostic(UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile): void {
|
||||
if (compilerOptions.noUnusedLocals && !(node.flags & NodeFlags.Ambient)) {
|
||||
function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile, addDiagnostic: AddUnusedDiagnostic): void {
|
||||
if (!(node.flags & NodeFlags.Ambient)) {
|
||||
// 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.
|
||||
const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>();
|
||||
node.locals.forEach(local => {
|
||||
|
@ -22106,7 +22119,7 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
else {
|
||||
errorUnusedLocal(declaration, symbolName(local));
|
||||
errorUnusedLocal(declaration, symbolName(local), addDiagnostic);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -22114,13 +22127,13 @@ namespace ts {
|
|||
unusedImports.forEach(([importClause, unuseds]) => {
|
||||
const importDecl = importClause.parent;
|
||||
if (forEachImportedDeclaration(importClause, d => !contains(unuseds, d))) {
|
||||
for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name));
|
||||
for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name), addDiagnostic);
|
||||
}
|
||||
else if (unuseds.length === 1) {
|
||||
error(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name));
|
||||
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name)));
|
||||
}
|
||||
else {
|
||||
error(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused, showModuleSpecifier(importDecl));
|
||||
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused, showModuleSpecifier(importDecl)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -24878,6 +24891,17 @@ namespace ts {
|
|||
performance.measure("Check", "beforeCheck", "afterCheck");
|
||||
}
|
||||
|
||||
function unusedIsError(kind: UnusedKind): boolean {
|
||||
switch (kind) {
|
||||
case UnusedKind.Local:
|
||||
return compilerOptions.noUnusedLocals;
|
||||
case UnusedKind.Parameter:
|
||||
return compilerOptions.noUnusedParameters;
|
||||
default:
|
||||
return Debug.assertNever(kind);
|
||||
}
|
||||
}
|
||||
|
||||
// Fully type check a source file and collect the relevant diagnostics.
|
||||
function checkSourceFileWorker(node: SourceFile) {
|
||||
const links = getNodeLinks(node);
|
||||
|
@ -24896,8 +24920,10 @@ namespace ts {
|
|||
clear(potentialNewTargetCollisions);
|
||||
|
||||
deferredNodes = [];
|
||||
deferredUnusedIdentifierNodes = produceDiagnostics && noUnusedIdentifiers ? [] : undefined;
|
||||
flowAnalysisDisabled = false;
|
||||
if (produceDiagnostics) {
|
||||
Debug.assert(!allPotentiallyUnusedIdentifiers.has(node.fileName));
|
||||
allPotentiallyUnusedIdentifiers.set(node.fileName, potentiallyUnusedIdentifiers = []);
|
||||
}
|
||||
|
||||
forEach(node.statements, checkSourceElement);
|
||||
|
||||
|
@ -24907,13 +24933,17 @@ namespace ts {
|
|||
registerForUnusedIdentifiersCheck(node);
|
||||
}
|
||||
|
||||
if (!node.isDeclarationFile) {
|
||||
checkUnusedIdentifiers();
|
||||
if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) {
|
||||
checkUnusedIdentifiers(potentiallyUnusedIdentifiers, (kind, diag) => {
|
||||
if (unusedIsError(kind)) {
|
||||
diagnostics.add(diag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deferredNodes = undefined;
|
||||
seenDeferredUnusedIdentifiers.clear();
|
||||
deferredUnusedIdentifierNodes = undefined;
|
||||
seenPotentiallyUnusedIdentifiers.clear();
|
||||
potentiallyUnusedIdentifiers = undefined;
|
||||
|
||||
if (isExternalOrCommonJsModule(node)) {
|
||||
checkExternalModuleExports(node);
|
||||
|
|
|
@ -1606,6 +1606,7 @@ namespace ts {
|
|||
messageText: text,
|
||||
category: message.category,
|
||||
code: message.code,
|
||||
reportsUnnecessary: message.unused,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1635,7 +1636,8 @@ namespace ts {
|
|||
|
||||
messageText: text,
|
||||
category: message.category,
|
||||
code: message.code
|
||||
code: message.code,
|
||||
reportsUnnecessary: message.unused,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1647,7 +1649,7 @@ namespace ts {
|
|||
|
||||
code: chain.code,
|
||||
category: chain.category,
|
||||
messageText: chain.next ? chain : chain.messageText
|
||||
messageText: chain.next ? chain : chain.messageText,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3282,7 +3282,8 @@
|
|||
},
|
||||
"'{0}' is declared but its value is never read.": {
|
||||
"category": "Error",
|
||||
"code": 6133
|
||||
"code": 6133,
|
||||
"unused": true
|
||||
},
|
||||
"Report errors on unused locals.": {
|
||||
"category": "Message",
|
||||
|
@ -3302,7 +3303,8 @@
|
|||
},
|
||||
"Property '{0}' is declared but its value is never read.": {
|
||||
"category": "Error",
|
||||
"code": 6138
|
||||
"code": 6138,
|
||||
"unused": true
|
||||
},
|
||||
"Import emit helpers from 'tslib'.": {
|
||||
"category": "Message",
|
||||
|
@ -3514,7 +3516,8 @@
|
|||
},
|
||||
"All imports in import declaration are unused.": {
|
||||
"category": "Error",
|
||||
"code": 6192
|
||||
"code": 6192,
|
||||
"unused": true
|
||||
},
|
||||
"Found 1 error.": {
|
||||
"category": "Message",
|
||||
|
@ -3602,7 +3605,8 @@
|
|||
},
|
||||
"Unused label.": {
|
||||
"category": "Error",
|
||||
"code": 7028
|
||||
"code": 7028,
|
||||
"unused": true
|
||||
},
|
||||
"Fallthrough case in switch.": {
|
||||
"category": "Error",
|
||||
|
|
|
@ -4069,6 +4069,7 @@ namespace ts {
|
|||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
message: string;
|
||||
unused?: {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4090,6 +4091,8 @@ namespace ts {
|
|||
length: number | undefined;
|
||||
messageText: string | DiagnosticMessageChain;
|
||||
category: DiagnosticCategory;
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
code: number;
|
||||
source?: string;
|
||||
}
|
||||
|
|
|
@ -2577,7 +2577,7 @@ Actual: ${stringify(fullActual)}`);
|
|||
}
|
||||
const range = ts.first(ranges);
|
||||
|
||||
const codeFixes = this.getCodeFixes(fileName, errorCode, preferences);
|
||||
const codeFixes = this.getCodeFixes(fileName, errorCode, preferences).filter(f => f.fixId === undefined); // TODO: GH#20315 filter out those that use the import fix ID;
|
||||
|
||||
if (codeFixes.length === 0) {
|
||||
if (expectedTextArray.length !== 0) {
|
||||
|
|
|
@ -123,13 +123,14 @@ namespace ts {
|
|||
}
|
||||
{
|
||||
const actual = parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack);
|
||||
expected.errors = expected.errors.map<Diagnostic>(error => ({
|
||||
expected.errors = expected.errors.map((error): Diagnostic => ({
|
||||
category: error.category,
|
||||
code: error.code,
|
||||
file: undefined,
|
||||
length: undefined,
|
||||
messageText: error.messageText,
|
||||
start: undefined,
|
||||
reportsUnnecessary: undefined,
|
||||
}));
|
||||
assertParsed(actual, expected);
|
||||
}
|
||||
|
|
|
@ -4068,7 +4068,7 @@ namespace ts.projectSystem {
|
|||
const folderPath = "/a/b/projects/temp";
|
||||
const file1: FileOrFolder = {
|
||||
path: `${folderPath}/a.ts`,
|
||||
content: 'import f = require("pad")'
|
||||
content: 'import f = require("pad"); f;'
|
||||
};
|
||||
const files = [file1, libFile];
|
||||
const host = createServerHost(files);
|
||||
|
|
|
@ -353,11 +353,11 @@ namespace ts.server {
|
|||
return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync);
|
||||
}
|
||||
|
||||
private getDiagnostics(file: string, command: CommandNames) {
|
||||
private getDiagnostics(file: string, command: CommandNames): Diagnostic[] {
|
||||
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest | protocol.SemanticDiagnosticsSyncRequest | protocol.SuggestionDiagnosticsSyncRequest>(command, { file, includeLinePosition: true });
|
||||
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse | protocol.SemanticDiagnosticsSyncResponse | protocol.SuggestionDiagnosticsSyncResponse>(request);
|
||||
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => {
|
||||
return (<protocol.DiagnosticWithLinePosition[]>response.body).map((entry): Diagnostic => {
|
||||
const category = firstDefined(Object.keys(DiagnosticCategory), id =>
|
||||
isString(id) && entry.category === id.toLowerCase() ? (<any>DiagnosticCategory)[id] : undefined);
|
||||
return {
|
||||
|
@ -366,7 +366,8 @@ namespace ts.server {
|
|||
length: entry.length,
|
||||
messageText: entry.message,
|
||||
category: Debug.assertDefined(category, "convertDiagnostic: category should not be undefined"),
|
||||
code: entry.code
|
||||
code: entry.code,
|
||||
reportsUnnecessary: entry.reportsUnnecessary,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -455,6 +455,8 @@ namespace ts.server.protocol {
|
|||
endLocation: Location;
|
||||
category: string;
|
||||
code: number;
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -588,6 +588,7 @@ namespace ts {
|
|||
length: number;
|
||||
category: string;
|
||||
code: number;
|
||||
unused?: {};
|
||||
}
|
||||
export function realizeDiagnostics(diagnostics: ReadonlyArray<Diagnostic>, newLine: string): RealizedDiagnostic[] {
|
||||
return diagnostics.map(d => realizeDiagnostic(d, newLine));
|
||||
|
|
|
@ -2271,6 +2271,7 @@ declare namespace ts {
|
|||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
message: string;
|
||||
unused?: {};
|
||||
}
|
||||
/**
|
||||
* A linked list of formatted diagnostic messages to be used as part of a multiline message.
|
||||
|
@ -2290,6 +2291,8 @@ declare namespace ts {
|
|||
length: number | undefined;
|
||||
messageText: string | DiagnosticMessageChain;
|
||||
category: DiagnosticCategory;
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
code: number;
|
||||
source?: string;
|
||||
}
|
||||
|
@ -5398,6 +5401,8 @@ declare namespace ts.server.protocol {
|
|||
endLocation: Location;
|
||||
category: string;
|
||||
code: number;
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
}
|
||||
/**
|
||||
* Response message for "projectInfo" request
|
||||
|
|
|
@ -2271,6 +2271,7 @@ declare namespace ts {
|
|||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
message: string;
|
||||
unused?: {};
|
||||
}
|
||||
/**
|
||||
* A linked list of formatted diagnostic messages to be used as part of a multiline message.
|
||||
|
@ -2290,6 +2291,8 @@ declare namespace ts {
|
|||
length: number | undefined;
|
||||
messageText: string | DiagnosticMessageChain;
|
||||
category: DiagnosticCategory;
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
code: number;
|
||||
source?: string;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//// * @return {...*}
|
||||
//// */
|
||||
//// m(x) {
|
||||
//// return [x];
|
||||
//// }
|
||||
////}
|
||||
|
||||
|
@ -16,6 +17,7 @@ verify.codeFix({
|
|||
* @return {...*}
|
||||
*/
|
||||
m(x): any[] {
|
||||
return [x];
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
//// * @param {promise<String>} zeta
|
||||
//// */
|
||||
////function f(x, y, z, alpha, beta, gamma, delta, epsilon, zeta) {
|
||||
//// x; y; z; alpha; beta; gamma; delta; epsilon; zeta;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -29,5 +30,6 @@ verify.codeFix({
|
|||
* @param {promise<String>} zeta
|
||||
*/
|
||||
function f(x: boolean, y: string, z: number, alpha: object, beta: Date, gamma: Promise<any>, delta: Array<any>, epsilon: Array<number>, zeta: Promise<string>) {
|
||||
x; y; z; alpha; beta; gamma; delta; epsilon; zeta;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
// @strict: true
|
||||
/////** @type {function(*, ...number, ...boolean): void} */
|
||||
////var x = (x, ys, ...zs) => { };
|
||||
////var x = (x, ys, ...zs) => { x; ys; zs; };
|
||||
|
||||
verify.codeFix({
|
||||
description: "Annotate with type from JSDoc",
|
||||
index: 0,
|
||||
newFileContent:
|
||||
`/** @type {function(*, ...number, ...boolean): void} */
|
||||
var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void = (x, ys, ...zs) => { };`,
|
||||
var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void = (x, ys, ...zs) => { x; ys; zs; };`,
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//// /**
|
||||
//// * @param {number} x - the first parameter
|
||||
//// */
|
||||
//// constructor(x) {
|
||||
//// constructor(readonly x) {
|
||||
//// }
|
||||
////}
|
||||
|
||||
|
@ -14,7 +14,7 @@ verify.codeFix({
|
|||
/**
|
||||
* @param {number} x - the first parameter
|
||||
*/
|
||||
constructor(x: number) {
|
||||
constructor(readonly x: number) {
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
////class C {
|
||||
//// /** @param {number} value */
|
||||
//// set c(value) { return 12 }
|
||||
//// set c(value) { return value }
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -9,6 +9,6 @@ verify.codeFix({
|
|||
newFileContent:
|
||||
`class C {
|
||||
/** @param {number} value */
|
||||
set c(value: number) { return 12 }
|
||||
set c(value: number) { return value }
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//// * @param {T} b
|
||||
//// */
|
||||
////function f(a, b) {
|
||||
//// return a || b;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -17,5 +18,6 @@ verify.codeFix({
|
|||
* @param {T} b
|
||||
*/
|
||||
function f<T>(a: number, b: T) {
|
||||
return a || b;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
//// * @param {number} a
|
||||
//// * @param {T} b
|
||||
//// */
|
||||
////function /*1*/f<T>(a, b) {
|
||||
////function f<T>(a, b) {
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Annotate with type from JSDoc",
|
||||
errorCode: 80004, // ignore 'unused T'
|
||||
newFileContent:
|
||||
`/**
|
||||
* @param {number} a
|
||||
|
|
|
@ -4,19 +4,20 @@
|
|||
//// * @return {number}
|
||||
//// */
|
||||
////function [|f|](x, y) {
|
||||
//// return x + y;
|
||||
////}
|
||||
////
|
||||
/////**
|
||||
//// * @return {number}
|
||||
//// */
|
||||
////function g(x, y): number {
|
||||
//// return 0;
|
||||
//// return x + y;
|
||||
////}
|
||||
/////**
|
||||
//// * @param {number} x
|
||||
//// */
|
||||
////function h(x: number, y): number {
|
||||
//// return 0;
|
||||
//// return x + y;
|
||||
////}
|
||||
////
|
||||
/////**
|
||||
|
@ -24,13 +25,14 @@
|
|||
//// * @param {string} y
|
||||
//// */
|
||||
////function i(x: number, y: string) {
|
||||
//// return x + y;
|
||||
////}
|
||||
/////**
|
||||
//// * @param {number} x
|
||||
//// * @return {boolean}
|
||||
//// */
|
||||
////function j(x: number, y): boolean {
|
||||
//// return true;
|
||||
//// return x < y;
|
||||
////}
|
||||
|
||||
// Only first location triggers a suggestion
|
||||
|
@ -41,24 +43,26 @@ verify.getSuggestionDiagnostics([{
|
|||
|
||||
verify.codeFix({
|
||||
description: "Annotate with type from JSDoc",
|
||||
newFileContent:
|
||||
errorCode: 80004,
|
||||
newFileContent:
|
||||
`/**
|
||||
* @return {number}
|
||||
*/
|
||||
function f(x, y): number {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
function g(x, y): number {
|
||||
return 0;
|
||||
return x + y;
|
||||
}
|
||||
/**
|
||||
* @param {number} x
|
||||
*/
|
||||
function h(x: number, y): number {
|
||||
return 0;
|
||||
return x + y;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,12 +70,13 @@ function h(x: number, y): number {
|
|||
* @param {string} y
|
||||
*/
|
||||
function i(x: number, y: string) {
|
||||
return x + y;
|
||||
}
|
||||
/**
|
||||
* @param {number} x
|
||||
* @return {boolean}
|
||||
*/
|
||||
function j(x: number, y): boolean {
|
||||
return true;
|
||||
return x < y;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
/////** @param {Object<string, boolean>} sb
|
||||
//// * @param {Object<number, string>} ns */
|
||||
////function f(sb, ns) {
|
||||
//// sb; ns;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -13,5 +14,6 @@ verify.codeFix({
|
|||
/** @param {Object<string, boolean>} sb
|
||||
* @param {Object<number, string>} ns */
|
||||
function f(sb: { [s: string]: boolean; }, ns: { [n: number]: string; }) {
|
||||
sb; ns;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//// * @param {*} beta - I have no idea how this got here
|
||||
//// */
|
||||
////function [|f|](x, y, z: string, alpha, beta) {
|
||||
//// x; y; z; alpha; beta;
|
||||
////}
|
||||
|
||||
verify.getSuggestionDiagnostics([{
|
||||
|
@ -17,6 +18,7 @@ verify.getSuggestionDiagnostics([{
|
|||
verify.codeFix({
|
||||
description: "Annotate with type from JSDoc",
|
||||
newFileContent:
|
||||
// TODO: GH#22358
|
||||
`/**
|
||||
* @param {number} x - the first parameter
|
||||
* @param {{ a: string, b: Date }} y - the most complex parameter
|
||||
|
@ -25,5 +27,6 @@ verify.codeFix({
|
|||
* @param {*} beta - I have no idea how this got here
|
||||
*/
|
||||
function f(x: number, y: { a: string; b: Date; }, z: string, alpha, beta: any) {
|
||||
x; y; z; alpha; beta;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//// * @param {number!} delta
|
||||
//// */
|
||||
////function [|f|](x, y, z, alpha, beta, gamma, delta) {
|
||||
//// x; y; z; alpha; beta; gamma; delta;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -25,5 +26,6 @@ verify.codeFix({
|
|||
* @param {number!} delta
|
||||
*/
|
||||
function f(x: any, y: any, z: number | undefined, alpha: number[], beta: (this: { a: string; }, arg1: string, arg2: number) => boolean, gamma: number | null, delta: number) {
|
||||
x; y; z; alpha; beta; gamma; delta;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//// * @returns {number}
|
||||
//// */
|
||||
////function f(x) {
|
||||
//// return x;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -15,5 +16,6 @@ verify.codeFix({
|
|||
* @returns {number}
|
||||
*/
|
||||
function f(x: number): number {
|
||||
return x;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//// * @returns {number}
|
||||
//// */
|
||||
////var f = function (x) {
|
||||
//// return x
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
|
@ -15,5 +16,6 @@ verify.codeFix({
|
|||
* @returns {number}
|
||||
*/
|
||||
var f = function (x: number): number {
|
||||
return x
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
////not read
|
||||
|
||||
// @Filename: /a.ts
|
||||
/////**/import * as abs from "abs";
|
||||
////import * as abs from "abs";
|
||||
////abs;
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
});
|
||||
|
||||
goTo.marker();
|
||||
goTo.file("/a.ts");
|
||||
|
||||
verify.codeFixAvailable([{
|
||||
description: "Install '@types/abs'",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////import * as abs from "abs";
|
||||
////import {} from "abs";
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
// @Filename: /a.ts
|
||||
////import * as abs from [|"abs/subModule"|];
|
||||
////abs;
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
// @Filename: /a.js
|
||||
////import abs from [|"abs"|];
|
||||
////abs;
|
||||
|
||||
test.setTypesRegistry({ "abs": undefined });
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////interface I<X> { x: X}
|
||||
////[|class C<T extends string , U> extends I<T>|]{}
|
||||
////interface I<X, Y> { x: X; y: Y; }
|
||||
////[|class C<T extends string , U> extends I<T , U>|]{}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Change 'extends' to 'implements'",
|
||||
newRangeContent: "class C<T extends string , U> implements I<T>",
|
||||
newRangeContent: "class C<T extends string , U> implements I<T , U>",
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
//// var index = { set p(x: [|*|]) { } };
|
||||
//// var index = { set p(x: [|*|]) { x; } };
|
||||
|
||||
verify.codeFix({
|
||||
description: "Change '*' to 'any'",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////function foo<T>(a: T) {
|
||||
//// a;
|
||||
//// abstract class C<U> {
|
||||
//// abstract a: T | U;
|
||||
//// }
|
||||
|
@ -14,6 +15,7 @@ verify.codeFix({
|
|||
// TODO: GH#18795
|
||||
newFileContent:
|
||||
`function foo<T>(a: T) {
|
||||
a;
|
||||
abstract class C<U> {
|
||||
abstract a: T | U;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////function foo<T>(a: T) {
|
||||
//// a;
|
||||
//// abstract class C<U> {
|
||||
//// abstract a: T | U;
|
||||
//// }
|
||||
|
@ -14,6 +15,7 @@ verify.codeFix({
|
|||
// TODO: GH#18795
|
||||
newFileContent:
|
||||
`function foo<T>(a: T) {
|
||||
a;
|
||||
abstract class C<U> {
|
||||
abstract a: T | U;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////abstract class A {
|
||||
//// private _a: string;
|
||||
////
|
||||
//// abstract get a(): number | string;
|
||||
//// abstract get b(): this;
|
||||
//// abstract get c(): A;
|
||||
|
@ -25,8 +23,6 @@ verify.codeFix({
|
|||
// TODO: GH#18795
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
private _a: string;
|
||||
|
||||
abstract get a(): number | string;
|
||||
abstract get b(): this;
|
||||
abstract get c(): A;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
//// abstract class A {
|
||||
//// private abstract x: number;
|
||||
//// m() { this.x; } // Avoid unused private
|
||||
//// }
|
||||
////
|
||||
//// class C extends A {[| |]}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////abstract class A {
|
||||
//// private _a: string;
|
||||
////
|
||||
//// abstract get a(): string;
|
||||
//// abstract set a(newName: string);
|
||||
////
|
||||
|
@ -17,8 +15,6 @@ verify.codeFix({
|
|||
description: "Implement interface 'A'",
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
private _a: string;
|
||||
|
||||
abstract get a(): string;
|
||||
abstract set a(newName: string);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
////class A {
|
||||
//// method(a: number, b: string): boolean;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return a + b as any; }
|
||||
////}
|
||||
////class C implements A {}
|
||||
|
||||
|
@ -11,7 +11,7 @@ verify.codeFix({
|
|||
newFileContent:
|
||||
`class A {
|
||||
method(a: number, b: string): boolean;
|
||||
method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
method(a: string | number, b?: string | number): boolean | Function { return a + b as any; }
|
||||
}
|
||||
class C implements A {
|
||||
method(a: number, b: string): boolean;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//// method(a: any, b: string): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return a + b as any; }
|
||||
////}
|
||||
////class C implements A {[| |]}
|
||||
|
||||
|
@ -15,7 +15,7 @@ verify.codeFix({
|
|||
method(a: any, b: string): boolean;
|
||||
method(a: string, b: number): Function;
|
||||
method(a: string): Function;
|
||||
method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
method(a: string | number, b?: string | number): boolean | Function { return a + b as any; }
|
||||
}
|
||||
class C implements A {
|
||||
method(a: any, b: string): boolean;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//// private y: number;
|
||||
//// protected z: number;
|
||||
//// public w: number;
|
||||
//// public useY() { this.y; }
|
||||
////}
|
||||
////
|
||||
////class C implements A {[| |]}
|
||||
|
@ -17,11 +18,15 @@ verify.codeFix({
|
|||
private y: number;
|
||||
protected z: number;
|
||||
public w: number;
|
||||
public useY() { this.y; }
|
||||
}
|
||||
|
||||
class C implements A {
|
||||
x: number;
|
||||
protected z: number;
|
||||
public w: number;
|
||||
public useY(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
////abstract class C1 { }
|
||||
////abstract class C2 {
|
||||
//// abstract fA<T extends number>();
|
||||
//// abstract fA<T extends number>(): T;
|
||||
////}
|
||||
////interface I1 extends C1, C2 { }
|
||||
////class C3 implements I1 {[| |]}
|
||||
|
@ -12,11 +12,11 @@ verify.codeFix({
|
|||
newFileContent:
|
||||
`abstract class C1 { }
|
||||
abstract class C2 {
|
||||
abstract fA<T extends number>();
|
||||
abstract fA<T extends number>(): T;
|
||||
}
|
||||
interface I1 extends C1, C2 { }
|
||||
class C3 implements I1 {
|
||||
fA<T extends number>() {
|
||||
fA<T extends number>(): T {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}`,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//// this.a = 12;
|
||||
//// super();
|
||||
//// |]}
|
||||
//// m() { this.a; } // avoid unused 'a'
|
||||
////}
|
||||
verify.rangeAfterCodeFix(`
|
||||
super();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////class Base{
|
||||
//// constructor(id: number) { }
|
||||
//// constructor(id: number) { id; }
|
||||
////}
|
||||
////class C extends Base{
|
||||
//// constructor(private a:number) {
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
//// export interface Foo {
|
||||
//// bar: string;
|
||||
//// }
|
||||
//// const x: [|Foo.bar|] = ""
|
||||
//// export const x: [|Foo.bar|] = ""
|
||||
|
||||
verify.rangeAfterCodeFix(`Foo["bar"]`);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @noImplicitAny: true
|
||||
////function f1(a) { }
|
||||
////function f1(a) { a; }
|
||||
////function h1() {
|
||||
//// class C { p: number };
|
||||
//// f1({ ofTypeC: new C() });
|
||||
////}
|
||||
////
|
||||
////function f2(a) { }
|
||||
////function f2(a) { a; }
|
||||
////function h2() {
|
||||
//// interface I { a: number }
|
||||
//// var i: I = {a : 1};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// @noImplicitAny: true
|
||||
////function f([|a? |]){
|
||||
//// a;
|
||||
////}
|
||||
////f();
|
||||
////f(1);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// @noImplicitAny: true
|
||||
////function f(a: number, [|...rest |]){
|
||||
//// a; rest;
|
||||
////}
|
||||
////f(1);
|
||||
////f(2, "s1");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// @noImplicitAny: true
|
||||
////function f(a: number, [|...rest |]){
|
||||
//// a; rest;
|
||||
////}
|
||||
////f(1);
|
||||
////f(2, "s1");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// @noImplicitAny: true
|
||||
////function f(a: number, [|...rest |]){
|
||||
//// a;
|
||||
//// rest.push(22);
|
||||
////}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// @noImplicitAny: true
|
||||
////class C {
|
||||
//// set [|x(v)|] {
|
||||
//// v;
|
||||
//// }
|
||||
////}
|
||||
////(new C).x = 1;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// @noImplicitAny: true
|
||||
////class C {
|
||||
//// set [|x(v)|] {
|
||||
//// v;
|
||||
//// }
|
||||
////}
|
||||
////(new C).x = 1;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
// @Filename: /b.ts
|
||||
////export class C {
|
||||
//// [|set x(val) {}|]
|
||||
//// [|set x(val) { val; }|]
|
||||
//// method() { this.x = import("./a"); }
|
||||
////}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
////export const fooooooooo = 1;
|
||||
|
||||
// @Filename: f2.ts
|
||||
////import {[|fooooooooa|]} from "./f1";
|
||||
////import {[|fooooooooa|]} from "./f1"; fooooooooa;
|
||||
|
||||
goTo.file("f2.ts")
|
||||
verify.rangeAfterCodeFix(`fooooooooo`);
|
25
tests/cases/fourslash/codeFixUnusedIdentifier_suggestion.ts
Normal file
25
tests/cases/fourslash/codeFixUnusedIdentifier_suggestion.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////function f([|p|]) {
|
||||
//// const [|x|] = 0;
|
||||
////}
|
||||
|
||||
const [r0, r1] = test.ranges();
|
||||
verify.getSuggestionDiagnostics([
|
||||
{
|
||||
message: "'p' is declared but its value is never read.",
|
||||
range: r0,
|
||||
code: 6133,
|
||||
unused: true,
|
||||
},
|
||||
{
|
||||
message: "'x' is declared but its value is never read.",
|
||||
range: r1,
|
||||
code: 6133,
|
||||
unused: true,
|
||||
}
|
||||
]);
|
||||
|
||||
verify.codeFixAvailable(
|
||||
["Remove declaration for: 'p'", "Prefix 'p' with an underscore", "Remove declaration for: 'x'"]
|
||||
.map(description => ({ description })));
|
|
@ -8,15 +8,18 @@
|
|||
|
||||
// @Filename: /b.ts
|
||||
/////*com ment*/import * as [|a|] from "./a";/*tnem moc*/
|
||||
////a;
|
||||
|
||||
// @Filename: /c.ts
|
||||
/////*com ment*/import [|a|] = require("./a");/*tnem moc*/
|
||||
////a;
|
||||
|
||||
// @Filename: /d.ts
|
||||
////import "./a";
|
||||
|
||||
// @Filename: /e.ts
|
||||
////import * as n from "./non-existant";
|
||||
////n;
|
||||
|
||||
for (const file of ["/b.ts", "/c.ts"]) {
|
||||
goTo.file(file);
|
||||
|
@ -29,7 +32,9 @@ for (const file of ["/b.ts", "/c.ts"]) {
|
|||
|
||||
verify.codeFix({
|
||||
description: "Convert to default import",
|
||||
newFileContent: `/*com ment*/import a from "./a";/*tnem moc*/`,
|
||||
newFileContent:
|
||||
`/*com ment*/import a from "./a";/*tnem moc*/
|
||||
a;`,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
/////**
|
||||
//// * This is a cool function!
|
||||
////*/
|
||||
////fn.prototype.bar = function (x, y, z) {
|
||||
////fn.prototype.bar = function (y) {
|
||||
//// this.x = y;
|
||||
////};
|
||||
|
||||
|
@ -38,7 +38,7 @@ verify.codeFix({
|
|||
/**
|
||||
* This is a cool function!
|
||||
*/
|
||||
bar(x, y, z) {
|
||||
bar(y) {
|
||||
this.x = y;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -518,6 +518,7 @@ declare namespace FourSlashInterface {
|
|||
/** @default `test.ranges()[0]` */
|
||||
range?: Range;
|
||||
code: number;
|
||||
unused?: true;
|
||||
}
|
||||
interface VerifyDocumentHighlightsOptions {
|
||||
filesToSearch?: ReadonlyArray<string>;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @noImplicitAny: true
|
||||
//// function f(/*1*/x) {
|
||||
//// }
|
||||
//// f(
|
||||
////function f(/*1*/x) {
|
||||
//// x;
|
||||
////}
|
||||
////f(
|
||||
|
||||
verify.not.codeFixAvailable([]);
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @noImplicitAny: true
|
||||
//// function f(new C(100, 3, undefined)
|
||||
|
||||
verify.not.codeFixAvailable([]);
|
||||
////function f(new C(100, 3, undefined)
|
||||
|
||||
verify.codeFix({
|
||||
description: "Prefix 'C' with an underscore",
|
||||
newFileContent: "function f(new _C(100, 3, undefined)",
|
||||
});
|
||||
|
|
|
@ -11,10 +11,12 @@
|
|||
////exports.y;
|
||||
////
|
||||
////exports.z = 2;
|
||||
////function f(z) {
|
||||
//// exports.z;
|
||||
////exports.f = function(z) {
|
||||
//// z;
|
||||
////}
|
||||
|
||||
// TODO: GH#22492 Should be a able access `exports.z` inside `exports.f`
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent:
|
||||
|
@ -26,9 +28,8 @@ const _y = y;
|
|||
export { _y as y };
|
||||
_y;
|
||||
|
||||
const _z = 2;
|
||||
export { _z as z };
|
||||
function f(z) {
|
||||
_z;
|
||||
export const z = 2;
|
||||
export function f(z) {
|
||||
z;
|
||||
}`,
|
||||
});
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// @allowJs: true
|
||||
|
||||
// @Filename: /a.js
|
||||
////exports.f = async function* f(p) {}
|
||||
////exports.f = async function* f(p) { p; }
|
||||
////exports.C = class C extends D { m() {} }
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent:
|
||||
`export async function* f(p) { }
|
||||
`export async function* f(p) { p; }
|
||||
export class C extends D {
|
||||
m() { }
|
||||
}`,
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
// @Filename: /a.js
|
||||
////const [x, y] = /*a*/require/*b*/("x");
|
||||
////x; y;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent: `import _x from "x";
|
||||
const [x, y] = _x;`,
|
||||
const [x, y] = _x;
|
||||
x; y;`,
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
////const x = require("x");
|
||||
////const [a, b] = require("x");
|
||||
////const {c, ...d} = require("x");
|
||||
////x; a; b; c; d;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
|
@ -14,5 +15,6 @@ verify.codeFix({
|
|||
import _x from "x";
|
||||
const [a, b] = _x;
|
||||
import __x from "x";
|
||||
const { c, ...d } = __x;`,
|
||||
const { c, ...d } = __x;
|
||||
x; a; b; c; d;`,
|
||||
});
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
|
||||
// @Filename: /a.js
|
||||
////const x = require("x"), y = 0, { z } = require("z");
|
||||
////x; y; z;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent:
|
||||
`import x from "x";
|
||||
const y = 0;
|
||||
import { z } from "z";`,
|
||||
import { z } from "z";
|
||||
x; y; z;`,
|
||||
});
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
// @Filename: /a.js
|
||||
////const { x: { a, b } } = require("x");
|
||||
////a; b;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent:
|
||||
`import x from "x";
|
||||
const { x: { a, b } } = x;`,
|
||||
const { x: { a, b } } = x;
|
||||
a; b;`,
|
||||
});
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
// @Filename: /a.js
|
||||
////const { x, y: z } = require("x");
|
||||
////x; z;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent: 'import { x, y as z } from "x";',
|
||||
newFileContent:
|
||||
`import { x, y as z } from "x";
|
||||
x; z;`,
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
////const a = require("a").a;
|
||||
////const [a, b] = require("c").d;
|
||||
////const [a, b] = require("c").a; // Test that we avoid shadowing the earlier local variable 'a' from 'const [a,b] = d;'.
|
||||
////x; a; b;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
|
@ -18,5 +19,6 @@ import { a } from "a";
|
|||
import { d } from "c";
|
||||
const [a, b] = d;
|
||||
import { a as _a } from "c";
|
||||
const [a, b] = _a; // Test that we avoid shadowing the earlier local variable 'a' from 'const [a,b] = d;'.`,
|
||||
const [a, b] = _a; // Test that we avoid shadowing the earlier local variable 'a' from 'const [a,b] = d;'.
|
||||
x; a; b;`,
|
||||
});
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
// @allowJs: true
|
||||
// @Filename: /a.js
|
||||
////const a = require('a');
|
||||
////const a = require('a'); a;
|
||||
|
||||
verify.codeFix({
|
||||
description: "Convert to ES6 module",
|
||||
newFileContent: "import a from 'a';",
|
||||
newFileContent: "import a from 'a'; a;",
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue