Skip to content

Conversation

Tanujkanti4441
Copy link
Contributor

@Tanujkanti4441 Tanujkanti4441 commented Jun 2, 2025

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

@eslint-github-bot eslint-github-bot bot added the feature This change adds a new feature to ESLint label Jun 2, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Jun 2, 2025
Copy link

netlify bot commented Jun 2, 2025

Deploy Preview for docs-eslint canceled.

Name Link
🔨 Latest commit 4213540
🔍 Latest deploy log https://app.netlify.com/projects/docs-eslint/deploys/687a0e80b60e5200086b15e9

@github-actions github-actions bot added the rule Relates to ESLint's core rules label Jun 2, 2025
@nzakas nzakas moved this from Needs Triage to Implementing in Triage Jun 10, 2025
Copy link

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.

@github-actions github-actions bot added the Stale label Jun 20, 2025
@Tanujkanti4441 Tanujkanti4441 marked this pull request as ready for review July 4, 2025 15:25
@Tanujkanti4441 Tanujkanti4441 requested a review from a team as a code owner July 4, 2025 15:25
@lumirlumir lumirlumir added the accepted There is consensus among the team that this change meets the criteria for inclusion label Jul 7, 2025
Copy link
Member

@lumirlumir lumirlumir left a 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 😄

* @private
*/
function isDefinitionFile(filename) {
return filename?.endsWith(".d.ts") ?? false;
Copy link
Member

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

Image

Comment on lines 144 to 145
},
},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
},
},
},
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.

eslint/lib/rules/no-var.js

Lines 202 to 203 in 28cc7ab

dialects: ["typescript", "javascript"],
language: "javascript",

* @returns {boolean} `true` if the identifier is only used in a type position
* @private
*/
function isTypeReference(node) {
Copy link
Member

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:

//------------------------------------------------------------------------------
// 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;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Contributor

@JoshuaKGoldberg JoshuaKGoldberg left a 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) {
Copy link
Contributor

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?

if (
parent.parent === "FunctionExpression" &&
parent.parent.decarators &&
parent.parent.decarators.length > 0
Copy link
Contributor

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.

return typeof filename === "string"
? /\.d\.(ts|mts|cts)$/u.test(filename)
: false;
}
Copy link
Contributor

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))
Copy link
Contributor

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.

Copy link
Contributor Author

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 &&
Copy link
Contributor

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

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?

},
{
filename: "foo.d.ts",
code: "class Foo {}\ndeclare class Bar {}\nexport {};",
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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?

Copy link
Contributor Author

@Tanujkanti4441 Tanujkanti4441 Jul 13, 2025

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" },
				},
			   ],
		  },
	],
},

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@nzakas nzakas requested a review from lumirlumir July 23, 2025 15:16
Copy link
Member

@nzakas nzakas left a 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",
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 this was added accidentally?

Copy link
Contributor Author

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.

@mdjermanovic
Copy link
Member

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.

Doesn't @typescript-eslint/scope-manager already track type references? So, when this rule is used with @typescript-eslint/parser, which uses @typescript-eslint/scope-manager as scope manager, type references should already be available to this rule in scopes?

@nzakas
Copy link
Member

nzakas commented Jul 24, 2025

@mdjermanovic Perhaps? 🤔 Do we want to assume that @typescript-eslint/scope-manager is always being used when we see TypeScript syntax? That seems like a shaky assumption moving forward.

@mdjermanovic
Copy link
Member

Based on this playground example, it seems that @typescript-eslint/scope-manager does track type references.

I'd say it's reasonable to expect that a TypeScript-aware parser provides a TypeScript-aware scope manager.

@nzakas
Copy link
Member

nzakas commented Jul 29, 2025

I suppose --- I'm just still uncomfortable with the added 500 lines of code to this rule. Is that really needed?

@mdjermanovic
Copy link
Member

I haven't reviewed the changes in detail, but I see that @typescript-eslint/scope-manager is used directly in the rule:

https://github.com/Tanujkanti4441/eslint/blob/421354008ddfbd023f3d9d038b0277dc19445af7/lib/rules/no-unused-vars.js#L13-L16

@Tanujkanti4441 if we would assume that, for TypeScript code, scopes have already been analyzed by @typescript-eslint/scope-manager or a compatible scope manager, would that, and how much, reduce the complexity and the number of lines added to this rule?

@Tanujkanti4441
Copy link
Contributor Author

I haven't reviewed the changes in detail, but I see that @typescript-eslint/scope-manager is used directly in the rule:

https://github.com/Tanujkanti4441/eslint/blob/421354008ddfbd023f3d9d038b0277dc19445af7/lib/rules/no-unused-vars.js#L13-L16

@Tanujkanti4441 if we would assume that, for TypeScript code, scopes have already been analyzed by @typescript-eslint/scope-manager or a compatible scope manager, would that, and how much, reduce the complexity and the number of lines added to this rule?

@mdjermanovic, I mostly tried to replicate the code of typescript-eslint's no-unused-vars rule and using of @typescript-eslint/scope-manager is also a part of that process and in most of the code we are marking the identifiers in ts specific nodes as used, so can we assume that we may not simplify this further with the use of @typescript-eslint/scope-manager? Or am I missing any detail here?

@nzakas
Copy link
Member

nzakas commented Aug 19, 2025

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion feature This change adds a new feature to ESLint rule Relates to ESLint's core rules
Projects
Status: Implementing
Development

Successfully merging this pull request may close these issues.

5 participants