Adding support for @implements. (#36292)

* Adding support for @implements.

* Fixed code review issues for @implements, added some more tests.

* Fixed declaration emit for @interface

* Improved getImplementsTypes to not cache the results since it is only used once.

* Removed unnecessary checks from getImplementsTypes
This commit is contained in:
Titian Cernicova-Dragomir
2020-02-27 09:27:37 -08:00
committed by GitHub
parent e3ec3d1942
commit f883bf3acb
40 changed files with 981 additions and 66 deletions
+35 -2
View File
@@ -5889,9 +5889,13 @@ namespace ts {
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
const classType = getDeclaredTypeOfClassOrInterface(symbol);
const baseTypes = getBaseTypes(classType);
const implementsTypes = getImplementsTypes(classType);
const staticType = getTypeOfSymbol(symbol);
const staticBaseType = getBaseConstructorTypeOfClass(staticType as InterfaceType);
const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))];
const heritageClauses = [
...!length(baseTypes) ? [] : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeBaseType(b, staticBaseType, localName)))]
];
const symbolProps = getPropertiesOfType(classType);
const publicSymbolProps = filter(symbolProps, s => {
const valueDecl = s.valueDeclaration;
@@ -8251,6 +8255,26 @@ namespace ts {
return type.resolvedBaseConstructorType;
}
function getImplementsTypes(type: InterfaceType): BaseType[] {
let resolvedImplementsTypes: BaseType[] = emptyArray;
for (const declaration of type.symbol.declarations) {
const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration);
if (!implementsTypeNodes) continue;
for (const node of implementsTypeNodes) {
const implementsType = getTypeFromTypeNode(node);
if (implementsType !== errorType) {
if (resolvedImplementsTypes === emptyArray) {
resolvedImplementsTypes = [<ObjectType>implementsType];
}
else {
resolvedImplementsTypes.push(implementsType);
}
}
}
}
return resolvedImplementsTypes;
}
function getBaseTypes(type: InterfaceType): BaseType[] {
if (!type.resolvedBaseTypes) {
if (type.objectFlags & ObjectFlags.Tuple) {
@@ -30339,6 +30363,13 @@ namespace ts {
checkSignatureDeclaration(node);
}
function checkJSDocImplementsTag(node: JSDocImplementsTag): void {
const classLike = getJSDocHost(node);
if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) {
error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName));
return;
}
}
function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void {
const classLike = getJSDocHost(node);
if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) {
@@ -32609,7 +32640,7 @@ namespace ts {
}
}
const implementedTypeNodes = getClassImplementsHeritageClauseElements(node);
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
if (implementedTypeNodes) {
for (const typeRefNode of implementedTypeNodes) {
if (!isEntityNameExpression(typeRefNode.expression)) {
@@ -33829,6 +33860,8 @@ namespace ts {
return checkImportType(<ImportTypeNode>node);
case SyntaxKind.JSDocAugmentsTag:
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
case SyntaxKind.JSDocImplementsTag:
return checkJSDocImplementsTag(node as JSDocImplementsTag);
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
+3 -2
View File
@@ -1519,8 +1519,9 @@ namespace ts {
case SyntaxKind.JSDocThisTag:
case SyntaxKind.JSDocEnumTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
case SyntaxKind.JSDocImplementsTag:
case SyntaxKind.JSDocAugmentsTag:
return emitJSDocAugmentsTag(node as JSDocAugmentsTag);
return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag);
case SyntaxKind.JSDocTemplateTag:
return emitJSDocTemplateTag(node as JSDocTemplateTag);
case SyntaxKind.JSDocTypedefTag:
@@ -3468,7 +3469,7 @@ namespace ts {
emitJSDocComment(tag.comment);
}
function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) {
function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) {
emitJSDocTagName(tag.tagName);
writeSpace();
writePunctuation("{");
+13
View File
@@ -479,6 +479,9 @@ namespace ts {
visitNode(cbNode, (<JSDocPropertyLikeTag>node).name));
case SyntaxKind.JSDocAuthorTag:
return visitNode(cbNode, (node as JSDocTag).tagName);
case SyntaxKind.JSDocImplementsTag:
return visitNode(cbNode, (node as JSDocTag).tagName) ||
visitNode(cbNode, (<JSDocImplementsTag>node).class);
case SyntaxKind.JSDocAugmentsTag:
return visitNode(cbNode, (node as JSDocTag).tagName) ||
visitNode(cbNode, (<JSDocAugmentsTag>node).class);
@@ -6999,6 +7002,9 @@ namespace ts {
case "author":
tag = parseAuthorTag(start, tagName, margin);
break;
case "implements":
tag = parseImplementsTag(start, tagName);
break;
case "augments":
case "extends":
tag = parseAugmentsTag(start, tagName);
@@ -7355,6 +7361,13 @@ namespace ts {
}
}
function parseImplementsTag(start: number, tagName: Identifier): JSDocImplementsTag {
const result = <JSDocImplementsTag>createNode(SyntaxKind.JSDocImplementsTag, start);
result.tagName = tagName;
result.class = parseExpressionWithTypeArgumentsForAugments();
return finishNode(result);
}
function parseAugmentsTag(start: number, tagName: Identifier): JSDocAugmentsTag {
const result = <JSDocAugmentsTag>createNode(SyntaxKind.JSDocAugmentsTag, start);
result.tagName = tagName;
+7 -1
View File
@@ -471,6 +471,7 @@ namespace ts {
JSDocSignature,
JSDocTag,
JSDocAugmentsTag,
JSDocImplementsTag,
JSDocAuthorTag,
JSDocClassTag,
JSDocPublicTag,
@@ -1986,7 +1987,7 @@ namespace ts {
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
kind: SyntaxKind.ExpressionWithTypeArguments;
parent: HeritageClause | JSDocAugmentsTag;
parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag;
expression: LeftHandSideExpression;
}
@@ -2660,6 +2661,11 @@ namespace ts {
class: ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
}
export interface JSDocImplementsTag extends JSDocTag {
kind: SyntaxKind.JSDocImplementsTag;
class: ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
}
export interface JSDocAuthorTag extends JSDocTag {
kind: SyntaxKind.JSDocAuthorTag;
}
+9 -4
View File
@@ -2807,15 +2807,20 @@ namespace ts {
return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined;
}
export function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration) {
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
return heritageClause ? heritageClause.types : undefined;
export function getEffectiveImplementsTypeNodes(node: ClassLikeDeclaration): undefined | readonly ExpressionWithTypeArguments[]{
if(isInJSFile(node)) {
return getJSDocImplementsTags(node).map(n => n.class);
}
else {
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
return heritageClause?.types;
}
}
/** Returns the node in an `extends` or `implements` clause of a class or interface. */
export function getAllSuperTypeNodes(node: Node): readonly TypeNode[] {
return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray :
isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray :
isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getEffectiveImplementsTypeNodes(node)) || emptyArray :
emptyArray;
}
+15 -1
View File
@@ -671,6 +671,11 @@ namespace ts {
return getFirstJSDocTag(node, isJSDocAugmentsTag);
}
/** Gets the JSDoc implements tags for the node if present */
export function getJSDocImplementsTags(node: Node): readonly JSDocImplementsTag[] {
return getAllJSDocTags(node, isJSDocImplementsTag);
}
/** Gets the JSDoc class tag for the node if present */
export function getJSDocClassTag(node: Node): JSDocClassTag | undefined {
return getFirstJSDocTag(node, isJSDocClassTag);
@@ -787,7 +792,12 @@ namespace ts {
return find(getJSDocTags(node), predicate);
}
/** Gets all JSDoc tags of a specified kind, or undefined if not present. */
/** Gets all JSDoc tags that match a specified predicate */
export function getAllJSDocTags<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T): readonly T[] {
return getJSDocTags(node).filter(predicate);
}
/** Gets all JSDoc tags of a specified kind */
export function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): readonly JSDocTag[] {
return getJSDocTags(node).filter(doc => doc.kind === kind);
}
@@ -1582,6 +1592,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocAugmentsTag;
}
export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
return node.kind === SyntaxKind.JSDocImplementsTag;
}
export function isJSDocClassTag(node: Node): node is JSDocClassTag {
return node.kind === SyntaxKind.JSDocClassTag;
}
@@ -10,7 +10,7 @@ namespace ts.codefix {
getCodeActions(context) {
const { sourceFile, span } = context;
const classDeclaration = getClass(sourceFile, span.start);
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getEffectiveImplementsTypeNodes(classDeclaration), implementedTypeNode => {
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(context, implementedTypeNode, sourceFile, classDeclaration, t, context.preferences));
return changes.length === 0 ? undefined : createCodeFixAction(fixId, changes, [Diagnostics.Implement_interface_0, implementedTypeNode.getText(sourceFile)], fixId, Diagnostics.Implement_all_unimplemented_interfaces);
});
@@ -21,7 +21,7 @@ namespace ts.codefix {
return codeFixAll(context, errorCodes, (changes, diag) => {
const classDeclaration = getClass(diag.file, diag.start);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)!) {
for (const implementedTypeNode of getEffectiveImplementsTypeNodes(classDeclaration)!) {
addMissingDeclarations(context, implementedTypeNode, diag.file, classDeclaration, changes, context.preferences);
}
}
+2
View File
@@ -129,6 +129,8 @@ namespace ts.JsDoc {
function getCommentText(tag: JSDocTag): string | undefined {
const { comment } = tag;
switch (tag.kind) {
case SyntaxKind.JSDocImplementsTag:
return withNode((tag as JSDocImplementsTag).class);
case SyntaxKind.JSDocAugmentsTag:
return withNode((tag as JSDocAugmentsTag).class);
case SyntaxKind.JSDocTemplateTag: