-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
feat: support TypeScript syntax in no-unused-vars
#19812
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
base: main
Are you sure you want to change the base?
feat: support TypeScript syntax in no-unused-vars
#19812
Conversation
✅ Deploy Preview for docs-eslint canceled.
|
Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update. |
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.
Nice work! I’ve left a few comments about some missing parts 😄
lib/rules/no-unused-vars.js
Outdated
* @private | ||
*/ | ||
function isDefinitionFile(filename) { | ||
return filename?.endsWith(".d.ts") ?? false; |
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.
Can we add a logic to the isDefinitionFile
helper function to also check for .d.mts
and .d.cts
files?
Here is a reference for it:
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#new-file-extensions
}, | ||
}, |
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.
}, | |
}, | |
}, | |
dialects: ["typescript", "javascript"], | |
language: "javascript", | |
}, |
Could you add dialects
and language
fields to the meta
property? As far as I know, the newly updated rules that support TypeScript use these fields.
Lines 202 to 203 in 28cc7ab
dialects: ["typescript", "javascript"], | |
language: "javascript", |
lib/rules/no-unused-vars.js
Outdated
* @returns {boolean} `true` if the identifier is only used in a type position | ||
* @private | ||
*/ | ||
function isTypeReference(node) { |
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.
Can we move the isDefinitionFile
and isTypeReference
helper functions under the Helpers
comment?
Here is a reference for it:
eslint/lib/rules/accessor-pairs.js
Lines 31 to 60 in 28cc7ab
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
/** | |
* Checks whether or not the given lists represent the equal tokens in the same order. | |
* Tokens are compared by their properties, not by instance. | |
* @param {Token[]} left First list of tokens. | |
* @param {Token[]} right Second list of tokens. | |
* @returns {boolean} `true` if the lists have same tokens. | |
*/ | |
function areEqualTokenLists(left, right) { | |
if (left.length !== right.length) { | |
return false; | |
} | |
for (let i = 0; i < left.length; i++) { | |
const leftToken = left[i], | |
rightToken = right[i]; | |
if ( | |
leftToken.type !== rightToken.type || | |
leftToken.value !== rightToken.value | |
) { | |
return false; | |
} | |
} | |
return 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.
Done!
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.
Very exciting!
* @throws {Error} If `value` is null or undefined. | ||
* @returns {ASTNode} The provided node, if it is not null or undefined. | ||
*/ | ||
function nullThrows(value, message) { |
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 an interesting piece to copy over. The typescript-eslint project style is to very aggressively handle edge cases exposed by types. But it does sometimes go overboard and seem to require extra logic in places that don't need it (typescript-eslint/typescript-eslint#10077).
Some of the places that nullThrows
are also unnecessary. For example, the node.parent
passed to nullThrows
in the Program
selector will be unnecessary once typescript-eslint/typescript-eslint#11334 is resolved.
The ESLint project doesn't generally go that extra step. So, raising for other reviewers: should we just omit nullThrows
& NullThrowsReasons
in this PR?
lib/rules/no-unused-vars.js
Outdated
if ( | ||
parent.parent === "FunctionExpression" && | ||
parent.parent.decarators && | ||
parent.parent.decarators.length > 0 |
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.
[Bug] decorators
, not decarators
. The fact that this isn't failing any tests belies a lack of coverage.
lib/rules/no-unused-vars.js
Outdated
return typeof filename === "string" | ||
? /\.d\.(ts|mts|cts)$/u.test(filename) | ||
: false; | ||
} |
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.
[Refactor] This is very similar to the isDeclareInDTSFile
logic in no-shadow.js
, added in #19565. Which also had a review comment about matching all definition file extensions: #19565 (comment)
I think that that's a sign there needs to be a shared utility function.
if (nodes.includes(scope.block)) { | ||
if ( | ||
(Array.isArray(nodes) && nodes.includes(scope.block)) || | ||
(nodes instanceof Set && nodes.has(scope.block)) |
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.
[Docs] The JSDoc for this function still says nodes
can only ever be an array.
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.
Done!
@@ -755,7 +926,7 @@ module.exports = { | |||
|
|||
// skip variables marked with markVariableAsUsed() | |||
if ( | |||
!config.reportUsedIgnorePattern && | |||
// !config.reportUsedIgnorePattern && |
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.
?
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.
Forgot to remove this one, but with this line following test is falling:
{
code: `
export enum Foo {
_A,
}
`,
options: [
{ reportUsedIgnorePattern: true, varsIgnorePattern: "_" },
],
},
And without this line, I think reportUsedIgnorePattern: true
won't report the markedAsUsedVariables with ignored pattern when they are used, so should we report them in this condition?
tests/lib/rules/no-unused-vars.js
Outdated
}, | ||
{ | ||
filename: "foo.d.ts", | ||
code: "class Foo {}\ndeclare class Bar {}\nexport {};", |
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.
[Question] Is there a reason this uses a single line string instead of a multi-line template string? The original tseslint tests use the latter.
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.
Actually, while writing suggestions, the column number is not matching properly for me, so I tried this. Please suggest if I can do something better here?
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 don't follow, what do you mean by the column number not matching properly?
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.
Like here column: 10
must be same for both code and output but somehow I was getting error on multiline code.
{
code: "import { Nullable } from 'nullable';\nconst a: string = 'hello';\nconsole.log(a);",
errors: [
{
column: 10,
data: {
action: "defined",
additional: "",
varName: "Nullable",
},
line: 1,
messageId: "unusedVar",
suggestions: [
{
output: "\nconst a: string = 'hello';\nconsole.log(a);",
messageId: "removeVar",
data: { varName: "Nullable" },
},
],
},
],
},
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 passes on my machine. Maybe there's something with indentation that's misaligned?
Reference commit: 59ba196
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.
Done!
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 adds over 500 lines to the current rule, and I'm not sure this is the right place to add all of that logic. I think ideally, reference tracking would end up in eslint-scope and not be implemented inside of a single rule.
I'd like to propose we hold off on this and think about whether or not we want to add type reference tracking into eslint-scope. @eslint/eslint-tsc
@@ -117,6 +117,7 @@ | |||
"@humanwhocodes/retry": "^0.4.2", | |||
"@types/estree": "^1.0.6", | |||
"@types/json-schema": "^7.0.15", | |||
"@typescript-eslint/scope-manager": "^8.34.1", |
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 this was added accidentally?
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.
No, it is an intentional change. It is used in the rule.
Doesn't |
@mdjermanovic Perhaps? 🤔 Do we want to assume that |
Based on this playground example, it seems that I'd say it's reasonable to expect that a TypeScript-aware parser provides a TypeScript-aware scope manager. |
I suppose --- I'm just still uncomfortable with the added 500 lines of code to this rule. Is that really needed? |
I haven't reviewed the changes in detail, but I see that @Tanujkanti4441 if we would assume that, for TypeScript code, scopes have already been analyzed by |
@mdjermanovic, I mostly tried to replicate the code of typescript-eslint's |
@Tanujkanti4441 the overall question is about the amount of code this adds to the rule. It seems like if variable reference tracking is already implemented in the typescript-eslint scope manager, that it shouldn't take 500 lines of code to track TypeScript identifiers. |
Prerequisites checklist
What is the purpose of this pull request? (put an "X" next to an item)
[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[x] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:
What changes did you make? (Give an overview)
Added typescript syntax support in
no-unused-vars
rule.Is there anything you'd like reviewers to focus on?
Refs #19173
Moving forward #19580