Still re-writing missing member grabber

This commit is contained in:
Arthur Ozga
2016-11-04 16:35:51 -07:00
parent 36c5befae9
commit 1b8486df89
5 changed files with 165 additions and 70 deletions

View File

@@ -74,6 +74,7 @@ namespace ts {
getGlobalDiagnostics,
getTypeOfSymbolAtLocation,
getSymbolsOfParameterPropertyDeclaration,
getTypeOfSymbol,
getDeclaredTypeOfSymbol,
getPropertiesOfType,
getPropertyOfType,
@@ -84,6 +85,7 @@ namespace ts {
resolveStructuredTypeMembers,
getNonNullableType,
getSymbolsInScope,
getSymbolOfNode,
getSymbolAtLocation,
getShorthandAssignmentValueSymbol,
getExportSpecifierLocalTargetSymbol,
@@ -4352,6 +4354,9 @@ namespace ts {
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
}
/**
* Converts an AnonymousType to a ResolvedType.
*/
function resolveAnonymousTypeMembers(type: AnonymousType) {
const symbol = type.symbol;
if (type.target) {

View File

@@ -2245,6 +2245,7 @@ namespace ts {
export interface TypeChecker {
getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type;
getTypeOfSymbol(symbol: Symbol): Type;
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
getPropertiesOfType(type: Type): Symbol[];
getPropertyOfType(type: Type, propertyName: string): Symbol;
@@ -2256,6 +2257,7 @@ namespace ts {
getNonNullableType(type: Type): Type;
getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
getSymbolOfNode(node: Node): Symbol;
getSymbolAtLocation(node: Node): Symbol;
getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[];
getShorthandAssignmentValueSymbol(location: Node): Symbol;
@@ -2760,7 +2762,7 @@ namespace ts {
objectFlags: ObjectFlags;
}
// Class and interface types (TypeFlags.Class and TypeFlags.Interface)
/** Class and interface types (TypeFlags.Class and TypeFlags.Interface). */
export interface InterfaceType extends ObjectType {
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
outerTypeParameters: TypeParameter[]; // Outer type parameters (undefined if none)
@@ -2780,14 +2782,16 @@ namespace ts {
declaredNumberIndexInfo: IndexInfo; // Declared numeric indexing info
}
// Type references (TypeFlags.Reference). When a class or interface has type parameters or
// a "this" type, references to the class or interface are made using type references. The
// typeArguments property specifies the types to substitute for the type parameters of the
// class or interface and optionally includes an extra element that specifies the type to
// substitute for "this" in the resulting instantiation. When no extra argument is present,
// the type reference itself is substituted for "this". The typeArguments property is undefined
// if the class or interface has no type parameters and the reference isn't specifying an
// explicit "this" argument.
/**
* Type references (TypeFlags.Reference). When a class or interface has type parameters or
* a "this" type, references to the class or interface are made using type references. The
* typeArguments property specifies the types to substitute for the type parameters of the
* class or interface and optionally includes an extra element that specifies the type to
* substitute for "this" in the resulting instantiation. When no extra argument is present,
* the type reference itself is substituted for "this". The typeArguments property is undefined
* if the class or interface has no type parameters and the reference isn't specifying an
* explicit "this" argument.
*/
export interface TypeReference extends ObjectType {
target: GenericType; // Type reference target
typeArguments: Type[]; // Type reference type arguments (undefined if none)
@@ -2825,7 +2829,6 @@ namespace ts {
finalArrayType?: Type; // Final array type of evolving array type
}
/* @internal */
// Resolved object, union, or intersection type
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {
members: SymbolTable; // Properties by name

View File

@@ -11,17 +11,20 @@ namespace ts.codefix {
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
const classDeclaration = <ClassDeclaration>token.parent;
const startPos = classDeclaration.members.pos;
const abstractClassMembers = ts.map(getNamedAbstractClassMembers(classDeclaration), member => member.name.getText());
const trackingAddedMembers: string[] = [];
const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
const textChanges = getCodeFixChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
if (textChanges.length > 0) {
// const abstractClassMembers = ts.map(getNamedAbstractClassMembers(classDeclaration), member => member.name.getText());
// const trackingAddedMembers: string[] = [];
// const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
// const textChanges = getCodeFixChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
const insertion = getMissingAbstractMemberInsertion(classDeclaration, checker, context.newLineCharacter);
if (insertion.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
textChanges: [{
span: {start: startPos, length: 0},
newText: insertion
}]
}]
}];
}

View File

@@ -19,6 +19,7 @@ namespace ts.codefix {
for (let i = 0; interfaceClauses && i < interfaceClauses.length; i++) {
const newChanges = getCodeFixChanges(interfaceClauses[i], classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
// getMissingAbstractMemberChanges(classDeclaration, checker, context.newLineCharacter);
textChanges = textChanges ? textChanges.concat(newChanges) : newChanges;
}

View File

@@ -1359,60 +1359,139 @@ namespace ts {
};
}
/*
const classMembers: TypeElement[]; // TODO: this
const implementedInterfaceMembers: TypeElement[] = [];
const parentAbstractMembers: TypeElement[] = []
const missingMembers: TypeElement[] = [];
// const typeWiththis = checker.getTypeWithThisArgument(classType);
// const staticType = <ObjectType>checker.getTypeOfSymbol(symbol);
// const classType = <InterfaceType>checker.getTypeAtLocation(classDecl);
*/
/*
const classMembers: TypeElement[]; // TODO: this
const implementedInterfaceMembers: TypeElement[] = [];
const parentAbstractMembers: TypeElement[] = []
const missingMembers: TypeElement[] = [];
// const typeWiththis = checker.getTypeWithThisArgument(classType);
// const staticType = <ObjectType>checker.getTypeOfSymbol(symbol);
// const classType = <InterfaceType>checker.getTypeAtLocation(classDecl);
*/
export function getUnimplementedMemberChanges(classDecl: ClassDeclaration, checker: TypeChecker): TextChange[] {
// TODO: (arozga) Get changes for interface as well.
// const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
export function getMissingAbstractMemberInsertion(classDecl: ClassDeclaration, checker: TypeChecker, newlineChar: string): string {
const baseTypeNode: ExpressionWithTypeArguments = getClassExtendsHeritageClauseElement(classDecl);
if (!baseTypeNode)
{
return [];
if (!baseTypeNode) {
return ""; // TODO: (arozga) undefined?
}
const classSymbol = checker.getSymbolAtLocation(classDecl);
const classType = <InterfaceType>checker.getDeclaredTypeOfSymbol(classSymbol);
const baseTypes = checker.getBaseTypes(classType);
Debug.assert(baseTypes.length === 1);
const baseType = baseTypes[0];
const classSymbol = checker.getSymbolOfNode(classDecl);
// TODO: (arozga) Should this be getTypeOfSymbol?
// We want the once that gets the members. I think that's the instance, so we want typeofsymbol.
const classType = <InterfaceType>checker.getTypeOfSymbol(classSymbol);
const resolvedClassType = checker.resolveStructuredTypeMembers(classType);
const resolvedBaseType = checker.resolveStructuredTypeMembers(baseType);
const baseTypes = checker.getBaseTypes(classType);
Debug.assert(baseTypes.length === 1);
const baseType = baseTypes[0];
const missingMembers = filterMissingMembers(resolvedClassType.members, resolvedBaseType.members);
return insertionsForMembers(missingMembers);
}
// TODO: (arozga) Does this give us the correct instantiations for generics?
// TODO: (arozga) If not, how do we get them?
const resolvedClassType = checker.resolveStructuredTypeMembers(classType);
const resolvedBaseType = checker.resolveStructuredTypeMembers(baseType);
// TODO: (arozga) Get changes for interface as well.
// const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
// TODO: (arozga) handle private members as well.
const missingMembers = filterMissingMembers(filterAbstract(resolvedBaseType.members), resolvedClassType.members);
return insertionsForMembers(missingMembers, newlineChar);
}
function insertionsForMembers(symbols: Symbol[]): TextChange[] {
let changes: TextChange[] = [];
for (const symbol of symbols) {
const decl = getdeclaration
switch (member.kind) {
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyDeclaration:
break;
case SyntaxKind.MethodSignature:
case SyntaxKind.MethodDeclaration:
break;
default:
break;
function filterSymbolMapByDecl(symbolMap: Map<Symbol>, pred: (decl: Declaration) => boolean): Map<Symbol> {
let result = createMap<Symbol>();
for (const key in symbolMap) {
const decl = symbolMap[key].getDeclarations();
Debug.assert(!!(decl && decl.length));
if (pred(decl[0])) {
result[key] = symbolMap[key];
}
}
return changes;
return result;
}
// TODO: (arozga) simplify to quadratic time solution.
function filterAbstract(symbolMap: Map<Symbol>) {
return filterSymbolMapByDecl(symbolMap, decl => !!(getModifierFlags(decl) & ModifierFlags.Abstract));
}
function filterNonPrivate(symbolMap: Map<Symbol>) {
return filterSymbolMapByDecl(symbolMap, decl => !(getModifierFlags(decl) & ModifierFlags.Private));
}
function insertionsForMembers(symbols: Symbol[], newlineChar: string): string {
let insertion = "";
for (const symbol of symbols) {
const decls = symbol.getDeclarations();
if(!(decls && decls.length)) {
return "";
}
insertion = insertion.concat(getInsertion(decls[0], newlineChar));
}
return insertion;
}
const stubMethodBody = "{\nthrow new Error('Method not Implemented');\n}";
const functionPrefix = "function ";
// TODO: (arozga) needs a re-write that takes the symbol and type (with type parameter instantiations)
// and figures out how to print it.
function getInsertion(decl: Declaration, newlineChar: string): string {
let insertion = "";
switch (decl.kind) {
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyDeclaration:
insertion = decl.getText();
// TODO: (arozga) need to remove trailing comma
if (insertion.length && insertion.charAt(insertion.length - 1) !== ";") {
insertion += ";";
}
return insertion;
case SyntaxKind.MethodSignature:
case SyntaxKind.MethodDeclaration:
const method = decl as MethodSignature | MethodDeclaration;
// TODO: (arozga) figure out how to do proper instantiation of generic values based on heritage clause.
const typeParameters = method.typeParameters && method.typeParameters.length > 0 ?
getCommaSeparatedString(method.typeParameters.map(param => param.getText()), "<", ">") : "";
const parameters = getCommaSeparatedString(method.parameters.map(param => param.getText()), "(", ")");
insertion += functionPrefix + method.name + typeParameters + parameters + stubMethodBody;
break;
default:
break;
}
return insertion += newlineChar;
/**
* Flattens params into a comma-separated list, sandwiched by prefix
* and suffix on either end.
*/
function getCommaSeparatedString(params: string[], prefix: string, suffix: string) {
let result = prefix;
for(let i = 0; params && i < params.length; ++i) {
result += (i > 0 ? "," : "") + params[i];
}
return result + suffix;
}
}
/**
* Finds the symbols in source but not target.
*/
function filterMissingMembers(sourceSymbols: Map<Symbol>, targetSymbols: Map<Symbol>): Symbol[] {
let result: Symbol[] = [];
outer:
for(const sourceName in sourceSymbols) {
for(const targetName in targetSymbols) {
if(sourceName === targetName) {
continue outer;
}
}
result.push(sourceSymbols[sourceName]);
}
return result;
}
/*
function filterMissingMembers(sourceSymbols: Map<Symbol>, targetSymbols: Map<Symbol>): Symbol[] {
let missingMembers: Symbol[] = [];
const sortedSourceKeys = Object.keys(sourceSymbols).sort();
@@ -1439,6 +1518,7 @@ namespace ts {
return missingMembers;
}
*/
/**
* Generates codefix changes to insert
@@ -1456,19 +1536,22 @@ namespace ts {
for (const member of missingMembers) {
if (member.kind === SyntaxKind.PropertySignature || member.kind === SyntaxKind.PropertyDeclaration) {
const interfaceProperty = <PropertySignature>member;
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) === -1) {
let propertyText = "";
if (reference) {
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
}
else {
propertyText = interfaceProperty.getText();
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
propertyText += stringToAdd;
}
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceProperty.name.getText());
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) !== -1) {
continue;
}
let propertyText = "";
if (reference) {
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
}
else {
propertyText = interfaceProperty.getText();
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
propertyText += stringToAdd;
}
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceProperty.name.getText());
}
else if (member.kind === SyntaxKind.MethodSignature || member.kind === SyntaxKind.MethodDeclaration) {
const interfaceMethod = <MethodSignature>member;