Add unqualified JSDoc member references (#44202)

* 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

* Remove type/object literal container check

Since they don't work anyway
This commit is contained in:
Nathan Shively-Sanders
2021-05-26 09:54:05 -07:00
committed by GitHub
parent 3ffa245f07
commit 459bd19941
2 changed files with 121 additions and 22 deletions

View File

@@ -39335,7 +39335,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));
if (container) {
return resolveJSDocMemberName(name, getSymbolOfNode(container));
}
}
return result;
}
else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) {
const links = getNodeLinks(name);
@@ -39374,25 +39381,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);
}
}