From 362ef852ae4ba91559354bbc87145b8dc42dadf8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 21 May 2021 10:01:05 -0700 Subject: [PATCH 1/2] Add unqualified JSDoc member references This allows unqualified references like: ```ts class Zero { /** @param buddy Must be {@link D_HORSE} or {@link D_DOG}. */ deploy(buddy: number) { } static D_HORSE = 1 static D_DOG = 2 } ``` I surveyed @see and @link again to estimate how common this is. I found a little over 200 uses, which is around 2%. Sorted by frequency, this *is* the next feature on the list, along with the `module:` prefix. So I think this is about the right point to stop adding code. In this case, however, I liked most of the uses -- there were a lot of deprecated functions that referenced a function just below, where it would be wordy to qualify the name, but the reader would benefit from a link. Note that unqualified references do *not* work inside type or object literals. The code I ended up with is quite complicated and I didn't observe any uses in the wild. Fixes #43595 --- src/compiler/checker.ts | 33 ++++-- .../findAllReferencesLinkTag1.baseline.jsonc | 110 ++++++++++++++++-- 2 files changed, 121 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 010a30bf92789..21cd294f4aff7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39320,7 +39320,14 @@ namespace ts { const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); return symbol === unknownSymbol ? undefined : symbol; } - return resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name)); + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration, isObjectLiteralExpression, isTypeLiteralNode)); + if (container) { + return resolveJSDocMemberName(name, getSymbolOfNode(container)); + } + } + return result; } else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { const links = getNodeLinks(name); @@ -39359,25 +39366,27 @@ namespace ts { * 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) + * + * For unqualified names, a container K may be provided as a second argument. */ - function resolveJSDocMemberName(name: EntityName | JSDocMemberName): Symbol | undefined { + function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): 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 + // resolve static values first + const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; + let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!symbol && isIdentifier(name) && container) { + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); + } + if (symbol) { return symbol; } } - const left = resolveJSDocMemberName(name.left); + const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left); + const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; 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); + return getPropertyOfType(t, right); } } diff --git a/tests/baselines/reference/findAllReferencesLinkTag1.baseline.jsonc b/tests/baselines/reference/findAllReferencesLinkTag1.baseline.jsonc index 606f8e8150ab5..1752bcb37f42e 100644 --- a/tests/baselines/reference/findAllReferencesLinkTag1.baseline.jsonc +++ b/tests/baselines/reference/findAllReferencesLinkTag1.baseline.jsonc @@ -4,8 +4,8 @@ // n = 1 // static s() { } // /** -// * {@link m} -// * @see {m} +// * {@link [|m|]} +// * @see {[|m|]} // * {@link C.[|m|]} // * @see {C.[|m|]} // * {@link C#[|m|]} @@ -144,6 +144,24 @@ "isWriteAccess": true, "isDefinition": true }, + { + "textSpan": { + "start": 73, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 89, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, { "textSpan": { "start": 108, @@ -219,8 +237,8 @@ // */ // p() { } // /** -// * {@link n} -// * @see {n} +// * {@link [|n|]} +// * @see {[|n|]} // * {@link C.[|n|]} // * @see {C.[|n|]} // * {@link C#[|n|]} @@ -340,6 +358,24 @@ "isWriteAccess": true, "isDefinition": true }, + { + "textSpan": { + "start": 265, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 281, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, { "textSpan": { "start": 300, @@ -426,8 +462,8 @@ // */ // q() { } // /** -// * {@link s} -// * @see {s} +// * {@link [|s|]} +// * @see {[|s|]} // * {@link C.[|s|]} // * @see {C.[|s|]} // */ @@ -544,6 +580,24 @@ "isWriteAccess": true, "isDefinition": true }, + { + "textSpan": { + "start": 457, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 473, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, { "textSpan": { "start": 492, @@ -606,8 +660,8 @@ // [|a|]/*FIND ALL REFS*/() // b: 1 // /** -// * {@link a} -// * @see {a} +// * {@link [|a|]} +// * @see {[|a|]} // * {@link I.[|a|]} // * @see {I.[|a|]} // * {@link I#[|a|]} @@ -712,6 +766,24 @@ "isWriteAccess": false, "isDefinition": true }, + { + "textSpan": { + "start": 589, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 605, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, { "textSpan": { "start": 624, @@ -801,8 +873,8 @@ // */ // c() // /** -// * {@link b} -// * @see {b} +// * {@link [|b|]} +// * @see {[|b|]} // * {@link I.[|b|]} // * @see {I.[|b|]} // */ @@ -890,6 +962,24 @@ "isWriteAccess": false, "isDefinition": true }, + { + "textSpan": { + "start": 720, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 736, + "length": 1 + }, + "fileName": "/tests/cases/fourslash/findAllReferencesLinkTag1.ts", + "isWriteAccess": false, + "isDefinition": false + }, { "textSpan": { "start": 755, From 8d25eea837e5d0c15486b9e80917991a89496b49 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 21 May 2021 10:30:02 -0700 Subject: [PATCH 2/2] Remove type/object literal container check Since they don't work anyway --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 21cd294f4aff7..12724d0ba8e37 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39322,7 +39322,7 @@ namespace ts { } const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name)); if (!result && isJSDoc) { - const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration, isObjectLiteralExpression, isTypeLiteralNode)); + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); if (container) { return resolveJSDocMemberName(name, getSymbolOfNode(container)); }