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

Rule to mark types in JSDoc as used #858

Closed
Josh-Cena opened this issue Mar 27, 2022 · 15 comments
Closed

Rule to mark types in JSDoc as used #858

Josh-Cena opened this issue Mar 27, 2022 · 15 comments

Comments

@Josh-Cena
Copy link

Josh-Cena commented Mar 27, 2022

Motivation

Consider the following code:

import type { ComponentType } from "react";

/** @see {@link ComponentType} */
export type Foo = number;

TypeScript compiler understands that ComponentType is used in JSDoc and hence will not flag it as unused; TS-ESLint, on the other hand, reports @typescript-eslint/no-unused-vars, because it's unaware of JSDoc.

ESLint offers the markVariableAsUsed API that we can leverage to mark this type as used. eslint-plugin-react takes advantage of this to mark React as used when there's JSX. I don't know if it's portable to types, but hopefully it is.

Current behavior

Reports error.

Desired behavior

Should not give @typescript-eslint/no-unused-vars

Alternatives considered

This could also be an extension in TS-ESLint, but AFAIK they never handle JSDoc, so I decided to see if you would consider it first.


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

@brettz9
Copy link
Collaborator

brettz9 commented Mar 28, 2022

I personally prefer to use import() within the types, feeling the importing into code as kind of an anti-pattern, and as per #99 we don't want to continue the buggy support we have for this in no-undefined-types (the bugginess relates to the fact that our approach was to handle comment blocks one-by-one, but for proper unused variable support, it needs to process them all together).

However, as a separate rule, not on by default, I'd be open to a PR, but am not really inclined to work on this, as again, I kind of view it as an anti-pattern, albeit an approach supported by TypeScript and I think meriting accommodation to projects which want to use it.

@Josh-Cena
Copy link
Author

I see. I do prefer to split the marking types as used feature into a separate rule, because then it would be purely syntactical (detecting @see or @param or similar tags), so we can probably handle the comment blocks one-by-one. I get your point about anti patterns; frankly, I like to use import type in TS files because the JSDoc import() alternative looks quite... ugly. Furthermore, I'm actually unable to get this working:

/** @typedef {import('react').ComponentType} ComponentType */

/** @see {@link ComponentType} */
export type Foo = number;

ComponentType is resolved as any, even when import('react').ComponentType seems to be correctly resolved.

@brettz9
Copy link
Collaborator

brettz9 commented Mar 28, 2022

Does this work for you?

/** @typedef {typeof import(https://github.com/facebook/react/).ComponentType} ComponentType */

One admitted problem with the typedef approach though is that there is no means to mark it as local (as suggested per
microsoft/TypeScript#46011 (comment) ). It automatically gets exported as a type if generating a declaration file. Hopefully that will be fixed though, as it's one of two items I think particularly make TypeScript less than practical when trying to use plain JavaScript files (as some projects like ESLint want to do to keep the codebase accessible to more developers, and who do not want to curate a separate type declaration file as it is more likely to go out of sync when the types are not inline to the page) and thus holding me back from switching to TypeScript as our default mode (even for users without a TypeScript parser). @sandersn

@maribethb
Copy link

If I'm understanding this issue correctly, we would also like this feature as users of the Closure Compiler. We import types that are only used in JsDoc with

const {SomeType} = goog.requireType('Ns.SomeType');

but we have to add /* eslint-disable-next-line no-unused-vars */ above every one of these requireTypes. Since requireType is the Closure method for importing types specifically then I don't think this would be considered an antipattern.

I looked into doing this myself but we are moving towards es modules rather than closure modules so it wasn't worth implementing. But it would probably be useful to anyone using Closure Compiler.

@JohnLouderback
Copy link

I also would appreciate seeing this implemented. My use-case is also for @link. It works great with VS Code, but without marking the variables as used, other ES Lint plugin grumble.

@brettz9
Copy link
Collaborator

brettz9 commented Nov 2, 2022

As discovered in #507 (referenced also in #99), there was an eslint issue which pointed to an issue in marking variables used on program exit (i.e., as when examining comment nodes which are not attached to other AST). As per that issue, we might need to implement our own custom scope analysis in place of markVariableAsUsed.

@kraenhansen
Copy link
Contributor

kraenhansen commented Apr 28, 2023

@brettz9 do you think something changed upstream that might make it more feasible to implement now?
If you think this is possible and something you would take a PR for (even as a rule disabled by default) I'd love to take a stab at it 👍

@brettz9
Copy link
Collaborator

brettz9 commented Apr 28, 2023

@kraenhansen : While there were some architectural changes on markVariableAsUsed recently (moving it to the SourceCode object away from context), I think the same issue must still exist with that function. You might just search online about scope analysis and ESLint. I haven't worked very much at all with that, but it should I think be possible to do what you need with such analysis.

But if you were to do so, I think you'd have to disable/reimplement the TypeScript no-unused-vars rule since there'd be no common API to rely on. Ideally, this would be fixed at the ESLint level as markVariableAsUsed is intended for this purpose of sharing such data between rules, but maybe implementing it separately would be more stable given the fact that ESLint has been ambivalent about markVariableAsUsed (being as that rules typically don't influence other rules' behavior, and there can be a deterministic problem as I recall with the order in which they're applied).

@kraenhansen
Copy link
Contributor

@brettz9: Looking more at the code, it looks to me as if the inline {@link} tag isn't supported at all.

The parsed comment returned from @es-joy/jsdoccomment's parseComment function via

const getIndentAndJSDoc = function (lines, jsdocNode) {
const sourceLine = lines[jsdocNode.loc.start.line - 1];
const indnt = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
const jsdc = parseComment(jsdocNode, '');
return [
indnt, jsdc,
];
};

returns no tags for the following block comment:

/** This thing uses {@link foo} for something */

It looks like the tokenizers used by the underlying implementation is configurable:

https://github.com/es-joy/jsdoccomment/blob/08b258d7ede45abf58d354683faf1a7391dcf409/src/parseComment.js#L112-L118

... so I wonder if I'd have to implement that as well or if I'm missing something obvious 🤔

@brettz9
Copy link
Collaborator

brettz9 commented May 2, 2023

No, inline tags are indeed not tokenized. You'd have to get at them through the block or tag descriptions. Same with synonyms, @linkplain and @linkcode, or inline @tutorial.

@kraenhansen
Copy link
Contributor

kraenhansen commented May 2, 2023

Just to clarify: Would you consider that the responsibility of @es-joy/jsdoccomment or do you think it'd be reasonable to implement that in eslint-plugin-jsdoc (which would probably be the easiest for me).

@brettz9
Copy link
Collaborator

brettz9 commented May 2, 2023

@es-joy/jsdcomment is meant as a place for reusable utilities of interest to projects using JSDoc, but it should probably only add properties (e.g., an inlineTags array property with objects like {offset: [start: number, end: number], tag: string, text: string, path: string, linkStyle: "plain"|"markdown"|"pipe"|"space"}) since existing code depends on the current structure. (The linkStyle is following the four listed styles at both https://jsdoc.app/tags-inline-link.html and https://jsdoc.app/tags-inline-tutorial.html .) Another benefit of adding to jsdoccomment is we can build comment AST for it--so rules can target JSDoc blocks using specific inline tags or structures of inline tags.

@kraenhansen
Copy link
Contributor

kraenhansen commented May 2, 2023

@brettz9 I started working on this: #1058, so perhaps you can assign the issue to me?

Note that this is still draft and I have outstanding tasks (see the PR description): I wanted to get this up quickly in hope of some early feedback to increase the likelihood of an eventual merge.

@brettz9
Copy link
Collaborator

brettz9 commented May 10, 2023

A good deal of this issue has now been resolved by #1062 and #1065. In short, there is no new rule, but new config (disableReporting) can be used in no-undefined-types to disable the reporting of undefined tags (solely getting the marking of used variables from the rule, as though it were a separate rule). (Config was also added (markVariablesAsUsed) to allow disabling the used-variable marking.)

Note that a variable will still not be marked as used if it is not among the defined types; if someone wants to trust all used types (even potentially misspelled ones), let us know.

Thanks to @kraenhansen 's work, inline tags (such as @link) can now be parsed and are checked for this rule, so together with the above, the issue as reported should now be addressed.

However, as mentioned earlier, the ESLint API on which we still rely, markVariableAsUsed, is error prone (see #507 and eslint/eslint#9810 ), so it may not work to suppress used variable errors in all cases. I'll therefore keep the issue open in case someone can replace this API with a custom solution.

@brettz9
Copy link
Collaborator

brettz9 commented May 28, 2023

On second thought, since the remaining issue, markVariableAsUsed, is a bug with the ESLint API itself, I think we can close. Note that no-undefined-types is now off by default in recommended TypeScript configs, as TypeScript can handle this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants