Skip to content

Commit f883bf3

Browse files
Adding support for @implements. (microsoft#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
1 parent e3ec3d1 commit f883bf3

40 files changed

+981
-66
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5889,9 +5889,13 @@ namespace ts {
58895889
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
58905890
const classType = getDeclaredTypeOfClassOrInterface(symbol);
58915891
const baseTypes = getBaseTypes(classType);
5892+
const implementsTypes = getImplementsTypes(classType);
58925893
const staticType = getTypeOfSymbol(symbol);
58935894
const staticBaseType = getBaseConstructorTypeOfClass(staticType as InterfaceType);
5894-
const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))];
5895+
const heritageClauses = [
5896+
...!length(baseTypes) ? [] : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
5897+
...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeBaseType(b, staticBaseType, localName)))]
5898+
];
58955899
const symbolProps = getPropertiesOfType(classType);
58965900
const publicSymbolProps = filter(symbolProps, s => {
58975901
const valueDecl = s.valueDeclaration;
@@ -8251,6 +8255,26 @@ namespace ts {
82518255
return type.resolvedBaseConstructorType;
82528256
}
82538257

8258+
function getImplementsTypes(type: InterfaceType): BaseType[] {
8259+
let resolvedImplementsTypes: BaseType[] = emptyArray;
8260+
for (const declaration of type.symbol.declarations) {
8261+
const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration);
8262+
if (!implementsTypeNodes) continue;
8263+
for (const node of implementsTypeNodes) {
8264+
const implementsType = getTypeFromTypeNode(node);
8265+
if (implementsType !== errorType) {
8266+
if (resolvedImplementsTypes === emptyArray) {
8267+
resolvedImplementsTypes = [<ObjectType>implementsType];
8268+
}
8269+
else {
8270+
resolvedImplementsTypes.push(implementsType);
8271+
}
8272+
}
8273+
}
8274+
}
8275+
return resolvedImplementsTypes;
8276+
}
8277+
82548278
function getBaseTypes(type: InterfaceType): BaseType[] {
82558279
if (!type.resolvedBaseTypes) {
82568280
if (type.objectFlags & ObjectFlags.Tuple) {
@@ -30339,6 +30363,13 @@ namespace ts {
3033930363
checkSignatureDeclaration(node);
3034030364
}
3034130365

30366+
function checkJSDocImplementsTag(node: JSDocImplementsTag): void {
30367+
const classLike = getJSDocHost(node);
30368+
if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) {
30369+
error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName));
30370+
return;
30371+
}
30372+
}
3034230373
function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void {
3034330374
const classLike = getJSDocHost(node);
3034430375
if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) {
@@ -32609,7 +32640,7 @@ namespace ts {
3260932640
}
3261032641
}
3261132642

32612-
const implementedTypeNodes = getClassImplementsHeritageClauseElements(node);
32643+
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
3261332644
if (implementedTypeNodes) {
3261432645
for (const typeRefNode of implementedTypeNodes) {
3261532646
if (!isEntityNameExpression(typeRefNode.expression)) {
@@ -33829,6 +33860,8 @@ namespace ts {
3382933860
return checkImportType(<ImportTypeNode>node);
3383033861
case SyntaxKind.JSDocAugmentsTag:
3383133862
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
33863+
case SyntaxKind.JSDocImplementsTag:
33864+
return checkJSDocImplementsTag(node as JSDocImplementsTag);
3383233865
case SyntaxKind.JSDocTypedefTag:
3383333866
case SyntaxKind.JSDocCallbackTag:
3383433867
case SyntaxKind.JSDocEnumTag:

src/compiler/emitter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,8 +1519,9 @@ namespace ts {
15191519
case SyntaxKind.JSDocThisTag:
15201520
case SyntaxKind.JSDocEnumTag:
15211521
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
1522+
case SyntaxKind.JSDocImplementsTag:
15221523
case SyntaxKind.JSDocAugmentsTag:
1523-
return emitJSDocAugmentsTag(node as JSDocAugmentsTag);
1524+
return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag);
15241525
case SyntaxKind.JSDocTemplateTag:
15251526
return emitJSDocTemplateTag(node as JSDocTemplateTag);
15261527
case SyntaxKind.JSDocTypedefTag:
@@ -3468,7 +3469,7 @@ namespace ts {
34683469
emitJSDocComment(tag.comment);
34693470
}
34703471

3471-
function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) {
3472+
function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) {
34723473
emitJSDocTagName(tag.tagName);
34733474
writeSpace();
34743475
writePunctuation("{");

src/compiler/parser.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,9 @@ namespace ts {
479479
visitNode(cbNode, (<JSDocPropertyLikeTag>node).name));
480480
case SyntaxKind.JSDocAuthorTag:
481481
return visitNode(cbNode, (node as JSDocTag).tagName);
482+
case SyntaxKind.JSDocImplementsTag:
483+
return visitNode(cbNode, (node as JSDocTag).tagName) ||
484+
visitNode(cbNode, (<JSDocImplementsTag>node).class);
482485
case SyntaxKind.JSDocAugmentsTag:
483486
return visitNode(cbNode, (node as JSDocTag).tagName) ||
484487
visitNode(cbNode, (<JSDocAugmentsTag>node).class);
@@ -6999,6 +7002,9 @@ namespace ts {
69997002
case "author":
70007003
tag = parseAuthorTag(start, tagName, margin);
70017004
break;
7005+
case "implements":
7006+
tag = parseImplementsTag(start, tagName);
7007+
break;
70027008
case "augments":
70037009
case "extends":
70047010
tag = parseAugmentsTag(start, tagName);
@@ -7355,6 +7361,13 @@ namespace ts {
73557361
}
73567362
}
73577363

7364+
function parseImplementsTag(start: number, tagName: Identifier): JSDocImplementsTag {
7365+
const result = <JSDocImplementsTag>createNode(SyntaxKind.JSDocImplementsTag, start);
7366+
result.tagName = tagName;
7367+
result.class = parseExpressionWithTypeArgumentsForAugments();
7368+
return finishNode(result);
7369+
}
7370+
73587371
function parseAugmentsTag(start: number, tagName: Identifier): JSDocAugmentsTag {
73597372
const result = <JSDocAugmentsTag>createNode(SyntaxKind.JSDocAugmentsTag, start);
73607373
result.tagName = tagName;

src/compiler/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ namespace ts {
471471
JSDocSignature,
472472
JSDocTag,
473473
JSDocAugmentsTag,
474+
JSDocImplementsTag,
474475
JSDocAuthorTag,
475476
JSDocClassTag,
476477
JSDocPublicTag,
@@ -1986,7 +1987,7 @@ namespace ts {
19861987

19871988
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
19881989
kind: SyntaxKind.ExpressionWithTypeArguments;
1989-
parent: HeritageClause | JSDocAugmentsTag;
1990+
parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag;
19901991
expression: LeftHandSideExpression;
19911992
}
19921993

@@ -2660,6 +2661,11 @@ namespace ts {
26602661
class: ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
26612662
}
26622663

2664+
export interface JSDocImplementsTag extends JSDocTag {
2665+
kind: SyntaxKind.JSDocImplementsTag;
2666+
class: ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
2667+
}
2668+
26632669
export interface JSDocAuthorTag extends JSDocTag {
26642670
kind: SyntaxKind.JSDocAuthorTag;
26652671
}

src/compiler/utilities.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2807,15 +2807,20 @@ namespace ts {
28072807
return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined;
28082808
}
28092809

2810-
export function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration) {
2811-
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
2812-
return heritageClause ? heritageClause.types : undefined;
2810+
export function getEffectiveImplementsTypeNodes(node: ClassLikeDeclaration): undefined | readonly ExpressionWithTypeArguments[]{
2811+
if(isInJSFile(node)) {
2812+
return getJSDocImplementsTags(node).map(n => n.class);
2813+
}
2814+
else {
2815+
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
2816+
return heritageClause?.types;
2817+
}
28132818
}
28142819

28152820
/** Returns the node in an `extends` or `implements` clause of a class or interface. */
28162821
export function getAllSuperTypeNodes(node: Node): readonly TypeNode[] {
28172822
return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray :
2818-
isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray :
2823+
isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getEffectiveImplementsTypeNodes(node)) || emptyArray :
28192824
emptyArray;
28202825
}
28212826

src/compiler/utilitiesPublic.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,11 @@ namespace ts {
671671
return getFirstJSDocTag(node, isJSDocAugmentsTag);
672672
}
673673

674+
/** Gets the JSDoc implements tags for the node if present */
675+
export function getJSDocImplementsTags(node: Node): readonly JSDocImplementsTag[] {
676+
return getAllJSDocTags(node, isJSDocImplementsTag);
677+
}
678+
674679
/** Gets the JSDoc class tag for the node if present */
675680
export function getJSDocClassTag(node: Node): JSDocClassTag | undefined {
676681
return getFirstJSDocTag(node, isJSDocClassTag);
@@ -787,7 +792,12 @@ namespace ts {
787792
return find(getJSDocTags(node), predicate);
788793
}
789794

790-
/** Gets all JSDoc tags of a specified kind, or undefined if not present. */
795+
/** Gets all JSDoc tags that match a specified predicate */
796+
export function getAllJSDocTags<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T): readonly T[] {
797+
return getJSDocTags(node).filter(predicate);
798+
}
799+
800+
/** Gets all JSDoc tags of a specified kind */
791801
export function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): readonly JSDocTag[] {
792802
return getJSDocTags(node).filter(doc => doc.kind === kind);
793803
}
@@ -1582,6 +1592,10 @@ namespace ts {
15821592
return node.kind === SyntaxKind.JSDocAugmentsTag;
15831593
}
15841594

1595+
export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
1596+
return node.kind === SyntaxKind.JSDocImplementsTag;
1597+
}
1598+
15851599
export function isJSDocClassTag(node: Node): node is JSDocClassTag {
15861600
return node.kind === SyntaxKind.JSDocClassTag;
15871601
}

src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts.codefix {
1010
getCodeActions(context) {
1111
const { sourceFile, span } = context;
1212
const classDeclaration = getClass(sourceFile, span.start);
13-
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
13+
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getEffectiveImplementsTypeNodes(classDeclaration), implementedTypeNode => {
1414
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(context, implementedTypeNode, sourceFile, classDeclaration, t, context.preferences));
1515
return changes.length === 0 ? undefined : createCodeFixAction(fixId, changes, [Diagnostics.Implement_interface_0, implementedTypeNode.getText(sourceFile)], fixId, Diagnostics.Implement_all_unimplemented_interfaces);
1616
});
@@ -21,7 +21,7 @@ namespace ts.codefix {
2121
return codeFixAll(context, errorCodes, (changes, diag) => {
2222
const classDeclaration = getClass(diag.file, diag.start);
2323
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
24-
for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)!) {
24+
for (const implementedTypeNode of getEffectiveImplementsTypeNodes(classDeclaration)!) {
2525
addMissingDeclarations(context, implementedTypeNode, diag.file, classDeclaration, changes, context.preferences);
2626
}
2727
}

src/services/jsDoc.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ namespace ts.JsDoc {
129129
function getCommentText(tag: JSDocTag): string | undefined {
130130
const { comment } = tag;
131131
switch (tag.kind) {
132+
case SyntaxKind.JSDocImplementsTag:
133+
return withNode((tag as JSDocImplementsTag).class);
132134
case SyntaxKind.JSDocAugmentsTag:
133135
return withNode((tag as JSDocAugmentsTag).class);
134136
case SyntaxKind.JSDocTemplateTag:

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -383,29 +383,30 @@ declare namespace ts {
383383
JSDocSignature = 305,
384384
JSDocTag = 306,
385385
JSDocAugmentsTag = 307,
386-
JSDocAuthorTag = 308,
387-
JSDocClassTag = 309,
388-
JSDocPublicTag = 310,
389-
JSDocPrivateTag = 311,
390-
JSDocProtectedTag = 312,
391-
JSDocReadonlyTag = 313,
392-
JSDocCallbackTag = 314,
393-
JSDocEnumTag = 315,
394-
JSDocParameterTag = 316,
395-
JSDocReturnTag = 317,
396-
JSDocThisTag = 318,
397-
JSDocTypeTag = 319,
398-
JSDocTemplateTag = 320,
399-
JSDocTypedefTag = 321,
400-
JSDocPropertyTag = 322,
401-
SyntaxList = 323,
402-
NotEmittedStatement = 324,
403-
PartiallyEmittedExpression = 325,
404-
CommaListExpression = 326,
405-
MergeDeclarationMarker = 327,
406-
EndOfDeclarationMarker = 328,
407-
SyntheticReferenceExpression = 329,
408-
Count = 330,
386+
JSDocImplementsTag = 308,
387+
JSDocAuthorTag = 309,
388+
JSDocClassTag = 310,
389+
JSDocPublicTag = 311,
390+
JSDocPrivateTag = 312,
391+
JSDocProtectedTag = 313,
392+
JSDocReadonlyTag = 314,
393+
JSDocCallbackTag = 315,
394+
JSDocEnumTag = 316,
395+
JSDocParameterTag = 317,
396+
JSDocReturnTag = 318,
397+
JSDocThisTag = 319,
398+
JSDocTypeTag = 320,
399+
JSDocTemplateTag = 321,
400+
JSDocTypedefTag = 322,
401+
JSDocPropertyTag = 323,
402+
SyntaxList = 324,
403+
NotEmittedStatement = 325,
404+
PartiallyEmittedExpression = 326,
405+
CommaListExpression = 327,
406+
MergeDeclarationMarker = 328,
407+
EndOfDeclarationMarker = 329,
408+
SyntheticReferenceExpression = 330,
409+
Count = 331,
409410
FirstAssignment = 62,
410411
LastAssignment = 74,
411412
FirstCompoundAssignment = 63,
@@ -434,9 +435,9 @@ declare namespace ts {
434435
LastStatement = 241,
435436
FirstNode = 153,
436437
FirstJSDocNode = 294,
437-
LastJSDocNode = 322,
438+
LastJSDocNode = 323,
438439
FirstJSDocTagNode = 306,
439-
LastJSDocTagNode = 322,
440+
LastJSDocTagNode = 323,
440441
}
441442
export enum NodeFlags {
442443
None = 0,
@@ -1145,7 +1146,7 @@ declare namespace ts {
11451146
}
11461147
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
11471148
kind: SyntaxKind.ExpressionWithTypeArguments;
1148-
parent: HeritageClause | JSDocAugmentsTag;
1149+
parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag;
11491150
expression: LeftHandSideExpression;
11501151
}
11511152
export interface NewExpression extends PrimaryExpression, Declaration {
@@ -1635,6 +1636,12 @@ declare namespace ts {
16351636
expression: Identifier | PropertyAccessEntityNameExpression;
16361637
};
16371638
}
1639+
export interface JSDocImplementsTag extends JSDocTag {
1640+
kind: SyntaxKind.JSDocImplementsTag;
1641+
class: ExpressionWithTypeArguments & {
1642+
expression: Identifier | PropertyAccessEntityNameExpression;
1643+
};
1644+
}
16381645
export interface JSDocAuthorTag extends JSDocTag {
16391646
kind: SyntaxKind.JSDocAuthorTag;
16401647
}
@@ -3516,6 +3523,8 @@ declare namespace ts {
35163523
function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean;
35173524
/** Gets the JSDoc augments tag for the node if present */
35183525
function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined;
3526+
/** Gets the JSDoc implements tags for the node if present */
3527+
function getJSDocImplementsTags(node: Node): readonly JSDocImplementsTag[];
35193528
/** Gets the JSDoc class tag for the node if present */
35203529
function getJSDocClassTag(node: Node): JSDocClassTag | undefined;
35213530
/** Gets the JSDoc public tag for the node if present */
@@ -3557,7 +3566,9 @@ declare namespace ts {
35573566
function getJSDocReturnType(node: Node): TypeNode | undefined;
35583567
/** Get all JSDoc tags related to a node, including those on parent nodes. */
35593568
function getJSDocTags(node: Node): readonly JSDocTag[];
3560-
/** Gets all JSDoc tags of a specified kind, or undefined if not present. */
3569+
/** Gets all JSDoc tags that match a specified predicate */
3570+
function getAllJSDocTags<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T): readonly T[];
3571+
/** Gets all JSDoc tags of a specified kind */
35613572
function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): readonly JSDocTag[];
35623573
/**
35633574
* Gets the effective type parameters. If the node was parsed in a
@@ -3733,6 +3744,7 @@ declare namespace ts {
37333744
function isJSDoc(node: Node): node is JSDoc;
37343745
function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag;
37353746
function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag;
3747+
function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag;
37363748
function isJSDocClassTag(node: Node): node is JSDocClassTag;
37373749
function isJSDocPublicTag(node: Node): node is JSDocPublicTag;
37383750
function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag;

0 commit comments

Comments
 (0)