mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-10 06:41:59 -06:00
missing member fixes use createTypeNode
This commit is contained in:
parent
bf2acf1d2c
commit
d6863bea25
@ -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
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user