Skip to content

Add jsdoc member names: Class#method #44150

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 8 commits into from
May 21, 2021
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
86 changes: 42 additions & 44 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39151,16 +39151,6 @@ namespace ts {
return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
}

function getJSDocEntryNameReference(node: Identifier | PrivateIdentifier | PropertyAccessExpression | QualifiedName): JSDocNameReference | undefined {
while (node.parent.kind === SyntaxKind.QualifiedName) {
node = node.parent as QualifiedName;
}
while (node.parent.kind === SyntaxKind.PropertyAccessExpression) {
node = node.parent as PropertyAccessExpression;
}
return isJSDocNameReference(node.parent) ? node.parent : undefined;
}

function forEachEnclosingClass<T>(node: Node, callback: (node: Node) => T | undefined): T | undefined {
let result: T | undefined;

Expand Down Expand Up @@ -39235,7 +39225,7 @@ namespace ts {
return undefined;
}

function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression): Symbol | undefined {
function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined {
if (isDeclarationName(name)) {
return getSymbolOfNode(name.parent);
}
Expand All @@ -39244,7 +39234,7 @@ namespace ts {
name.parent.kind === SyntaxKind.PropertyAccessExpression &&
name.parent === (name.parent.parent as BinaryExpression).left) {
// Check if this is a special property assignment
if (!isPrivateIdentifier(name)) {
if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) {
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name);
if (specialPropertyAssignmentSymbol) {
return specialPropertyAssignmentSymbol;
Expand All @@ -39260,14 +39250,14 @@ namespace ts {
return success;
}
}
else if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name) && isInRightSideOfImportOrExportAssignment(name)) {
else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) {
// Since we already checked for ExportAssignment, this really could only be an Import
const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration);
Debug.assert(importEqualsDeclaration !== undefined);
return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true);
}

if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name)) {
if (isEntityName(name)) {
const possibleImportNode = isImportTypeQualifierPart(name);
if (possibleImportNode) {
getTypeFromTypeNode(possibleImportNode);
Expand All @@ -39276,8 +39266,8 @@ namespace ts {
}
}

while (isRightSideOfQualifiedNameOrPropertyAccess(name)) {
name = name.parent as QualifiedName | PropertyAccessEntityNameExpression;
while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) {
name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName;
}

if (isHeritageClauseElementIdentifier(name)) {
Expand Down Expand Up @@ -39318,12 +39308,14 @@ namespace ts {
return undefined;
}

const isJSDoc = findAncestor(name, or(isJSDocLink, isJSDocNameReference, isJSDocMemberName));
const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value;
if (name.kind === SyntaxKind.Identifier) {
if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) {
const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement);
return symbol === unknownSymbol ? undefined : symbol;
}
return resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
return resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name));
}
else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) {
const links = getNodeLinks(name);
Expand All @@ -39337,47 +39329,53 @@ namespace ts {
else {
checkQualifiedName(name, CheckMode.Normal);
}
if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) {
return resolveJSDocMemberName(name);
}
return links.resolvedSymbol;
}
else if (isJSDocMemberName(name)) {
return resolveJSDocMemberName(name);
}
}
else if (isTypeReferenceIdentifier(name as EntityName)) {
const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace;
return resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
}

const jsdocReference = getJSDocEntryNameReference(name);
if (jsdocReference || isJSDocLink(name.parent)) {
const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value;
const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ false, getHostSignatureFromJSDoc(name));
if (symbol) {
return symbol;
}
else if (isQualifiedName(name) && isIdentifier(name.left)) {
// resolve C.m as a static member first
const links = getNodeLinks(name);
if (links.resolvedSymbol) {
return links.resolvedSymbol;
}
checkQualifiedName(name, CheckMode.Normal);
if (links.resolvedSymbol) {
return links.resolvedSymbol;
}

// then resolve it as an instance member
const s = resolveEntityName(name.left, meaning, /*ignoreErrors*/ false);
if (s) {
const t = getDeclaredTypeOfSymbol(s);
return getPropertyOfType(t, name.right.escapedText);
}
}
}
if (name.parent.kind === SyntaxKind.TypePredicate) {
return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable);
}

return undefined;
}

/**
* Recursively resolve entity names and jsdoc instance references:
* 1. K#m as K.prototype.m for a class (or other value) K
* 2. K.m as K.prototype.m
* 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2)
*/
function resolveJSDocMemberName(name: EntityName | JSDocMemberName): Symbol | undefined {
if (isEntityName(name)) {
const symbol = resolveEntityName(
name,
SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value,
/*ignoreErrors*/ false,
/*dontResolveAlias*/ true,
getHostSignatureFromJSDoc(name));
if (symbol || isIdentifier(name)) {
// can't recur on identifier, so just return when it's undefined
return symbol;
}
}
const left = resolveJSDocMemberName(name.left);
if (left) {
const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String);
const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left);
return getPropertyOfType(t, name.right.escapedText);
}
}

function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined {
if (node.kind === SyntaxKind.SourceFile) {
return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined;
Expand Down
29 changes: 25 additions & 4 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ namespace ts {
updateJSDocSeeTag,
createJSDocNameReference,
updateJSDocNameReference,
createJSDocMemberName,
updateJSDocMemberName,
createJSDocLink,
updateJSDocLink,
// lazily load factory members for JSDoc tags with similar structure
Expand Down Expand Up @@ -4391,29 +4393,48 @@ namespace ts {
}

// @api
function createJSDocNameReference(name: EntityName): JSDocNameReference {
function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference {
const node = createBaseNode<JSDocNameReference>(SyntaxKind.JSDocNameReference);
node.name = name;
return node;
}

// @api
function updateJSDocNameReference(node: JSDocNameReference, name: EntityName): JSDocNameReference {
function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference {
return node.name !== name
? update(createJSDocNameReference(name), node)
: node;
}

// @api
function createJSDocLink(name: EntityName | undefined, text: string): JSDocLink {
function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) {
const node = createBaseNode<JSDocMemberName>(SyntaxKind.JSDocMemberName);
node.left = left;
node.right = right;
node.transformFlags |=
propagateChildFlags(node.left) |
propagateChildFlags(node.right);
return node;
}

// @api
function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) {
return node.left !== left
|| node.right !== right
? update(createJSDocMemberName(left, right), node)
: node;
}

// @api
function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink {
const node = createBaseNode<JSDocLink>(SyntaxKind.JSDocLink);
node.name = name;
node.text = text;
return node;
}

// @api
function updateJSDocLink(node: JSDocLink, name: EntityName | undefined, text: string): JSDocLink {
function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink {
return node.name !== name
? update(createJSDocLink(name, text), node)
: node;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocNameReference;
}

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

export function isJSDocLink(node: Node): node is JSDocLink {
return node.kind === SyntaxKind.JSDocLink;
}
Expand Down
25 changes: 23 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@ namespace ts {
(typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray<JSDocText | JSDocLink> | undefined));
case SyntaxKind.JSDocNameReference:
return visitNode(cbNode, (node as JSDocNameReference).name);
case SyntaxKind.JSDocMemberName:
return visitNode(cbNode, (node as JSDocMemberName).left) ||
visitNode(cbNode, (node as JSDocMemberName).right);
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
return visitNode(cbNode, (node as JSDocTag).tagName) ||
Expand Down Expand Up @@ -1427,6 +1430,10 @@ namespace ts {
return currentToken = scanner.reScanLessThanToken();
}

function reScanHashToken(): SyntaxKind {
return currentToken = scanner.reScanHashToken();
}

function scanJsxIdentifier(): SyntaxKind {
return currentToken = scanner.scanJsxIdentifier();
}
Expand Down Expand Up @@ -7305,7 +7312,13 @@ namespace ts {
export function parseJSDocNameReference(): JSDocNameReference {
const pos = getNodePos();
const hasBrace = parseOptional(SyntaxKind.OpenBraceToken);
const entityName = parseEntityName(/* allowReservedWords*/ false);
const p2 = getNodePos();
let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false);
while (token() === SyntaxKind.PrivateIdentifier) {
Copy link
Member

@DanielRosenwasser DanielRosenwasser May 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while?

What does Foo#Bar#Baz parse as?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A left-deep JSDocInstanceReference tree: ((Foo#Bar)#Baz)

reScanHashToken(); // rescan #id as # id
nextTokenJSDoc(); // then skip the #
entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2);
}
if (hasBrace) {
parseExpectedJSDoc(SyntaxKind.CloseBraceToken);
}
Expand Down Expand Up @@ -7760,9 +7773,17 @@ namespace ts {
nextTokenJSDoc(); // start at token after link, then skip any whitespace
skipWhitespace();
// parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error
const name = tokenIsIdentifierOrKeyword(token())
const p2 = getNodePos();
let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token())
? parseEntityName(/*allowReservedWords*/ true)
: undefined;
if (name) {
while (token() === SyntaxKind.PrivateIdentifier) {
reScanHashToken(); // rescan #id as # id
nextTokenJSDoc(); // then skip the #
name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2);
}
}
const text = [];
while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) {
text.push(scanner.getTokenText());
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace ts {
reScanJsxAttributeValue(): SyntaxKind;
reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind;
reScanLessThanToken(): SyntaxKind;
reScanHashToken(): SyntaxKind;
reScanQuestionToken(): SyntaxKind;
reScanInvalidIdentifier(): SyntaxKind;
scanJsxToken(): JsxTokenSyntaxKind;
Expand Down Expand Up @@ -220,7 +221,8 @@ namespace ts {
"&&=": SyntaxKind.AmpersandAmpersandEqualsToken,
"??=": SyntaxKind.QuestionQuestionEqualsToken,
"@": SyntaxKind.AtToken,
"`": SyntaxKind.BacktickToken
"#": SyntaxKind.HashToken,
"`": SyntaxKind.BacktickToken,
}));

/*
Expand Down Expand Up @@ -981,6 +983,7 @@ namespace ts {
reScanJsxAttributeValue,
reScanJsxToken,
reScanLessThanToken,
reScanHashToken,
reScanQuestionToken,
reScanInvalidIdentifier,
scanJsxToken,
Expand Down Expand Up @@ -2251,6 +2254,14 @@ namespace ts {
return token;
}

function reScanHashToken(): SyntaxKind {
if (token === SyntaxKind.PrivateIdentifier) {
pos = tokenPos + 1;
return token = SyntaxKind.HashToken;
}
return token;
}

function reScanQuestionToken(): SyntaxKind {
Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'");
pos = tokenPos + 1;
Expand Down Expand Up @@ -2437,6 +2448,8 @@ namespace ts {
return token = SyntaxKind.DotToken;
case CharacterCodes.backtick:
return token = SyntaxKind.BacktickToken;
case CharacterCodes.hash:
return token = SyntaxKind.HashToken;
case CharacterCodes.backslash:
pos--;
const extendedCookedChar = peekExtendedUnicodeEscape();
Expand Down
Loading