-
Notifications
You must be signed in to change notification settings - Fork 663
[api-extractor] Fix incorrect declaration references for local symbols within namespaces #3566
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
Conversation
| // If that doesn't work, try to find a parent symbol via the node tree. As far as we can tell, | ||
| // this logic is only needed for local symbols within namespaces. For example: | ||
| // | ||
| // ``` | ||
| // export namespace n { | ||
| // type SomeType = number; | ||
| // export function someFunction(): SomeType { return 5; } | ||
| // } | ||
| // ``` | ||
| // | ||
| // In the example above, `SomeType` doesn't have a parent symbol per the TS internal API above, | ||
| // but its reference still needs to be qualified with the parent reference for `n`. | ||
| const grandParent: ts.Node | undefined = declaration?.parent?.parent; | ||
| if (grandParent && ts.isModuleDeclaration(grandParent)) { | ||
| const grandParentSymbol: ts.Symbol | undefined = TypeScriptInternals.tryGetSymbolForDeclaration( | ||
| grandParent, | ||
| this._typeChecker | ||
| ); | ||
| if (grandParentSymbol) { | ||
| return this._symbolToDeclarationReference( | ||
| grandParentSymbol, | ||
| grandParentSymbol.flags, | ||
| /*includeModuleSymbols*/ true | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new logic that fixes the bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code looks okay, but these sorts of changes sometimes cause a regression due to some obscure edge case. Let's publish this as as separate PATCH release from your other fix, and then we can get some large monorepos to try it.
| }, | ||
| { | ||
| "kind": "Class", | ||
| "canonicalReference": "api-extractor-scenarios!n1.SomeClass1:class", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This canonical reference should be api-extractor-scenarios!n1~SomeClass1:class, but this bug is tackled in #3552. It's a separate code path, because this reference is generated by api-extractor-model itself from the .api.json, whereas the canonical references in tokens are generated by DeclarationReferenceGenerator from the TypeScript nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also an existing bug that this item is included at all in the doc model, as it's unexported from the n1 namespace and thus not consumable. This bug is mentioned at #3552 (comment), and is due to the fact that items within namespaces don't have their own CollectorEntitys, and the CollectorEntity is the unit at which we currently track whether an item is exported/consumable or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have simply never tested this n1.SomeClass1 case. It's not our typical coding pattern. 🙂
| export namespace n1 { | ||
| // (undocumented) | ||
| export namespace n2 { | ||
| // (undocumented) | ||
| export class SomeClass3 { | ||
| } | ||
| } | ||
| // (undocumented) | ||
| export class SomeClass1 { | ||
| } | ||
| // (undocumented) | ||
| export class SomeClass2 extends SomeClass1 { | ||
| } | ||
| {}; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also an instance of the pre-existing bug mentioned in #3552 (comment). Notice the empty {} and the fact that SomeClass1 is exported here even though it's not actually exported in the original source file.
There are a few instances of this bug throughout this PR, JFYI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export { } is a special .d.ts directive that disables a deprecated semantics for modules. (I don't remember the details offhand, but I think it was something like, if NO declaration is exported, then ALL declarations are implicitly exported?)
Here's your test case in the playground -- on the .d.ts tab, export { } appears inside n1.
{}; without export is definitely a bug.
|
Thanks for making this fix BTW! The code change is small, but the concepts aren't, and the extra test cases are appreciated. 😎 |
…erences # Conflicts: # build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.json # build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.md # build-tests/api-extractor-scenarios/etc/apiItemKinds/rollup.d.ts # build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml
|
This was published as API Extractor |
[api-extractor] Fix incorrect declaration references for local symbols within namespaces
Summary
Fixes the bug mentioned at #3552 (comment). Suppose you have the following code:
When building the excerpt tokens for
someFunction's return type, API Extractor generates a canonical reference ID forSomeType. Today, it generates the wrong reference:my-package!SomeType:type. This is incorrect because the canonical reference needs to be qualified with the namespacen. The reference should bemy-package!n~SomeType:type.The reason it generates the wrong reference is because the logic in
DeclarationReferenceGeneratoris incomplete. The APIsymbol.parentisundefinedforSomeType, presumably because it's unexported from the namespace (not entirely sure). Regardless, the solution implemented in this PR is to traverse the node tree to find the appropriate parent symbol.Details
No additional details.
How it was tested
I added a new api-extractor-scenarios test directory called "referenceTokens" that I used to validate that the appropriate reference tokens were generated for various symbols.
I also cleaned up / extended some other api-extractor-scenarios test directories that I encountered throughout this PR.