Skip to content

Commit 0864237

Browse files
authored
Add jsdoc member names: Class#method (#44150)
* Everything mostly works A couple of mixed, nested references don't work yet. The scanner+parser interaction is wrong, the parser consumes one too many spaces, and the checker+services code needs a little cleanup. * Cleanup 1. I decided that correctly parsing a#b.c, an entity name containing an instance reference, is not worth the work. 2. I made the scanner, at least the jsdoc part, emit a # token, and provided a reScanPrivateIdentifier in order to convert #a to # a. 3. I cleaned up the code in the checker. 2. Unrelated: I added a missing space in linkPart display. * Cleanup lint + var naming * investigate+clean up a couple of TODOs * Fix lint in utilities.ts * change name to JSDocMemberName * address PR comments
1 parent 71cdf6a commit 0864237

19 files changed

+2244
-714
lines changed

src/compiler/checker.ts

+42-44
Original file line numberDiff line numberDiff line change
@@ -39156,16 +39156,6 @@ namespace ts {
3915639156
return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
3915739157
}
3915839158

39159-
function getJSDocEntryNameReference(node: Identifier | PrivateIdentifier | PropertyAccessExpression | QualifiedName): JSDocNameReference | undefined {
39160-
while (node.parent.kind === SyntaxKind.QualifiedName) {
39161-
node = node.parent as QualifiedName;
39162-
}
39163-
while (node.parent.kind === SyntaxKind.PropertyAccessExpression) {
39164-
node = node.parent as PropertyAccessExpression;
39165-
}
39166-
return isJSDocNameReference(node.parent) ? node.parent : undefined;
39167-
}
39168-
3916939159
function forEachEnclosingClass<T>(node: Node, callback: (node: Node) => T | undefined): T | undefined {
3917039160
let result: T | undefined;
3917139161

@@ -39240,7 +39230,7 @@ namespace ts {
3924039230
return undefined;
3924139231
}
3924239232

39243-
function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression): Symbol | undefined {
39233+
function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined {
3924439234
if (isDeclarationName(name)) {
3924539235
return getSymbolOfNode(name.parent);
3924639236
}
@@ -39249,7 +39239,7 @@ namespace ts {
3924939239
name.parent.kind === SyntaxKind.PropertyAccessExpression &&
3925039240
name.parent === (name.parent.parent as BinaryExpression).left) {
3925139241
// Check if this is a special property assignment
39252-
if (!isPrivateIdentifier(name)) {
39242+
if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) {
3925339243
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name);
3925439244
if (specialPropertyAssignmentSymbol) {
3925539245
return specialPropertyAssignmentSymbol;
@@ -39265,14 +39255,14 @@ namespace ts {
3926539255
return success;
3926639256
}
3926739257
}
39268-
else if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name) && isInRightSideOfImportOrExportAssignment(name)) {
39258+
else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) {
3926939259
// Since we already checked for ExportAssignment, this really could only be an Import
3927039260
const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration);
3927139261
Debug.assert(importEqualsDeclaration !== undefined);
3927239262
return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true);
3927339263
}
3927439264

39275-
if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name)) {
39265+
if (isEntityName(name)) {
3927639266
const possibleImportNode = isImportTypeQualifierPart(name);
3927739267
if (possibleImportNode) {
3927839268
getTypeFromTypeNode(possibleImportNode);
@@ -39281,8 +39271,8 @@ namespace ts {
3928139271
}
3928239272
}
3928339273

39284-
while (isRightSideOfQualifiedNameOrPropertyAccess(name)) {
39285-
name = name.parent as QualifiedName | PropertyAccessEntityNameExpression;
39274+
while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) {
39275+
name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName;
3928639276
}
3928739277

3928839278
if (isHeritageClauseElementIdentifier(name)) {
@@ -39323,12 +39313,14 @@ namespace ts {
3932339313
return undefined;
3932439314
}
3932539315

39316+
const isJSDoc = findAncestor(name, or(isJSDocLink, isJSDocNameReference, isJSDocMemberName));
39317+
const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value;
3932639318
if (name.kind === SyntaxKind.Identifier) {
3932739319
if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) {
3932839320
const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement);
3932939321
return symbol === unknownSymbol ? undefined : symbol;
3933039322
}
39331-
return resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
39323+
return resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name));
3933239324
}
3933339325
else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) {
3933439326
const links = getNodeLinks(name);
@@ -39342,47 +39334,53 @@ namespace ts {
3934239334
else {
3934339335
checkQualifiedName(name, CheckMode.Normal);
3934439336
}
39337+
if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) {
39338+
return resolveJSDocMemberName(name);
39339+
}
3934539340
return links.resolvedSymbol;
3934639341
}
39342+
else if (isJSDocMemberName(name)) {
39343+
return resolveJSDocMemberName(name);
39344+
}
3934739345
}
3934839346
else if (isTypeReferenceIdentifier(name as EntityName)) {
3934939347
const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace;
3935039348
return resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
3935139349
}
39352-
39353-
const jsdocReference = getJSDocEntryNameReference(name);
39354-
if (jsdocReference || isJSDocLink(name.parent)) {
39355-
const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value;
39356-
const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ false, getHostSignatureFromJSDoc(name));
39357-
if (symbol) {
39358-
return symbol;
39359-
}
39360-
else if (isQualifiedName(name) && isIdentifier(name.left)) {
39361-
// resolve C.m as a static member first
39362-
const links = getNodeLinks(name);
39363-
if (links.resolvedSymbol) {
39364-
return links.resolvedSymbol;
39365-
}
39366-
checkQualifiedName(name, CheckMode.Normal);
39367-
if (links.resolvedSymbol) {
39368-
return links.resolvedSymbol;
39369-
}
39370-
39371-
// then resolve it as an instance member
39372-
const s = resolveEntityName(name.left, meaning, /*ignoreErrors*/ false);
39373-
if (s) {
39374-
const t = getDeclaredTypeOfSymbol(s);
39375-
return getPropertyOfType(t, name.right.escapedText);
39376-
}
39377-
}
39378-
}
3937939350
if (name.parent.kind === SyntaxKind.TypePredicate) {
3938039351
return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable);
3938139352
}
3938239353

3938339354
return undefined;
3938439355
}
3938539356

39357+
/**
39358+
* Recursively resolve entity names and jsdoc instance references:
39359+
* 1. K#m as K.prototype.m for a class (or other value) K
39360+
* 2. K.m as K.prototype.m
39361+
* 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2)
39362+
*/
39363+
function resolveJSDocMemberName(name: EntityName | JSDocMemberName): Symbol | undefined {
39364+
if (isEntityName(name)) {
39365+
const symbol = resolveEntityName(
39366+
name,
39367+
SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value,
39368+
/*ignoreErrors*/ false,
39369+
/*dontResolveAlias*/ true,
39370+
getHostSignatureFromJSDoc(name));
39371+
if (symbol || isIdentifier(name)) {
39372+
// can't recur on identifier, so just return when it's undefined
39373+
return symbol;
39374+
}
39375+
}
39376+
const left = resolveJSDocMemberName(name.left);
39377+
if (left) {
39378+
const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String);
39379+
const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left);
39380+
return getPropertyOfType(t, name.right.escapedText);
39381+
}
39382+
}
39383+
3938639384
function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined {
3938739385
if (node.kind === SyntaxKind.SourceFile) {
3938839386
return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined;

src/compiler/factory/nodeFactory.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ namespace ts {
345345
updateJSDocSeeTag,
346346
createJSDocNameReference,
347347
updateJSDocNameReference,
348+
createJSDocMemberName,
349+
updateJSDocMemberName,
348350
createJSDocLink,
349351
updateJSDocLink,
350352
// lazily load factory members for JSDoc tags with similar structure
@@ -4391,29 +4393,48 @@ namespace ts {
43914393
}
43924394

43934395
// @api
4394-
function createJSDocNameReference(name: EntityName): JSDocNameReference {
4396+
function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference {
43954397
const node = createBaseNode<JSDocNameReference>(SyntaxKind.JSDocNameReference);
43964398
node.name = name;
43974399
return node;
43984400
}
43994401

44004402
// @api
4401-
function updateJSDocNameReference(node: JSDocNameReference, name: EntityName): JSDocNameReference {
4403+
function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference {
44024404
return node.name !== name
44034405
? update(createJSDocNameReference(name), node)
44044406
: node;
44054407
}
44064408

44074409
// @api
4408-
function createJSDocLink(name: EntityName | undefined, text: string): JSDocLink {
4410+
function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) {
4411+
const node = createBaseNode<JSDocMemberName>(SyntaxKind.JSDocMemberName);
4412+
node.left = left;
4413+
node.right = right;
4414+
node.transformFlags |=
4415+
propagateChildFlags(node.left) |
4416+
propagateChildFlags(node.right);
4417+
return node;
4418+
}
4419+
4420+
// @api
4421+
function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) {
4422+
return node.left !== left
4423+
|| node.right !== right
4424+
? update(createJSDocMemberName(left, right), node)
4425+
: node;
4426+
}
4427+
4428+
// @api
4429+
function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink {
44094430
const node = createBaseNode<JSDocLink>(SyntaxKind.JSDocLink);
44104431
node.name = name;
44114432
node.text = text;
44124433
return node;
44134434
}
44144435

44154436
// @api
4416-
function updateJSDocLink(node: JSDocLink, name: EntityName | undefined, text: string): JSDocLink {
4437+
function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink {
44174438
return node.name !== name
44184439
? update(createJSDocLink(name, text), node)
44194440
: node;

src/compiler/factory/nodeTests.ts

+4
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,10 @@ namespace ts {
770770
return node.kind === SyntaxKind.JSDocNameReference;
771771
}
772772

773+
export function isJSDocMemberName(node: Node): node is JSDocMemberName {
774+
return node.kind === SyntaxKind.JSDocMemberName;
775+
}
776+
773777
export function isJSDocLink(node: Node): node is JSDocLink {
774778
return node.kind === SyntaxKind.JSDocLink;
775779
}

src/compiler/parser.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ namespace ts {
489489
(typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray<JSDocText | JSDocLink> | undefined));
490490
case SyntaxKind.JSDocNameReference:
491491
return visitNode(cbNode, (node as JSDocNameReference).name);
492+
case SyntaxKind.JSDocMemberName:
493+
return visitNode(cbNode, (node as JSDocMemberName).left) ||
494+
visitNode(cbNode, (node as JSDocMemberName).right);
492495
case SyntaxKind.JSDocParameterTag:
493496
case SyntaxKind.JSDocPropertyTag:
494497
return visitNode(cbNode, (node as JSDocTag).tagName) ||
@@ -1424,6 +1427,10 @@ namespace ts {
14241427
return currentToken = scanner.reScanLessThanToken();
14251428
}
14261429

1430+
function reScanHashToken(): SyntaxKind {
1431+
return currentToken = scanner.reScanHashToken();
1432+
}
1433+
14271434
function scanJsxIdentifier(): SyntaxKind {
14281435
return currentToken = scanner.scanJsxIdentifier();
14291436
}
@@ -7338,7 +7345,13 @@ namespace ts {
73387345
export function parseJSDocNameReference(): JSDocNameReference {
73397346
const pos = getNodePos();
73407347
const hasBrace = parseOptional(SyntaxKind.OpenBraceToken);
7341-
const entityName = parseEntityName(/* allowReservedWords*/ false);
7348+
const p2 = getNodePos();
7349+
let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false);
7350+
while (token() === SyntaxKind.PrivateIdentifier) {
7351+
reScanHashToken(); // rescan #id as # id
7352+
nextTokenJSDoc(); // then skip the #
7353+
entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2);
7354+
}
73427355
if (hasBrace) {
73437356
parseExpectedJSDoc(SyntaxKind.CloseBraceToken);
73447357
}
@@ -7793,9 +7806,17 @@ namespace ts {
77937806
nextTokenJSDoc(); // start at token after link, then skip any whitespace
77947807
skipWhitespace();
77957808
// parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error
7796-
const name = tokenIsIdentifierOrKeyword(token())
7809+
const p2 = getNodePos();
7810+
let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token())
77977811
? parseEntityName(/*allowReservedWords*/ true)
77987812
: undefined;
7813+
if (name) {
7814+
while (token() === SyntaxKind.PrivateIdentifier) {
7815+
reScanHashToken(); // rescan #id as # id
7816+
nextTokenJSDoc(); // then skip the #
7817+
name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2);
7818+
}
7819+
}
77997820
const text = [];
78007821
while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) {
78017822
text.push(scanner.getTokenText());

src/compiler/scanner.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace ts {
4242
reScanJsxAttributeValue(): SyntaxKind;
4343
reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind;
4444
reScanLessThanToken(): SyntaxKind;
45+
reScanHashToken(): SyntaxKind;
4546
reScanQuestionToken(): SyntaxKind;
4647
reScanInvalidIdentifier(): SyntaxKind;
4748
scanJsxToken(): JsxTokenSyntaxKind;
@@ -220,7 +221,8 @@ namespace ts {
220221
"&&=": SyntaxKind.AmpersandAmpersandEqualsToken,
221222
"??=": SyntaxKind.QuestionQuestionEqualsToken,
222223
"@": SyntaxKind.AtToken,
223-
"`": SyntaxKind.BacktickToken
224+
"#": SyntaxKind.HashToken,
225+
"`": SyntaxKind.BacktickToken,
224226
}));
225227

226228
/*
@@ -981,6 +983,7 @@ namespace ts {
981983
reScanJsxAttributeValue,
982984
reScanJsxToken,
983985
reScanLessThanToken,
986+
reScanHashToken,
984987
reScanQuestionToken,
985988
reScanInvalidIdentifier,
986989
scanJsxToken,
@@ -2240,6 +2243,14 @@ namespace ts {
22402243
return token;
22412244
}
22422245

2246+
function reScanHashToken(): SyntaxKind {
2247+
if (token === SyntaxKind.PrivateIdentifier) {
2248+
pos = tokenPos + 1;
2249+
return token = SyntaxKind.HashToken;
2250+
}
2251+
return token;
2252+
}
2253+
22432254
function reScanQuestionToken(): SyntaxKind {
22442255
Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'");
22452256
pos = tokenPos + 1;
@@ -2426,6 +2437,8 @@ namespace ts {
24262437
return token = SyntaxKind.DotToken;
24272438
case CharacterCodes.backtick:
24282439
return token = SyntaxKind.BacktickToken;
2440+
case CharacterCodes.hash:
2441+
return token = SyntaxKind.HashToken;
24292442
case CharacterCodes.backslash:
24302443
pos--;
24312444
const extendedCookedChar = peekExtendedUnicodeEscape();

0 commit comments

Comments
 (0)