From b1e97b370f2ba52fc347def309b94c772b00bcde Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 9 Nov 2016 16:04:56 -0800 Subject: [PATCH] Get one fix per interface --- ...sDoesntImplementInheritedAbstractMember.ts | 9 ++-- .../fixClassIncorrectlyImplementsInterface.ts | 47 +++++++++++++++---- src/services/utilities.ts | 29 ++++-------- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 5c8532b5436..8a9836afb54 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -9,10 +9,13 @@ namespace ts.codefix { const checker = context.program.getTypeChecker(); if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) { - const classDeclaration = token.parent; - const startPos = classDeclaration.members.pos; + const classDecl = token.parent; + const startPos = classDecl.members.pos; - const insertion = getMissingAbstractMemberInsertion(classDeclaration, checker, context.newLineCharacter); + const InstantiatedExtendsType = checker.getTypeFromTypeReference(getClassExtendsHeritageClauseElement(classDecl)); + const resolvedExtendsType = checker.resolveStructuredTypeMembers(InstantiatedExtendsType); + + const insertion = getMissingAbstractMembersInsertion(classDecl, resolvedExtendsType, checker, context.newLineCharacter); if (insertion.length > 0) { return [{ diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 17c716eadc6..bf15f4fcf38 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -8,15 +8,40 @@ namespace ts.codefix { const token = getTokenAtPosition(sourceFile, start); const checker = context.program.getTypeChecker(); - if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) { - const classDeclaration = token.parent; - const startPos: number = classDeclaration.members.pos; + if (!(token.kind === SyntaxKind.Identifier && isClassLike(token.parent))) { + return undefined; + } + const classDecl = token.parent; + const startPos: number = classDecl.members.pos; - const insertion = getMissingInterfaceMembersInsertion(classDeclaration, checker, context.newLineCharacter); + const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl); + const implementedTypes = implementedTypeNodes.map(checker.getTypeFromTypeReference); + const resolvedImplementedTypes = implementedTypes.map(checker.resolveStructuredTypeMembers); + let result: CodeAction[] | undefined = undefined; + + for (const resolvedType of resolvedImplementedTypes) { + const insertion = getMissingMembersInsertion(classDecl, resolvedType, checker, context.newLineCharacter); + result = pushAction(insertion, getLocaleSpecificMessage(Diagnostics.Implement_interface_on_class), result); + } + + // TODO: (arozga) Get this working and figure out how to test it reliably. + /* + // If there are multiple objects, we additionally try to generate a combined fix that simultaneously implements all types. + const intersectionType = checker.getIntersectionType(implementedTypes); + if(intersectionType.flags & TypeFlags.Intersection) { + const resolvedIntersectionType = checker.resolveStructuredTypeMembers(intersectionType) + const insertion = getMissingMembersInsertion(classDecl, resolvedIntersectionType, checker, context.newLineCharacter); + result = pushAction(insertion, "stubbed locale message", result) + } + */ + + return result; + + function pushAction(insertion: string, description: string, result?: CodeAction[]): CodeAction[] { if (insertion && insertion.length) { - return [{ - description: getLocaleSpecificMessage(Diagnostics.Implement_interface_on_class), + const newAction: CodeAction = { + description: description, changes: [{ fileName: sourceFile.fileName, textChanges: [{ @@ -24,10 +49,16 @@ namespace ts.codefix { newText: insertion }] }] - }]; + }; + if (result) { + result.push(newAction); + } + else { + result = [newAction]; + } } + return result; } - return undefined; } }); } \ No newline at end of file diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a0994b691e9..8f3ce8ae413 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1359,32 +1359,19 @@ namespace ts { }; } - export function getMissingAbstractMemberInsertion(classDecl: ClassDeclaration, checker: TypeChecker, newlineChar: string): string { + export function getMissingAbstractMembersInsertion(classDecl: ClassDeclaration, resolvedType: ResolvedType, checker: TypeChecker, newlineChar: string): string { const classSymbol = checker.getSymbolOfNode(classDecl); - - const InstantiatedExtendsType = checker.getTypeFromTypeReference(getClassExtendsHeritageClauseElement(classDecl)); - const resolvedExtendsType = checker.resolveStructuredTypeMembers(InstantiatedExtendsType); - - const missingMembers = filterMissingMembers(filterAbstract(filterNonPrivate(resolvedExtendsType.members)), classSymbol.members); - + const missingMembers = filterMissingMembers(filterAbstract(filterNonPrivate(resolvedType.members)), classSymbol.members); return getInsertionsForMembers(missingMembers, classDecl, checker, newlineChar); } - export function getMissingInterfaceMembersInsertion(classDecl: ClassDeclaration, checker: TypeChecker, newlineChar: string): string { - const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl); - + /** + * Finds members of the resolved type that are missing in the class pointed to by class decl + * and generates source code for the missing members. + */ + export function getMissingMembersInsertion(classDecl: ClassDeclaration, resolvedType: ResolvedType, checker: TypeChecker, newlineChar: string): string { const classSymbol = checker.getSymbolOfNode(classDecl); - - let implementsIntersectionType: IntersectionType | InterfaceType; - if (implementedTypeNodes.length > 1) { - implementsIntersectionType = checker.getIntersectionType(implementedTypeNodes.map(checker.getTypeFromTypeReference)); - } - else { - implementsIntersectionType = checker.getTypeFromTypeReference(implementedTypeNodes[0]); - } - - const structuredType = checker.resolveStructuredTypeMembers(implementsIntersectionType); - const missingMembers = filterMissingMembers(filterNonPrivate(structuredType.members), classSymbol.members); + const missingMembers = filterMissingMembers(filterNonPrivate(resolvedType.members), classSymbol.members); return getInsertionsForMembers(missingMembers, classDecl, checker, newlineChar); }