missing member fixes use createTypeNode

This commit is contained in:
Arthur Ozga 2017-03-12 12:49:29 -07:00
parent bf2acf1d2c
commit d6863bea25
3 changed files with 112 additions and 69 deletions

View File

@ -19,10 +19,9 @@ namespace ts.codefix {
const checker = context.program.getTypeChecker();
if (isClassLike(token.parent)) {
const classDecl = token.parent as ClassLikeDeclaration;
const startPos = classDecl.members.pos;
const classDeclaration = token.parent as ClassLikeDeclaration;
const extendsNode = getClassExtendsHeritageClauseElement(classDecl);
const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration);
const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode);
// Note that this is ultimately derived from a map indexed by symbol names,
@ -30,18 +29,12 @@ namespace ts.codefix {
const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType);
const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember);
const insertion = getMissingMembersInsertion(classDecl, abstractAndNonPrivateExtendsSymbols, checker, context.newLineCharacter);
if (insertion.length) {
const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker);
const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context);
if(changes && changes.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: [{
span: { start: startPos, length: 0 },
newText: insertion
}]
}]
changes
}];
}
}

View File

@ -16,7 +16,7 @@ namespace ts.codefix {
return undefined;
}
const startPos: number = classDecl.members.pos;
const openBrace = getOpenBraceOfClassLike(classDecl, sourceFile);
const classType = checker.getTypeAtLocation(classDecl) as InterfaceType;
const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
@ -31,43 +31,46 @@ namespace ts.codefix {
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);
let newNodes: Node[] = [];
createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes);
createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes);
newNodes = newNodes.concat(createMissingMemberNodes(classDecl, nonPrivateMembers, checker));
const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
if (insertion) {
pushAction(result, insertion, message);
if (newNodes.length > 0) {
pushAction(result, newNodes, 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;
}
function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void {
if (hasIndexSigOfKind) {
return undefined;
}
return "";
const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);
if (!indexInfoOfKind) {
return undefined;
}
const typeNode = checker.createTypeNode(indexInfoOfKind.type);
let name: string;
const newIndexSignatureDeclaration = createIndexSignatureDeclaration(
[createParameter(
/*decorators*/undefined
, /*modifiers*/ undefined
, /*dotDotDotToken*/ undefined
, name
, /*questionToken*/ undefined
, kind === IndexKind.String ? createKeywordTypeNode(SyntaxKind.StringKeyword) : createKeywordTypeNode(SyntaxKind.NumberKeyword))]
, typeNode);
newNodes.push(newIndexSignatureDeclaration);
}
function pushAction(result: CodeAction[], insertion: string, description: string): void {
function pushAction(result: CodeAction[], newNodes: Node[], description: string): void {
const newAction: CodeAction = {
description: description,
changes: [{
fileName: sourceFile.fileName,
textChanges: [{
span: { start: startPos, length: 0 },
newText: insertion
}]
}]
changes: newNodesToChanges(newNodes, openBrace, context)
};
result.push(newAction);
}

View File

@ -1,31 +1,53 @@
/* @internal */
namespace ts.codefix {
export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) {
const sourceFile = context.sourceFile;
if (!(newNodes)) {
// TODO: make the appropriate value flow through gracefully.
throw new Error("newNodesToChanges expects an array");
}
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
for (let i = newNodes.length - 1; i >= 0; i--) {
changeTracker.insertNodeAfter(sourceFile, insertAfter, newNodes[i], { insertTrailingNewLine: true });
}
return changeTracker.getChanges();
}
/**
* 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 {
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker): Node[] {
const classMembers = classDeclaration.symbol.members;
const missingMembers = possiblyMissingSymbols.filter(symbol => !classMembers.has(symbol.getName()));
let insertion = "";
let newNodes: Node[] = [];
for (const symbol of missingMembers) {
insertion = insertion.concat(getInsertionForMemberSymbol(symbol, classDeclaration, checker, newlineChar));
const newNode = getNewNodeForMemberSymbol(symbol, classDeclaration, checker);
if (newNode) {
if (Array.isArray(newNode)) {
newNodes = newNodes.concat(newNode);
}
else {
newNodes.push(newNode);
}
}
}
return insertion;
return newNodes;
}
/**
* @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 {
function getNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined {
const declarations = symbol.getDeclarations();
if (!(declarations && declarations.length)) {
return "";
return undefined;
}
const declaration = declarations[0] as Declaration;
@ -39,9 +61,16 @@ namespace ts.codefix {
case SyntaxKind.SetAccessor:
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyDeclaration:
const typeString = checker.typeToString(type, enclosingDeclaration, TypeFormatFlags.None);
return `${visibility}${name}: ${typeString};${newlineChar}`;
const typeNode = checker.createTypeNode(type);
// TODO: add modifiers.
const property = createProperty(
/*decorators*/undefined
, /*modifiers*/ undefined
, name
, /*questionToken*/ undefined
, typeNode
, /*initializer*/ undefined);
return property;
case SyntaxKind.MethodSignature:
case SyntaxKind.MethodDeclaration:
// The signature for the implementation appears as an entry in `signatures` iff
@ -53,18 +82,22 @@ namespace ts.codefix {
// correspondence of declarations and signatures.
const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
if (!(signatures && signatures.length > 0)) {
return "";
return undefined;
}
if (declarations.length === 1) {
Debug.assert(signatures.length === 1);
const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
return getStubbedMethod(visibility, name, sigString, newlineChar);
// TODO: extract signature declaration from a signature.
// const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
// TODO: get parameters working.
// TODO: add support for type parameters.
return createStubbedMethod([visibility], name, /*typeParameters*/undefined, []);
}
let result = "";
let signatureDeclarations = [];
for (let i = 0; i < signatures.length; i++) {
const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
result += `${visibility}${name}${sigString};${newlineChar}`;
// const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
// TODO: make signatures instead of methods
signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, []));
}
// If there is a declaration with a body, it is the last declaration,
@ -77,12 +110,12 @@ namespace ts.codefix {
Debug.assert(declarations.length === signatures.length);
bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker);
}
const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
result += getStubbedMethod(visibility, name, sigString, newlineChar);
return result;
// const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, []));
return signatureDeclarations;
default:
return "";
return undefined;
}
}
@ -138,22 +171,36 @@ namespace ts.codefix {
}
}
export function getStubbedMethod(visibility: string, name: string, sigString = "()", newlineChar: string): string {
return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType?: TypeNode) {
return createMethod(
/*decorators*/undefined
, /*modifiers*/modifiers
, /*asteriskToken*/undefined
, name
, typeParameters
, parameters
, returnType
, createStubbedMethodBody());
}
function getMethodBodyStub(newlineChar: string) {
return ` {${newlineChar}throw new Error('Method not implemented.');${newlineChar}}${newlineChar}`;
function createStubbedMethodBody() {
return createBlock(
[createThrow(
createNew(
createIdentifier('Error')
, /*typeArguments*/undefined
, [createLiteral('Method not implemented.')]))]
, /*multiline*/true);
}
function getVisibilityPrefixWithSpace(flags: ModifierFlags): string {
function getVisibilityPrefixWithSpace(flags: ModifierFlags) {
if (flags & ModifierFlags.Public) {
return "public ";
return createToken(SyntaxKind.PublicKeyword);
}
else if (flags & ModifierFlags.Protected) {
return "protected ";
return createToken(SyntaxKind.ProtectedKeyword);
}
return "";
return undefined;
}
const SymbolConstructor = objectAllocator.getSymbolConstructor();