Skip to content

Commit aa7e2b0

Browse files
authored
Add callback tag, with type parameters (#23947)
* Add initial tests * Add types * Half of parsing (builds but does not pass tests) * Parsing done; types are uglier; doesn't crash but doesn't pass * Bind callback tag Builds but tests still don't pass * Only bind param tags inside callback tags * Fix binding switch to only handle param tags once * Checking is 1/3 done or so. Now I'm going to go rename some members to be more uniform. I hate unnnecessary conditionals. * Rename typeExpression to type (for some jsdoc) (maybe I'll rename more later) * Rename the rest of typeExpressions Turns out there is a constraint in services such that they all need to be named the same. * Few more checker changes * Revert "Rename the rest of typeExpressions" This reverts commit f41a96b. * Revert "Rename typeExpression to type (for some jsdoc)" This reverts commit 7d2233a. * Finish undoing typeExpression rename * Rename and improve getTypeParametersForAliasSymbol Plus some other small fixes * Core checking works, but is flabbergastingly messy I'm serious. * Callback return types work now * Fix crash in services * Make github diff smaller * Try to make github diff even smaller * Fix rename for callback tag * Fix nav bar for callback tag Also clean up some now-redundant code there to find the name of typedefs. * Handle ooorder callback tags Also get rid of redundant typedef name code *in the binder*. It's everywhere! * Add ooorder callback tag test * Parse comments for typedef/callback+display param comments * Always export callbacks This requires almost no new code since it is basically the same as typedefs * Update baselines * Fix support for nested namespaced callbacks And add test * Callbacks support type parameters 1. Haven't run it with all tests 2. Haven't tested typedef tags yet 3. Still allows shared symbols when on function or class declarations. * Template tags are now bound correctly * Test oorder template tags It works. * Parser cleanup * Cleanup types and utilities As much as possible, and not as much as I would like. * Handle callback more often in services * Cleanup of binder and checker * More checker cleanup * Remove TODOs and one more cleanup * Support parameter-less callback tags * Remove extra bind call on template type parameters * Bind template tag containers Doesn't quite work with typedefs, but that's because it's now stricter, without the typedef fixes. I'm going to merge with jsdoc/callback and see how it goes. * Fix fourslash failures * Stop pre-binding js type aliases Next up, stop pre-binding js type parameters * Further cleanup of delayed js type alias binding * Stop prebinding template tags too This gets rid of prebinding entirely * Remove TODO * Fix lint * Finish merge with use-jsdoc-aliases * Update callback tag baselines * Rename getTypeParametersForAliasSymbol The real fix is *probably* to rename Type.aliasTypeArguments to aliasTypeParameters, but I want to make sure and then put it in a separate PR.
1 parent dbd4ef4 commit aa7e2b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1168
-384
lines changed

src/compiler/binder.ts

+75-88
Large diffs are not rendered by default.

src/compiler/checker.ts

+103-69
Large diffs are not rendered by default.

src/compiler/parser.ts

+94-39
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,15 @@ namespace ts {
489489
return visitNode(cbNode, (<JSDocTypedefTag>node).fullName) ||
490490
visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression);
491491
}
492+
case SyntaxKind.JSDocCallbackTag:
493+
return visitNode(cbNode, (node as JSDocCallbackTag).fullName) ||
494+
visitNode(cbNode, (node as JSDocCallbackTag).typeExpression);
495+
case SyntaxKind.JSDocSignature:
496+
return visitNodes(cbNode, cbNodes, node.decorators) ||
497+
visitNodes(cbNode, cbNodes, node.modifiers) ||
498+
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).typeParameters) ||
499+
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).parameters) ||
500+
visitNode(cbNode, (<SignatureDeclaration>node).type);
492501
case SyntaxKind.JSDocTypeLiteral:
493502
if ((node as JSDocTypeLiteral).jsDocPropertyTags) {
494503
for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) {
@@ -6331,8 +6340,9 @@ namespace ts {
63316340
}
63326341

63336342
const enum PropertyLikeParse {
6334-
Property,
6335-
Parameter,
6343+
Property = 1 << 0,
6344+
Parameter = 1 << 1,
6345+
CallbackParameter = 1 << 2,
63366346
}
63376347

63386348
export function parseJSDocCommentWorker(start: number, length: number): JSDoc {
@@ -6386,7 +6396,7 @@ namespace ts {
63866396
case SyntaxKind.AtToken:
63876397
if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) {
63886398
removeTrailingNewlines(comments);
6389-
parseTag(indent);
6399+
addTag(parseTag(indent));
63906400
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
63916401
// Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
63926402
// for malformed examples like `/** @param {string} x @returns {number} the length */`
@@ -6503,8 +6513,7 @@ namespace ts {
65036513
case "arg":
65046514
case "argument":
65056515
case "param":
6506-
addTag(parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent));
6507-
return;
6516+
return parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent);
65086517
case "return":
65096518
case "returns":
65106519
tag = parseReturnTag(atToken, tagName);
@@ -6516,7 +6525,10 @@ namespace ts {
65166525
tag = parseTypeTag(atToken, tagName);
65176526
break;
65186527
case "typedef":
6519-
tag = parseTypedefTag(atToken, tagName);
6528+
tag = parseTypedefTag(atToken, tagName, indent);
6529+
break;
6530+
case "callback":
6531+
tag = parseCallbackTag(atToken, tagName, indent);
65206532
break;
65216533
default:
65226534
tag = parseUnknownTag(atToken, tagName);
@@ -6531,8 +6543,11 @@ namespace ts {
65316543
// a badly malformed tag should not be added to the list of tags
65326544
return;
65336545
}
6534-
tag.comment = parseTagComments(indent + tag.end - tag.pos);
6535-
addTag(tag);
6546+
if (!tag.comment) {
6547+
// some tags, like typedef and callback, have already parsed their comments earlier
6548+
tag.comment = parseTagComments(indent + tag.end - tag.pos);
6549+
}
6550+
return tag;
65366551
}
65376552

65386553
function parseTagComments(indent: number): string | undefined {
@@ -6605,6 +6620,9 @@ namespace ts {
66056620
}
66066621

66076622
function addTag(tag: JSDocTag): void {
6623+
if (!tag) {
6624+
return;
6625+
}
66086626
if (!tags) {
66096627
tags = [tag];
66106628
tagsPos = tag.pos;
@@ -6665,9 +6683,9 @@ namespace ts {
66656683
typeExpression = tryParseTypeExpression();
66666684
}
66676685

6668-
const result = target === PropertyLikeParse.Parameter ?
6669-
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos) :
6670-
<JSDocPropertyTag>createNode(SyntaxKind.JSDocPropertyTag, atToken.pos);
6686+
const result = target === PropertyLikeParse.Property ?
6687+
<JSDocPropertyTag>createNode(SyntaxKind.JSDocPropertyTag, atToken.pos) :
6688+
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos);
66716689
let comment: string | undefined;
66726690
if (indent !== undefined) comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos);
66736691
const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name, target);
@@ -6771,27 +6789,17 @@ namespace ts {
67716789
return finishNode(tag);
67726790
}
67736791

6774-
function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag {
6792+
function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag {
67756793
const typeExpression = tryParseTypeExpression();
67766794
skipWhitespace();
67776795

67786796
const typedefTag = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag, atToken.pos);
67796797
typedefTag.atToken = atToken;
67806798
typedefTag.tagName = tagName;
6781-
typedefTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0);
6782-
if (typedefTag.fullName) {
6783-
let rightNode = typedefTag.fullName;
6784-
while (true) {
6785-
if (rightNode.kind === SyntaxKind.Identifier || !rightNode.body) {
6786-
// if node is identifier - use it as name
6787-
// otherwise use name of the rightmost part that we were able to parse
6788-
typedefTag.name = rightNode.kind === SyntaxKind.Identifier ? rightNode : rightNode.name;
6789-
break;
6790-
}
6791-
rightNode = rightNode.body;
6792-
}
6793-
}
6799+
typedefTag.fullName = parseJSDocTypeNameWithNamespace();
6800+
typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName);
67946801
skipWhitespace();
6802+
typedefTag.comment = parseTagComments(indent);
67956803

67966804
typedefTag.typeExpression = typeExpression;
67976805
if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) {
@@ -6826,23 +6834,69 @@ namespace ts {
68266834
}
68276835

68286836
return finishNode(typedefTag);
6837+
}
68296838

6830-
function parseJSDocTypeNameWithNamespace(flags: NodeFlags) {
6831-
const pos = scanner.getTokenPos();
6832-
const typeNameOrNamespaceName = parseJSDocIdentifierName();
6839+
function parseJSDocTypeNameWithNamespace(nested?: boolean) {
6840+
const pos = scanner.getTokenPos();
6841+
const typeNameOrNamespaceName = parseJSDocIdentifierName();
68336842

6834-
if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) {
6835-
const jsDocNamespaceNode = <JSDocNamespaceDeclaration>createNode(SyntaxKind.ModuleDeclaration, pos);
6836-
jsDocNamespaceNode.flags |= flags;
6837-
jsDocNamespaceNode.name = typeNameOrNamespaceName;
6838-
jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace);
6839-
return finishNode(jsDocNamespaceNode);
6843+
if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) {
6844+
const jsDocNamespaceNode = <JSDocNamespaceDeclaration>createNode(SyntaxKind.ModuleDeclaration, pos);
6845+
if (nested) {
6846+
jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace;
68406847
}
6848+
jsDocNamespaceNode.name = typeNameOrNamespaceName;
6849+
jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true);
6850+
return finishNode(jsDocNamespaceNode);
6851+
}
6852+
6853+
if (typeNameOrNamespaceName && nested) {
6854+
typeNameOrNamespaceName.isInJSDocNamespace = true;
6855+
}
6856+
return typeNameOrNamespaceName;
6857+
}
68416858

6842-
if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) {
6843-
typeNameOrNamespaceName.isInJSDocNamespace = true;
6859+
function parseCallbackTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocCallbackTag {
6860+
const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag;
6861+
callbackTag.atToken = atToken;
6862+
callbackTag.tagName = tagName;
6863+
callbackTag.fullName = parseJSDocTypeNameWithNamespace();
6864+
callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName);
6865+
skipWhitespace();
6866+
callbackTag.comment = parseTagComments(indent);
6867+
6868+
let child: JSDocParameterTag | false;
6869+
const start = scanner.getStartPos();
6870+
const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature;
6871+
jsdocSignature.parameters = [];
6872+
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) {
6873+
jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray<JSDocParameterTag>, child);
6874+
}
6875+
const returnTag = tryParse(() => {
6876+
if (token() === SyntaxKind.AtToken) {
6877+
nextJSDocToken();
6878+
const tag = parseTag(indent);
6879+
if (tag && tag.kind === SyntaxKind.JSDocReturnTag) {
6880+
return tag as JSDocReturnTag;
6881+
}
6882+
}
6883+
});
6884+
if (returnTag) {
6885+
jsdocSignature.type = returnTag;
6886+
}
6887+
callbackTag.typeExpression = finishNode(jsdocSignature);
6888+
return finishNode(callbackTag);
6889+
}
6890+
6891+
function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) {
6892+
if (fullName) {
6893+
let rightNode = fullName;
6894+
while (true) {
6895+
if (ts.isIdentifier(rightNode) || !rightNode.body) {
6896+
return ts.isIdentifier(rightNode) ? rightNode : rightNode.name;
6897+
}
6898+
rightNode = rightNode.body;
68446899
}
6845-
return typeNameOrNamespaceName;
68466900
}
68476901
}
68486902

@@ -6872,6 +6926,7 @@ namespace ts {
68726926
if (canParseTag) {
68736927
const child = tryParseChildTag(target);
68746928
if (child && child.kind === SyntaxKind.JSDocParameterTag &&
6929+
target !== PropertyLikeParse.CallbackParameter &&
68756930
(ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) {
68766931
return false;
68776932
}
@@ -6920,12 +6975,12 @@ namespace ts {
69206975
case "arg":
69216976
case "argument":
69226977
case "param":
6923-
t = PropertyLikeParse.Parameter;
6978+
t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter;
69246979
break;
69256980
default:
69266981
return false;
69276982
}
6928-
if (target !== t) {
6983+
if (!(target & t)) {
69296984
return false;
69306985
}
69316986
const tag = parseParameterOrPropertyTag(atToken, tagName, target, /*indent*/ undefined);

src/compiler/types.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,11 @@ namespace ts {
413413
JSDocVariadicType,
414414
JSDocComment,
415415
JSDocTypeLiteral,
416+
JSDocSignature,
416417
JSDocTag,
417418
JSDocAugmentsTag,
418419
JSDocClassTag,
420+
JSDocCallbackTag,
419421
JSDocParameterTag,
420422
JSDocReturnTag,
421423
JSDocTypeTag,
@@ -2053,7 +2055,7 @@ namespace ts {
20532055

20542056
export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
20552057

2056-
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
2058+
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature;
20572059

20582060
export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
20592061
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
@@ -2387,6 +2389,21 @@ namespace ts {
23872389
typeExpression?: JSDocTypeExpression | JSDocTypeLiteral;
23882390
}
23892391

2392+
export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration {
2393+
parent: JSDoc;
2394+
kind: SyntaxKind.JSDocCallbackTag;
2395+
fullName?: JSDocNamespaceDeclaration | Identifier;
2396+
name?: Identifier;
2397+
typeExpression: JSDocSignature;
2398+
}
2399+
2400+
export interface JSDocSignature extends JSDocType, Declaration {
2401+
kind: SyntaxKind.JSDocSignature;
2402+
typeParameters?: ReadonlyArray<JSDocTemplateTag>;
2403+
parameters: ReadonlyArray<JSDocParameterTag>;
2404+
type: JSDocReturnTag | undefined;
2405+
}
2406+
23902407
export interface JSDocPropertyLikeTag extends JSDocTag, Declaration {
23912408
parent: JSDoc;
23922409
name: EntityName;
@@ -4032,7 +4049,7 @@ namespace ts {
40324049
}
40334050

40344051
export interface Signature {
4035-
declaration?: SignatureDeclaration; // Originating declaration
4052+
declaration?: SignatureDeclaration | JSDocSignature; // Originating declaration
40364053
typeParameters?: TypeParameter[]; // Type parameters (undefined if non-generic)
40374054
parameters: Symbol[]; // Parameters
40384055
/* @internal */

0 commit comments

Comments
 (0)