Skip to content
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

Fixed crashes when looking up symbols of jsdoc nodes in TS files #57110

Merged
merged 10 commits into from
Apr 4, 2024

Conversation

Andarist
Copy link
Contributor

@Andarist Andarist commented Jan 20, 2024

fixes #57094
closes #57108

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Jan 20, 2024
@@ -1837,6 +1837,9 @@ export namespace Core {
}
// falls through I guess
case SyntaxKind.Identifier:
if (node.flags & NodeFlags.JSDoc && !isInJSFile(node)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to additionally check if this is in an "any type tag" as those should be ignored whereas nodes in semantic~ jsdoc tags shouldn't (like in @see, @link, etc). I couldn't find any utility that would check something like this - I could create such and manually curate the list of those tags in it.

But there is another question: should those references in type-like tags always be ignored? Or should TS try to resolve corresponding TS types? There is always a risk that those won't match but in such a case displaying any (likely as a result of displaying errorType) could actually be OK.

An alternative fix for the crashes would be to ignore symbol-less declarations for now but I intentionally didn't go that route. It seemed like a bandaid for a deeper problem and I think handling this at a higher level would be better. I'm just not sure now what's the exact desired behavior.

cc @sandersn

Copy link
Member

Choose a reason for hiding this comment

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

I think you're asking whether find-all-refs should skip the references in @see et al? Is that right? Ideally the answer is No. Symbol resolution for those tags works outside the normal name resolution code, as I recall, via resolveJSDocMemberName. That function is called from getSymbolAtLocation, the usual entrypoint for public users of the API. You might want to call one of those two functions.

A utility to identify tags with semantics in Typescript would be useful, and I don't think it exists yet. scanner.ts does have a regex, but that's not useful for a parse tree. (and, besides, @deprecated should be included in the post-parser predicate, but not the scanner one.)

Please add a test either way.

//// data: string[];
////};

// @Filename: node_modules/use-query/package.json
Copy link
Member

Choose a reason for hiding this comment

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

This should be other to match the following index.d.ts
Although I don't know if package.json is really needed for either package.
And they might not need to be in separate packages for the problem to reproduce, right? It seems like this should even work inside a single file, but at least should repro between files of a single package.

Suggested change
// @Filename: node_modules/use-query/package.json
// @Filename: node_modules/other/package.json

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it should - the package split shouldn't be required for this to be reproduced at all, it was just faster for me to keep the package structure as-is (like in the original real-world code)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If u feel strongly about it, I can simplify this further or add an extra test case. I need to refresh myself on the whole situation (which I'll do soon since I plan to finish this PR) - but the file split itself might have been important for this (I'm not 100% sure about it right now though)

tests/baselines/reference/myFindAllRefs.baseline.jsonc Outdated Show resolved Hide resolved
////interface BottomSheetModalProps {
//// /**
//// * A scrollable node or normal view.
//// * @type null | (({ data: any }?) => any)
Copy link
Member

Choose a reason for hiding this comment

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

is null | the only difference between NoCrash2 and 1? If so, I don't see what value it adds above NoCrash1, especially since the new code doesn't reference Typescript types really.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test case exercises a braceless @type which was somewhat important when I started to reduce this and for some moments I couldn't figure out why the crash went away on certain reduction attempts. So I included that here to be extra sure that it's gonna be fixed in such a scenario too

@@ -1837,6 +1837,9 @@ export namespace Core {
}
// falls through I guess
case SyntaxKind.Identifier:
if (node.flags & NodeFlags.JSDoc && !isInJSFile(node)) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you're asking whether find-all-refs should skip the references in @see et al? Is that right? Ideally the answer is No. Symbol resolution for those tags works outside the normal name resolution code, as I recall, via resolveJSDocMemberName. That function is called from getSymbolAtLocation, the usual entrypoint for public users of the API. You might want to call one of those two functions.

A utility to identify tags with semantics in Typescript would be useful, and I don't think it exists yet. scanner.ts does have a regex, but that's not useful for a parse tree. (and, besides, @deprecated should be included in the post-parser predicate, but not the scanner one.)

Please add a test either way.

@Andarist Andarist requested a review from sandersn March 5, 2024 10:29
@@ -11027,6 +11027,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

if (isParameter(declaration)) {
if (!declaration.symbol) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I decided to include this here because it's just always possible to call checker.getTypeAtLocation or checker.getTypeAtLocation and those shouldn't crash or do something overly goofy - so the safest bet is to actually be on the safe side of things here + fine-tune existing call sites to those 2 if it's absolutely necessary to avoid getting errorType back when it's obvious that it will be returned back

Comment on lines 2116 to 2121
((node.parent.kind === SyntaxKind.PropertySignature && (node.parent as PropertySignature).name === node) ||
findAncestor(node, (n) => n.kind === SyntaxKind.Parameter))
) {
// if we'd request type at those locations we'd get `errorType` that displays confusingly as `any`
return false;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like how specific this is but I concluded that some condition like this is the best solution for #57108 . I'm not 100% sure if I'm missing some other node types that should be ignored here or not though. The worst case scenario quick info will continue to display any when it shouldn't so I feel it's fine. If you come up with any extra situations that should be handled here I can tweak this and add more tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that it's still good to display any here - and that's why I have to prevent the type request conditionally instead of just ignoring return values that are error types or smth like that

//// const test3 = { bar: '' };
////
//// type SomeObj = { bar: string; };
//// /** @type {SomeObj/*4*/} */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In TS files this is just ignored, in the sense that it doesn't impact types at all. So technically we could just avoid displaying anything here. I concluded though that it would actually be less useful and that it would be confusing - usually the user expects some feedback in form of a tooltip and not returning any info here could feel like something is off, like it's broken.

Since this isn't used for type-checking there is no guarantee that it "matches" the declaration below - I think it's an acceptable tradeoff. If somebody is writing JSDoc types in TS files then it's their responsibility to make them match.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @jakebailey since we were talking about this a few hours ago

Comment on lines +33 to +34
//// /** @type {(a/*8*/: string) => void} */
//// function test2(a: string) {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In situations like this and more, we could probably try to find the matching symbol and display that. That could be converted to a feature request - I don't think it's important enough for me to work on it though. Using both types of annotations in TS files shouldn't be common anyway.

@sandersn
Copy link
Member

Can you merge from main before I merge this? We've had a lot of baseline churn recently.

@jakebailey
Copy link
Member

We'll still not done, forgot one PR is left...

@Andarist
Copy link
Contributor Author

@sandersn done

@sandersn sandersn merged commit 69e7e57 into microsoft:main Apr 4, 2024
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
None yet
4 participants