Skip to content

Commit

Permalink
JSDoc overload tag (#51234)
Browse files Browse the repository at this point in the history
* Add support for JSDocOverloadTag

* Use overload tag to determine function type

* Update baselines

* Add new tests along with baselines

* Add tests for all @overload tags in one comment

* Add tests for find-all-ref and rename operations

* Add tests for alternative uses of @overload tag

Co-authored-by: Nathan Shively-Sanders <[email protected]>
  • Loading branch information
apendua and sandersn authored Dec 13, 2022
1 parent 4076ff8 commit e4816ed
Show file tree
Hide file tree
Showing 35 changed files with 1,979 additions and 46 deletions.
3 changes: 3 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ import {
JSDocClassTag,
JSDocEnumTag,
JSDocFunctionType,
JSDocOverloadTag,
JSDocParameterTag,
JSDocPropertyLikeTag,
JSDocSignature,
Expand Down Expand Up @@ -2966,6 +2967,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag);
case SyntaxKind.JSDocOverloadTag:
return bind((node as JSDocOverloadTag).typeExpression);
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ import {
isJSDocNullableType,
isJSDocOptionalParameter,
isJSDocOptionalType,
isJSDocOverloadTag,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocPropertyTag,
Expand Down Expand Up @@ -14270,6 +14271,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
continue;
}
}
if (isInJSFile(decl) && decl.jsDoc) {
let hasJSDocOverloads = false;
for (const node of decl.jsDoc) {
if (node.tags) {
for (const tag of node.tags) {
if (isJSDocOverloadTag(tag)) {
result.push(getSignatureFromDeclaration(tag.typeExpression));
hasJSDocOverloads = true;
}
}
}
}
if (hasJSDocOverloads) {
continue;
}
}
// If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters.
// Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible.
result.push(
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocPropertyLikeTag,
JSDocReturnTag,
JSDocSeeTag,
Expand Down Expand Up @@ -2117,6 +2118,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return;
case SyntaxKind.JSDocCallbackTag:
return emitJSDocCallbackTag(node as JSDocCallbackTag);
case SyntaxKind.JSDocOverloadTag:
return emitJSDocOverloadTag(node as JSDocOverloadTag);
// SyntaxKind.JSDocEnumTag (see below)
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
Expand Down Expand Up @@ -4375,6 +4378,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
emitJSDocSignature(tag.typeExpression);
}

function emitJSDocOverloadTag(tag: JSDocOverloadTag) {
emitJSDocComment(tag.comment);
emitJSDocSignature(tag.typeExpression);
}

function emitJSDocSimpleTag(tag: JSDocTag) {
emitJSDocTagName(tag.tagName);
emitJSDocComment(tag.comment);
Expand Down
20 changes: 20 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
Expand Down Expand Up @@ -830,6 +831,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateJSDocPropertyTag,
createJSDocCallbackTag,
updateJSDocCallbackTag,
createJSDocOverloadTag,
updateJSDocOverloadTag,
createJSDocAugmentsTag,
updateJSDocAugmentsTag,
createJSDocImplementsTag,
Expand Down Expand Up @@ -5305,6 +5308,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}

// @api
function createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag {
const node = createBaseJSDocTag<JSDocOverloadTag>(SyntaxKind.JSDocOverloadTag, tagName ?? createIdentifier("overload"), comment);
node.typeExpression = typeExpression;
return node;
}

// @api
function updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag {
return node.tagName !== tagName
|| node.typeExpression !== typeExpression
|| node.comment !== comment
? update(createJSDocOverloadTag(tagName, typeExpression, comment), node)
: node;
}

// @api
function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag {
const node = createBaseJSDocTag<JSDocAugmentsTag>(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment);
Expand Down Expand Up @@ -7193,6 +7212,7 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
case SyntaxKind.JSDocParameterTag: return "param";
case SyntaxKind.JSDocPropertyTag: return "prop";
case SyntaxKind.JSDocCallbackTag: return "callback";
case SyntaxKind.JSDocOverloadTag: return "overload";
case SyntaxKind.JSDocAugmentsTag: return "augments";
case SyntaxKind.JSDocImplementsTag: return "implements";
default:
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
Expand Down Expand Up @@ -1135,6 +1136,10 @@ export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
return node.kind === SyntaxKind.JSDocOverrideTag;
}

export function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag {
return node.kind === SyntaxKind.JSDocOverloadTag;
}

export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
return node.kind === SyntaxKind.JSDocDeprecatedTag;
}
Expand Down
29 changes: 24 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ import {
JSDocNonNullableType,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocOverrideTag,
JSDocParameterTag,
JSDocPrivateTag,
Expand Down Expand Up @@ -8782,6 +8783,9 @@ namespace Parser {
case "callback":
tag = parseCallbackTag(start, tagName, margin, indentText);
break;
case "overload":
tag = parseOverloadTag(start, tagName, margin, indentText);
break;
case "see":
tag = parseSeeTag(start, tagName, margin, indentText);
break;
Expand Down Expand Up @@ -9275,10 +9279,7 @@ namespace Parser {
return createNodeArray(parameters || [], pos);
}

function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
const fullName = parseJSDocTypeNameWithNamespace();
skipWhitespace();
let comment = parseTagComments(indent);
function parseJSDocSignature(start: number, indent: number): JSDocSignature {
const parameters = parseCallbackTagParameters(indent);
const returnTag = tryParse(() => {
if (parseOptionalJsdoc(SyntaxKind.AtToken)) {
Expand All @@ -9288,14 +9289,32 @@ namespace Parser {
}
}
});
const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
return finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start);
}

function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag {
const fullName = parseJSDocTypeNameWithNamespace();
skipWhitespace();
let comment = parseTagComments(indent);
const typeExpression = parseJSDocSignature(start, indent);
if (!comment) {
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
}
const end = comment !== undefined ? getNodePos() : typeExpression.end;
return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end);
}

function parseOverloadTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocOverloadTag {
skipWhitespace();
let comment = parseTagComments(indent);
const typeExpression = parseJSDocSignature(start, indent);
if (!comment) {
comment = parseTrailingTagComments(start, getNodePos(), indent, indentText);
}
const end = comment !== undefined ? getNodePos() : typeExpression.end;
return finishNode(factory.createJSDocOverloadTag(tagName, typeExpression, comment), start, end);
}

function escapedTextsEqual(a: EntityName, b: EntityName): boolean {
while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) {
if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) {
Expand Down
10 changes: 10 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ export const enum SyntaxKind {
JSDocReadonlyTag,
JSDocOverrideTag,
JSDocCallbackTag,
JSDocOverloadTag,
JSDocEnumTag,
JSDocParameterTag,
JSDocReturnTag,
Expand Down Expand Up @@ -4065,6 +4066,13 @@ export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration, LocalsCont
readonly typeExpression: JSDocSignature;
}


export interface JSDocOverloadTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocOverloadTag;
readonly parent: JSDoc;
readonly typeExpression: JSDocSignature;
}

export interface JSDocThrowsTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocThrowsTag;
readonly typeExpression?: JSDocTypeExpression;
Expand Down Expand Up @@ -8524,6 +8532,8 @@ export interface NodeFactory {
updateJSDocEnumTag(node: JSDocEnumTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocEnumTag;
createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray<JSDocComment>): JSDocCallbackTag;
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag;
updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag;
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ import {
isJSDocMemberName,
isJSDocNameReference,
isJSDocNode,
isJSDocOverloadTag,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocSignature,
Expand Down Expand Up @@ -5801,7 +5802,7 @@ export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParam

/** template tags are only available when a typedef isn't already using them */
function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag {
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && tag.parent.tags!.some(isJSDocTypeAlias));
return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDoc && (tag.parent.tags!.some(isJSDocTypeAlias) || tag.parent.tags!.some(isJSDocOverloadTag)));
}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ import {
isJSDocEnumTag,
isJSDocFunctionType,
isJSDocImplementsTag,
isJSDocOverloadTag,
isJSDocOverrideTag,
isJSDocParameterTag,
isJSDocPrivateTag,
Expand Down Expand Up @@ -1210,6 +1211,14 @@ function formatJSDocLink(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
*/
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): readonly TypeParameterDeclaration[] {
if (isJSDocSignature(node)) {
if (isJSDoc(node.parent)) {
const overloadTag = find(node.parent.tags, (tag) => {
return isJSDocOverloadTag(tag) && tag.typeExpression === node;
});
if (overloadTag) {
return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined);
}
}
return emptyArray;
}
if (isJSDocTypeAlias(node)) {
Expand Down
49 changes: 29 additions & 20 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4339,24 +4339,25 @@ declare namespace ts {
JSDocReadonlyTag = 339,
JSDocOverrideTag = 340,
JSDocCallbackTag = 341,
JSDocEnumTag = 342,
JSDocParameterTag = 343,
JSDocReturnTag = 344,
JSDocThisTag = 345,
JSDocTypeTag = 346,
JSDocTemplateTag = 347,
JSDocTypedefTag = 348,
JSDocSeeTag = 349,
JSDocPropertyTag = 350,
JSDocThrowsTag = 351,
SyntaxList = 352,
NotEmittedStatement = 353,
PartiallyEmittedExpression = 354,
CommaListExpression = 355,
MergeDeclarationMarker = 356,
EndOfDeclarationMarker = 357,
SyntheticReferenceExpression = 358,
Count = 359,
JSDocOverloadTag = 342,
JSDocEnumTag = 343,
JSDocParameterTag = 344,
JSDocReturnTag = 345,
JSDocThisTag = 346,
JSDocTypeTag = 347,
JSDocTemplateTag = 348,
JSDocTypedefTag = 349,
JSDocSeeTag = 350,
JSDocPropertyTag = 351,
JSDocThrowsTag = 352,
SyntaxList = 353,
NotEmittedStatement = 354,
PartiallyEmittedExpression = 355,
CommaListExpression = 356,
MergeDeclarationMarker = 357,
EndOfDeclarationMarker = 358,
SyntheticReferenceExpression = 359,
Count = 360,
FirstAssignment = 63,
LastAssignment = 78,
FirstCompoundAssignment = 64,
Expand Down Expand Up @@ -4385,9 +4386,9 @@ declare namespace ts {
LastStatement = 256,
FirstNode = 163,
FirstJSDocNode = 312,
LastJSDocNode = 351,
LastJSDocNode = 352,
FirstJSDocTagNode = 330,
LastJSDocTagNode = 351
LastJSDocTagNode = 352
}
type TriviaSyntaxKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.NewLineTrivia | SyntaxKind.WhitespaceTrivia | SyntaxKind.ShebangTrivia | SyntaxKind.ConflictMarkerTrivia;
type LiteralSyntaxKind = SyntaxKind.NumericLiteral | SyntaxKind.BigIntLiteral | SyntaxKind.StringLiteral | SyntaxKind.JsxText | SyntaxKind.JsxTextAllWhiteSpaces | SyntaxKind.RegularExpressionLiteral | SyntaxKind.NoSubstitutionTemplateLiteral;
Expand Down Expand Up @@ -5946,6 +5947,11 @@ declare namespace ts {
readonly name?: Identifier;
readonly typeExpression: JSDocSignature;
}
interface JSDocOverloadTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocOverloadTag;
readonly parent: JSDoc;
readonly typeExpression: JSDocSignature;
}
interface JSDocThrowsTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocThrowsTag;
readonly typeExpression?: JSDocTypeExpression;
Expand Down Expand Up @@ -7800,6 +7806,8 @@ declare namespace ts {
updateJSDocEnumTag(node: JSDocEnumTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocEnumTag;
createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray<JSDocComment>): JSDocCallbackTag;
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
createJSDocOverloadTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, comment?: string | NodeArray<JSDocComment>): JSDocOverloadTag;
updateJSDocOverloadTag(node: JSDocOverloadTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, comment: string | NodeArray<JSDocComment> | undefined): JSDocOverloadTag;
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
Expand Down Expand Up @@ -9056,6 +9064,7 @@ declare namespace ts {
function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag;
function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag;
function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag;
function isJSDocOverloadTag(node: Node): node is JSDocOverloadTag;
function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag;
function isJSDocSeeTag(node: Node): node is JSDocSeeTag;
function isJSDocEnumTag(node: Node): node is JSDocEnumTag;
Expand Down
Loading

0 comments on commit e4816ed

Please sign in to comment.