Skip to content

Adding support for @implements. #36292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5876,9 +5876,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;
Expand Down Expand Up @@ -8238,6 +8242,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) {
Expand Down Expand Up @@ -30237,6 +30261,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)) {
Expand Down Expand Up @@ -32507,7 +32538,7 @@ namespace ts {
}
}

const implementedTypeNodes = getClassImplementsHeritageClauseElements(node);
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
if (implementedTypeNodes) {
for (const typeRefNode of implementedTypeNodes) {
if (!isEntityNameExpression(typeRefNode.expression)) {
Expand Down Expand Up @@ -33723,6 +33754,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:
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -3468,7 +3469,7 @@ namespace ts {
emitJSDocComment(tag.comment);
}

function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) {
function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) {
emitJSDocTagName(tag.tagName);
writeSpace();
writePunctuation("{");
Expand Down
13 changes: 13 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -6946,6 +6949,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);
Expand Down Expand Up @@ -7302,6 +7308,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;
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ namespace ts {
JSDocSignature,
JSDocTag,
JSDocAugmentsTag,
JSDocImplementsTag,
JSDocAuthorTag,
JSDocClassTag,
JSDocPublicTag,
Expand Down Expand Up @@ -1986,7 +1987,7 @@ namespace ts {

export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
kind: SyntaxKind.ExpressionWithTypeArguments;
parent: HeritageClause | JSDocAugmentsTag;
parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag;
expression: LeftHandSideExpression;
}

Expand Down Expand Up @@ -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;
}
Expand Down
13 changes: 9 additions & 4 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2804,15 +2804,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;
}

Expand Down
16 changes: 15 additions & 1 deletion src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/services/jsDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
66 changes: 39 additions & 27 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,29 +383,30 @@ declare namespace ts {
JSDocSignature = 305,
JSDocTag = 306,
JSDocAugmentsTag = 307,
JSDocAuthorTag = 308,
JSDocClassTag = 309,
JSDocPublicTag = 310,
JSDocPrivateTag = 311,
JSDocProtectedTag = 312,
JSDocReadonlyTag = 313,
JSDocCallbackTag = 314,
JSDocEnumTag = 315,
JSDocParameterTag = 316,
JSDocReturnTag = 317,
JSDocThisTag = 318,
JSDocTypeTag = 319,
JSDocTemplateTag = 320,
JSDocTypedefTag = 321,
JSDocPropertyTag = 322,
SyntaxList = 323,
NotEmittedStatement = 324,
PartiallyEmittedExpression = 325,
CommaListExpression = 326,
MergeDeclarationMarker = 327,
EndOfDeclarationMarker = 328,
SyntheticReferenceExpression = 329,
Count = 330,
JSDocImplementsTag = 308,
JSDocAuthorTag = 309,
JSDocClassTag = 310,
JSDocPublicTag = 311,
JSDocPrivateTag = 312,
JSDocProtectedTag = 313,
JSDocReadonlyTag = 314,
JSDocCallbackTag = 315,
JSDocEnumTag = 316,
JSDocParameterTag = 317,
JSDocReturnTag = 318,
JSDocThisTag = 319,
JSDocTypeTag = 320,
JSDocTemplateTag = 321,
JSDocTypedefTag = 322,
JSDocPropertyTag = 323,
SyntaxList = 324,
NotEmittedStatement = 325,
PartiallyEmittedExpression = 326,
CommaListExpression = 327,
MergeDeclarationMarker = 328,
EndOfDeclarationMarker = 329,
SyntheticReferenceExpression = 330,
Count = 331,
FirstAssignment = 62,
LastAssignment = 74,
FirstCompoundAssignment = 63,
Expand Down Expand Up @@ -434,9 +435,9 @@ declare namespace ts {
LastStatement = 241,
FirstNode = 153,
FirstJSDocNode = 294,
LastJSDocNode = 322,
LastJSDocNode = 323,
FirstJSDocTagNode = 306,
LastJSDocTagNode = 322,
LastJSDocTagNode = 323,
}
export enum NodeFlags {
None = 0,
Expand Down Expand Up @@ -1145,7 +1146,7 @@ declare namespace ts {
}
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
kind: SyntaxKind.ExpressionWithTypeArguments;
parent: HeritageClause | JSDocAugmentsTag;
parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag;
expression: LeftHandSideExpression;
}
export interface NewExpression extends PrimaryExpression, Declaration {
Expand Down Expand Up @@ -1635,6 +1636,12 @@ declare namespace ts {
expression: Identifier | PropertyAccessEntityNameExpression;
};
}
export interface JSDocImplementsTag extends JSDocTag {
kind: SyntaxKind.JSDocImplementsTag;
class: ExpressionWithTypeArguments & {
expression: Identifier | PropertyAccessEntityNameExpression;
};
}
export interface JSDocAuthorTag extends JSDocTag {
kind: SyntaxKind.JSDocAuthorTag;
}
Expand Down Expand Up @@ -3510,6 +3517,8 @@ declare namespace ts {
function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean;
/** Gets the JSDoc augments tag for the node if present */
function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined;
/** Gets the JSDoc implements tags for the node if present */
function getJSDocImplementsTags(node: Node): readonly JSDocImplementsTag[];
/** Gets the JSDoc class tag for the node if present */
function getJSDocClassTag(node: Node): JSDocClassTag | undefined;
/** Gets the JSDoc public tag for the node if present */
Expand Down Expand Up @@ -3551,7 +3560,9 @@ declare namespace ts {
function getJSDocReturnType(node: Node): TypeNode | undefined;
/** Get all JSDoc tags related to a node, including those on parent nodes. */
function getJSDocTags(node: Node): readonly JSDocTag[];
/** Gets all JSDoc tags of a specified kind, or undefined if not present. */
/** Gets all JSDoc tags that match a specified predicate */
function getAllJSDocTags<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T): readonly T[];
/** Gets all JSDoc tags of a specified kind */
function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): readonly JSDocTag[];
/**
* Gets the effective type parameters. If the node was parsed in a
Expand Down Expand Up @@ -3727,6 +3738,7 @@ declare namespace ts {
function isJSDoc(node: Node): node is JSDoc;
function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag;
function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag;
function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag;
function isJSDocClassTag(node: Node): node is JSDocClassTag;
function isJSDocPublicTag(node: Node): node is JSDocPublicTag;
function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag;
Expand Down
Loading