Merge pull request #11547 from Microsoft/interfaceFixes

Codefix for implementing interfaces
This commit is contained in:
Mohamed Hegazy
2016-12-30 14:28:55 -08:00
committed by GitHub
163 changed files with 1570 additions and 228 deletions

View File

@@ -1,50 +0,0 @@
/* @internal */
namespace ts {
export interface CodeFix {
errorCodes: number[];
getCodeActions(context: CodeFixContext): CodeAction[] | undefined;
}
export interface CodeFixContext {
errorCode: number;
sourceFile: SourceFile;
span: TextSpan;
program: Program;
newLineCharacter: string;
host: LanguageServiceHost;
cancellationToken: CancellationToken;
}
export namespace codefix {
const codeFixes = createMap<CodeFix[]>();
export function registerCodeFix(action: CodeFix) {
forEach(action.errorCodes, error => {
let fixes = codeFixes[error];
if (!fixes) {
fixes = [];
codeFixes[error] = fixes;
}
fixes.push(action);
});
}
export function getSupportedErrorCodes() {
return Object.keys(codeFixes);
}
export function getFixes(context: CodeFixContext): CodeAction[] {
const fixes = codeFixes[context.errorCode];
let allActions: CodeAction[] = [];
forEach(fixes, f => {
const actions = f.getCodeActions(context);
if (actions && actions.length > 0) {
allActions = allActions.concat(actions);
}
});
return allActions;
}
}
}

View File

@@ -0,0 +1,59 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code],
getCodeActions: getActionForClassLikeMissingAbstractMember
});
registerCodeFix({
errorCodes: [Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code],
getCodeActions: getActionForClassLikeMissingAbstractMember
});
function getActionForClassLikeMissingAbstractMember(context: CodeFixContext): CodeAction[] | undefined {
const sourceFile = context.sourceFile;
const start = context.span.start;
// This is the identifier in the case of a class declaration
// or the class keyword token in the case of a class expression.
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
if (isClassLike(token.parent)) {
const classDecl = token.parent as ClassLikeDeclaration;
const startPos = classDecl.members.pos;
const classType = checker.getTypeAtLocation(classDecl) as InterfaceType;
const instantiatedExtendsType = checker.getBaseTypes(classType)[0];
// Note that this is ultimately derived from a map indexed by symbol names,
// so duplicates cannot occur.
const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType);
const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember);
const insertion = getMissingMembersInsertion(classDecl, abstractAndNonPrivateExtendsSymbols, checker, context.newLineCharacter);
if (insertion.length) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: [{
span: { start: startPos, length: 0 },
newText: insertion
}]
}]
}];
}
}
return undefined;
}
function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {
const decls = symbol.getDeclarations();
Debug.assert(!!(decls && decls.length > 0));
const flags = getModifierFlags(decls[0]);
return !(flags & ModifierFlags.Private) && !!(flags & ModifierFlags.Abstract);
}
}

View File

@@ -0,0 +1,75 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code],
getCodeActions: getActionForClassLikeIncorrectImplementsInterface
});
function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
const classDecl = getContainingClass(token);
if (!classDecl) {
return undefined;
}
const startPos: number = classDecl.members.pos;
const classType = checker.getTypeAtLocation(classDecl);
const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number);
const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String);
const result: CodeAction[] = [];
for (const implementedTypeNode of implementedTypeNodes) {
const implementedType = checker.getTypeFromTypeNode(implementedTypeNode) as InterfaceType;
// Note that this is ultimately derived from a map indexed by symbol names,
// so duplicates cannot occur.
const implementedTypeSymbols = checker.getPropertiesOfType(implementedType);
const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private));
let insertion = getMissingIndexSignatureInsertion(implementedType, IndexKind.Number, classDecl, hasNumericIndexSignature);
insertion += getMissingIndexSignatureInsertion(implementedType, IndexKind.String, classDecl, hasStringIndexSignature);
insertion += getMissingMembersInsertion(classDecl, nonPrivateMembers, checker, context.newLineCharacter);
const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
if (insertion) {
pushAction(result, insertion, message);
}
}
return result;
function getMissingIndexSignatureInsertion(type: InterfaceType, kind: IndexKind, enclosingDeclaration: ClassLikeDeclaration, hasIndexSigOfKind: boolean) {
if (!hasIndexSigOfKind) {
const IndexInfoOfKind = checker.getIndexInfoOfType(type, kind);
if (IndexInfoOfKind) {
const writer = getSingleLineStringWriter();
checker.getSymbolDisplayBuilder().buildIndexSignatureDisplay(IndexInfoOfKind, writer, kind, enclosingDeclaration);
const result = writer.string();
releaseStringWriter(writer);
return result;
}
}
return "";
}
function pushAction(result: CodeAction[], insertion: string, description: string): void {
const newAction: CodeAction = {
description: description,
changes: [{
fileName: sourceFile.fileName,
textChanges: [{
span: { start: startPos, length: 0 },
newText: insertion
}]
}]
};
result.push(newAction);
}
}
}

View File

@@ -1,28 +1,5 @@
/* @internal */
namespace ts.codefix {
function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
// First token is the open curly, this is where we want to put the 'super' call.
return constructor.body.getFirstToken(sourceFile).getEnd();
}
registerCodeFix({
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ConstructorKeyword) {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
}];
}
});
registerCodeFix({
errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code],
getCodeActions: (context: CodeFixContext) => {

View File

@@ -0,0 +1,20 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ConstructorKeyword) {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
}];
}
});
}

View File

@@ -0,0 +1,49 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const classDeclNode = getContainingClass(token);
if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) {
return undefined;
}
const heritageClauses = classDeclNode.heritageClauses;
if (!(heritageClauses && heritageClauses.length > 0)) {
return undefined;
}
const extendsToken = heritageClauses[0].getFirstToken();
if (!(extendsToken && extendsToken.kind === SyntaxKind.ExtendsKeyword)) {
return undefined;
}
let changeStart = extendsToken.getStart(sourceFile);
let changeEnd = extendsToken.getEnd();
const textChanges: TextChange[] = [{ newText: " implements", span: { start: changeStart, length: changeEnd - changeStart } }];
// We replace existing keywords with commas.
for (let i = 1; i < heritageClauses.length; i++) {
const keywordToken = heritageClauses[i].getFirstToken();
if (keywordToken) {
changeStart = keywordToken.getStart(sourceFile);
changeEnd = keywordToken.getEnd();
textChanges.push({ newText: ",", span: { start: changeStart, length: changeEnd - changeStart } });
}
}
const result = [{
description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
}];
return result;
}
});
}

View File

@@ -1,3 +1,9 @@
///<reference path='superFixes.ts' />
///<reference path='importFixes.ts' />
///<reference path='unusedIdentifierFixes.ts' />
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
/// <reference path="fixConstructorForDerivedNeedSuperCall.ts" />
/// <reference path="fixExtendsInterfaceBecomesImplements.ts" />
/// <reference path='unusedIdentifierFixes.ts' />
/// <reference path='importFixes.ts' />
/// <reference path='helpers.ts' />

View File

@@ -0,0 +1,156 @@
/* @internal */
namespace ts.codefix {
/**
* 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.
* @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
* @returns Empty string iff there are no member insertions.
*/
export function getMissingMembersInsertion(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker, newlineChar: string): string {
const classMembers = classDeclaration.symbol.members;
const missingMembers = possiblyMissingSymbols.filter(symbol => !(symbol.getName() in classMembers));
let insertion = "";
for (const symbol of missingMembers) {
insertion = insertion.concat(getInsertionForMemberSymbol(symbol, classDeclaration, checker, newlineChar));
}
return insertion;
}
/**
* @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
*/
function getInsertionForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, newlineChar: string): string {
// const name = symbol.getName();
const type = checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration);
const declarations = symbol.getDeclarations();
if (!(declarations && declarations.length)) {
return "";
}
const declaration = declarations[0] as Declaration;
const name = declaration.name ? declaration.name.getText() : undefined;
const visibility = getVisibilityPrefix(getModifierFlags(declaration));
switch (declaration.kind) {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyDeclaration:
const typeString = checker.typeToString(type, enclosingDeclaration, TypeFormatFlags.None);
return `${visibility}${name}: ${typeString};${newlineChar}`;
case SyntaxKind.MethodSignature:
case SyntaxKind.MethodDeclaration:
// The signature for the implementation appears as an entry in `signatures` iff
// there is only one signature.
// If there are overloads and an implementation signature, it appears as an
// extra declaration that isn't a signature for `type`.
// If there is more than one overload but no implementation signature
// (eg: an abstract method or interface declaration), there is a 1-1
// correspondence of declarations and signatures.
const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
if (!(signatures && signatures.length > 0)) {
return "";
}
if (declarations.length === 1) {
Debug.assert(signatures.length === 1);
const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
}
let result = "";
for (let i = 0; i < signatures.length; i++) {
const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
result += `${visibility}${name}${sigString};${newlineChar}`;
}
// If there is a declaration with a body, it is the last declaration,
// and it isn't caught by `getSignaturesOfType`.
let bodySig: Signature | undefined = undefined;
if (declarations.length > signatures.length) {
bodySig = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration);
}
else {
Debug.assert(declarations.length === signatures.length);
bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker);
}
const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
result += `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
return result;
default:
return "";
}
}
function createBodySignatureWithAnyTypes(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Signature {
const newSignatureDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration;
newSignatureDeclaration.parent = enclosingDeclaration;
newSignatureDeclaration.name = signatures[0].getDeclaration().name;
let maxNonRestArgs = -1;
let maxArgsIndex = 0;
let minArgumentCount = signatures[0].minArgumentCount;
let hasRestParameter = false;
for (let i = 0; i < signatures.length; i++) {
const sig = signatures[i];
minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount);
hasRestParameter = hasRestParameter || sig.hasRestParameter;
const nonRestLength = sig.parameters.length - (sig.hasRestParameter ? 1 : 0);
if (nonRestLength > maxNonRestArgs) {
maxNonRestArgs = nonRestLength;
maxArgsIndex = i;
}
}
const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName());
const optionalToken = createToken(SyntaxKind.QuestionToken);
newSignatureDeclaration.parameters = createNodeArray<ParameterDeclaration>();
for (let i = 0; i < maxNonRestArgs; i++) {
const newParameter = createParameterDeclarationWithoutType(i, minArgumentCount, newSignatureDeclaration);
newSignatureDeclaration.parameters.push(newParameter);
}
if (hasRestParameter) {
const restParameter = createParameterDeclarationWithoutType(maxNonRestArgs, minArgumentCount, newSignatureDeclaration);
restParameter.dotDotDotToken = createToken(SyntaxKind.DotDotDotToken);
newSignatureDeclaration.parameters.push(restParameter);
}
return checker.getSignatureFromDeclaration(newSignatureDeclaration);
function createParameterDeclarationWithoutType(index: number, minArgCount: number, enclosingSignatureDeclaration: SignatureDeclaration): ParameterDeclaration {
const newParameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration;
newParameter.symbol = new SymbolConstructor(SymbolFlags.FunctionScopedVariable, maxArgsParameterSymbolNames[index] || "rest");
newParameter.symbol.valueDeclaration = newParameter;
newParameter.symbol.declarations = [newParameter];
newParameter.parent = enclosingSignatureDeclaration;
if (index >= minArgCount) {
newParameter.questionToken = optionalToken;
}
return newParameter;
}
}
function getMethodBodyStub(newLineChar: string) {
return ` {${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`;
}
function getVisibilityPrefix(flags: ModifierFlags): string {
if (flags & ModifierFlags.Public) {
return "public ";
}
else if (flags & ModifierFlags.Protected) {
return "protected ";
}
return "";
}
const SymbolConstructor = objectAllocator.getSymbolConstructor();
}