Skip to content

Commit 1b8486d

Browse files
committed
Still re-writing missing member grabber
1 parent 36c5bef commit 1b8486d

File tree

5 files changed

+164
-69
lines changed

5 files changed

+164
-69
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ namespace ts {
7474
getGlobalDiagnostics,
7575
getTypeOfSymbolAtLocation,
7676
getSymbolsOfParameterPropertyDeclaration,
77+
getTypeOfSymbol,
7778
getDeclaredTypeOfSymbol,
7879
getPropertiesOfType,
7980
getPropertyOfType,
@@ -84,6 +85,7 @@ namespace ts {
8485
resolveStructuredTypeMembers,
8586
getNonNullableType,
8687
getSymbolsInScope,
88+
getSymbolOfNode,
8789
getSymbolAtLocation,
8890
getShorthandAssignmentValueSymbol,
8991
getExportSpecifierLocalTargetSymbol,
@@ -4352,6 +4354,9 @@ namespace ts {
43524354
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
43534355
}
43544356

4357+
/**
4358+
* Converts an AnonymousType to a ResolvedType.
4359+
*/
43554360
function resolveAnonymousTypeMembers(type: AnonymousType) {
43564361
const symbol = type.symbol;
43574362
if (type.target) {

src/compiler/types.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2245,6 +2245,7 @@ namespace ts {
22452245

22462246
export interface TypeChecker {
22472247
getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type;
2248+
getTypeOfSymbol(symbol: Symbol): Type;
22482249
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
22492250
getPropertiesOfType(type: Type): Symbol[];
22502251
getPropertyOfType(type: Type, propertyName: string): Symbol;
@@ -2256,6 +2257,7 @@ namespace ts {
22562257
getNonNullableType(type: Type): Type;
22572258

22582259
getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
2260+
getSymbolOfNode(node: Node): Symbol;
22592261
getSymbolAtLocation(node: Node): Symbol;
22602262
getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[];
22612263
getShorthandAssignmentValueSymbol(location: Node): Symbol;
@@ -2760,7 +2762,7 @@ namespace ts {
27602762
objectFlags: ObjectFlags;
27612763
}
27622764

2763-
// Class and interface types (TypeFlags.Class and TypeFlags.Interface)
2765+
/** Class and interface types (TypeFlags.Class and TypeFlags.Interface). */
27642766
export interface InterfaceType extends ObjectType {
27652767
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
27662768
outerTypeParameters: TypeParameter[]; // Outer type parameters (undefined if none)
@@ -2780,14 +2782,16 @@ namespace ts {
27802782
declaredNumberIndexInfo: IndexInfo; // Declared numeric indexing info
27812783
}
27822784

2783-
// Type references (TypeFlags.Reference). When a class or interface has type parameters or
2784-
// a "this" type, references to the class or interface are made using type references. The
2785-
// typeArguments property specifies the types to substitute for the type parameters of the
2786-
// class or interface and optionally includes an extra element that specifies the type to
2787-
// substitute for "this" in the resulting instantiation. When no extra argument is present,
2788-
// the type reference itself is substituted for "this". The typeArguments property is undefined
2789-
// if the class or interface has no type parameters and the reference isn't specifying an
2790-
// explicit "this" argument.
2785+
/**
2786+
* Type references (TypeFlags.Reference). When a class or interface has type parameters or
2787+
* a "this" type, references to the class or interface are made using type references. The
2788+
* typeArguments property specifies the types to substitute for the type parameters of the
2789+
* class or interface and optionally includes an extra element that specifies the type to
2790+
* substitute for "this" in the resulting instantiation. When no extra argument is present,
2791+
* the type reference itself is substituted for "this". The typeArguments property is undefined
2792+
* if the class or interface has no type parameters and the reference isn't specifying an
2793+
* explicit "this" argument.
2794+
*/
27912795
export interface TypeReference extends ObjectType {
27922796
target: GenericType; // Type reference target
27932797
typeArguments: Type[]; // Type reference type arguments (undefined if none)
@@ -2825,7 +2829,6 @@ namespace ts {
28252829
finalArrayType?: Type; // Final array type of evolving array type
28262830
}
28272831

2828-
/* @internal */
28292832
// Resolved object, union, or intersection type
28302833
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {
28312834
members: SymbolTable; // Properties by name

src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@ namespace ts.codefix {
1111
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
1212
const classDeclaration = <ClassDeclaration>token.parent;
1313
const startPos = classDeclaration.members.pos;
14-
const abstractClassMembers = ts.map(getNamedAbstractClassMembers(classDeclaration), member => member.name.getText());
15-
const trackingAddedMembers: string[] = [];
16-
const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
17-
const textChanges = getCodeFixChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
18-
19-
if (textChanges.length > 0) {
14+
// const abstractClassMembers = ts.map(getNamedAbstractClassMembers(classDeclaration), member => member.name.getText());
15+
// const trackingAddedMembers: string[] = [];
16+
// const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
17+
// const textChanges = getCodeFixChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
18+
const insertion = getMissingAbstractMemberInsertion(classDeclaration, checker, context.newLineCharacter);
19+
if (insertion.length > 0) {
2020
return [{
2121
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
2222
changes: [{
2323
fileName: sourceFile.fileName,
24-
textChanges: textChanges
24+
textChanges: [{
25+
span: {start: startPos, length: 0},
26+
newText: insertion
27+
}]
2528
}]
2629
}];
2730
}

src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace ts.codefix {
1919

2020
for (let i = 0; interfaceClauses && i < interfaceClauses.length; i++) {
2121
const newChanges = getCodeFixChanges(interfaceClauses[i], classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
22+
// getMissingAbstractMemberChanges(classDeclaration, checker, context.newLineCharacter);
2223
textChanges = textChanges ? textChanges.concat(newChanges) : newChanges;
2324
}
2425

src/services/utilities.ts

Lines changed: 135 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,60 +1359,139 @@ namespace ts {
13591359
};
13601360
}
13611361

1362-
/*
1363-
const classMembers: TypeElement[]; // TODO: this
1364-
const implementedInterfaceMembers: TypeElement[] = [];
1365-
const parentAbstractMembers: TypeElement[] = []
1366-
const missingMembers: TypeElement[] = [];
1367-
// const typeWiththis = checker.getTypeWithThisArgument(classType);
1368-
// const staticType = <ObjectType>checker.getTypeOfSymbol(symbol);
1369-
// const classType = <InterfaceType>checker.getTypeAtLocation(classDecl);
1370-
*/
1371-
1372-
export function getUnimplementedMemberChanges(classDecl: ClassDeclaration, checker: TypeChecker): TextChange[] {
1362+
/*
1363+
const classMembers: TypeElement[]; // TODO: this
1364+
const implementedInterfaceMembers: TypeElement[] = [];
1365+
const parentAbstractMembers: TypeElement[] = []
1366+
const missingMembers: TypeElement[] = [];
1367+
// const typeWiththis = checker.getTypeWithThisArgument(classType);
1368+
// const staticType = <ObjectType>checker.getTypeOfSymbol(symbol);
1369+
// const classType = <InterfaceType>checker.getTypeAtLocation(classDecl);
1370+
*/
1371+
1372+
// TODO: (arozga) Get changes for interface as well.
1373+
// const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
1374+
export function getMissingAbstractMemberInsertion(classDecl: ClassDeclaration, checker: TypeChecker, newlineChar: string): string {
13731375
const baseTypeNode: ExpressionWithTypeArguments = getClassExtendsHeritageClauseElement(classDecl);
13741376

1375-
if (!baseTypeNode)
1376-
{
1377-
return [];
1377+
if (!baseTypeNode) {
1378+
return ""; // TODO: (arozga) undefined?
13781379
}
1379-
const classSymbol = checker.getSymbolAtLocation(classDecl);
1380-
const classType = <InterfaceType>checker.getDeclaredTypeOfSymbol(classSymbol);
1381-
1382-
const baseTypes = checker.getBaseTypes(classType);
1383-
Debug.assert(baseTypes.length === 1);
1384-
const baseType = baseTypes[0];
1380+
const classSymbol = checker.getSymbolOfNode(classDecl);
1381+
// TODO: (arozga) Should this be getTypeOfSymbol?
1382+
// We want the once that gets the members. I think that's the instance, so we want typeofsymbol.
1383+
const classType = <InterfaceType>checker.getTypeOfSymbol(classSymbol);
1384+
1385+
const baseTypes = checker.getBaseTypes(classType);
1386+
Debug.assert(baseTypes.length === 1);
1387+
const baseType = baseTypes[0];
1388+
1389+
// TODO: (arozga) Does this give us the correct instantiations for generics?
1390+
// TODO: (arozga) If not, how do we get them?
1391+
const resolvedClassType = checker.resolveStructuredTypeMembers(classType);
1392+
const resolvedBaseType = checker.resolveStructuredTypeMembers(baseType);
13851393

1386-
const resolvedClassType = checker.resolveStructuredTypeMembers(classType);
1387-
const resolvedBaseType = checker.resolveStructuredTypeMembers(baseType);
1394+
// TODO: (arozga) handle private members as well.
1395+
const missingMembers = filterMissingMembers(filterAbstract(resolvedBaseType.members), resolvedClassType.members);
13881396

1389-
const missingMembers = filterMissingMembers(resolvedClassType.members, resolvedBaseType.members);
1390-
return insertionsForMembers(missingMembers);
1397+
return insertionsForMembers(missingMembers, newlineChar);
1398+
}
1399+
1400+
function filterSymbolMapByDecl(symbolMap: Map<Symbol>, pred: (decl: Declaration) => boolean): Map<Symbol> {
1401+
let result = createMap<Symbol>();
1402+
for (const key in symbolMap) {
1403+
const decl = symbolMap[key].getDeclarations();
1404+
Debug.assert(!!(decl && decl.length));
1405+
if (pred(decl[0])) {
1406+
result[key] = symbolMap[key];
1407+
}
13911408
}
1409+
return result;
1410+
}
1411+
1412+
function filterAbstract(symbolMap: Map<Symbol>) {
1413+
return filterSymbolMapByDecl(symbolMap, decl => !!(getModifierFlags(decl) & ModifierFlags.Abstract));
1414+
}
13921415

1393-
// TODO: (arozga) Get changes for interface as well.
1394-
// const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl);
1416+
function filterNonPrivate(symbolMap: Map<Symbol>) {
1417+
return filterSymbolMapByDecl(symbolMap, decl => !(getModifierFlags(decl) & ModifierFlags.Private));
13951418
}
13961419

1397-
function insertionsForMembers(symbols: Symbol[]): TextChange[] {
1398-
let changes: TextChange[] = [];
1420+
function insertionsForMembers(symbols: Symbol[], newlineChar: string): string {
1421+
let insertion = "";
13991422
for (const symbol of symbols) {
1400-
const decl = getdeclaration
1401-
switch (member.kind) {
1402-
case SyntaxKind.PropertySignature:
1403-
case SyntaxKind.PropertyDeclaration:
1404-
break;
1405-
case SyntaxKind.MethodSignature:
1406-
case SyntaxKind.MethodDeclaration:
1407-
break;
1408-
default:
1409-
break;
1423+
const decls = symbol.getDeclarations();
1424+
if(!(decls && decls.length)) {
1425+
return "";
1426+
}
1427+
insertion = insertion.concat(getInsertion(decls[0], newlineChar));
1428+
}
1429+
return insertion;
1430+
}
1431+
1432+
const stubMethodBody = "{\nthrow new Error('Method not Implemented');\n}";
1433+
const functionPrefix = "function ";
1434+
1435+
// TODO: (arozga) needs a re-write that takes the symbol and type (with type parameter instantiations)
1436+
// and figures out how to print it.
1437+
function getInsertion(decl: Declaration, newlineChar: string): string {
1438+
let insertion = "";
1439+
switch (decl.kind) {
1440+
case SyntaxKind.PropertySignature:
1441+
case SyntaxKind.PropertyDeclaration:
1442+
insertion = decl.getText();
1443+
1444+
// TODO: (arozga) need to remove trailing comma
1445+
if (insertion.length && insertion.charAt(insertion.length - 1) !== ";") {
1446+
insertion += ";";
1447+
}
1448+
return insertion;
1449+
case SyntaxKind.MethodSignature:
1450+
case SyntaxKind.MethodDeclaration:
1451+
const method = decl as MethodSignature | MethodDeclaration;
1452+
// TODO: (arozga) figure out how to do proper instantiation of generic values based on heritage clause.
1453+
const typeParameters = method.typeParameters && method.typeParameters.length > 0 ?
1454+
getCommaSeparatedString(method.typeParameters.map(param => param.getText()), "<", ">") : "";
1455+
const parameters = getCommaSeparatedString(method.parameters.map(param => param.getText()), "(", ")");
1456+
insertion += functionPrefix + method.name + typeParameters + parameters + stubMethodBody;
1457+
break;
1458+
default:
1459+
break;
1460+
}
1461+
1462+
return insertion += newlineChar;
1463+
1464+
/**
1465+
* Flattens params into a comma-separated list, sandwiched by prefix
1466+
* and suffix on either end.
1467+
*/
1468+
function getCommaSeparatedString(params: string[], prefix: string, suffix: string) {
1469+
let result = prefix;
1470+
for(let i = 0; params && i < params.length; ++i) {
1471+
result += (i > 0 ? "," : "") + params[i];
1472+
}
1473+
return result + suffix;
1474+
}
1475+
}
1476+
1477+
/**
1478+
* Finds the symbols in source but not target.
1479+
*/
1480+
function filterMissingMembers(sourceSymbols: Map<Symbol>, targetSymbols: Map<Symbol>): Symbol[] {
1481+
let result: Symbol[] = [];
1482+
outer:
1483+
for(const sourceName in sourceSymbols) {
1484+
for(const targetName in targetSymbols) {
1485+
if(sourceName === targetName) {
1486+
continue outer;
1487+
}
14101488
}
1489+
result.push(sourceSymbols[sourceName]);
14111490
}
1412-
return changes;
1491+
return result;
14131492
}
14141493

1415-
// TODO: (arozga) simplify to quadratic time solution.
1494+
/*
14161495
function filterMissingMembers(sourceSymbols: Map<Symbol>, targetSymbols: Map<Symbol>): Symbol[] {
14171496
let missingMembers: Symbol[] = [];
14181497
const sortedSourceKeys = Object.keys(sourceSymbols).sort();
@@ -1439,6 +1518,7 @@ namespace ts {
14391518
14401519
return missingMembers;
14411520
}
1521+
*/
14421522

14431523
/**
14441524
* Generates codefix changes to insert
@@ -1456,19 +1536,22 @@ namespace ts {
14561536
for (const member of missingMembers) {
14571537
if (member.kind === SyntaxKind.PropertySignature || member.kind === SyntaxKind.PropertyDeclaration) {
14581538
const interfaceProperty = <PropertySignature>member;
1459-
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) === -1) {
1460-
let propertyText = "";
1461-
if (reference) {
1462-
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
1463-
}
1464-
else {
1465-
propertyText = interfaceProperty.getText();
1466-
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
1467-
propertyText += stringToAdd;
1468-
}
1469-
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
1470-
trackingAddedMembers.push(interfaceProperty.name.getText());
1539+
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) !== -1) {
1540+
continue;
1541+
}
1542+
1543+
let propertyText = "";
1544+
if (reference) {
1545+
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
14711546
}
1547+
else {
1548+
propertyText = interfaceProperty.getText();
1549+
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
1550+
propertyText += stringToAdd;
1551+
}
1552+
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
1553+
trackingAddedMembers.push(interfaceProperty.name.getText());
1554+
14721555
}
14731556
else if (member.kind === SyntaxKind.MethodSignature || member.kind === SyntaxKind.MethodDeclaration) {
14741557
const interfaceMethod = <MethodSignature>member;

0 commit comments

Comments
 (0)