Skip to content

linter: no-unused-vars false positive when constructor parameter property shadows an imported identifier used in its own decorator #22622

@utrumo

Description

@utrumo

What version of Oxlint are you using?

1.65.0 (also reproduces on 1.30.0)

What command did you run?

oxlint test.ts

What does your .oxlintrc.json (or oxlint.config.ts) config file look like?

// default (no config file)

What happened?

When a TypeScript constructor parameter property has the same name as an outer binding (import or const), and that outer binding is used in the parameter's own decorator expression (e.g. @Inject(foo.KEY)), oxlint's no-unused-vars reports the outer binding as declared but never used.

This is a false positive. Parameter decorators are evaluated at class-decoration time in the enclosing class scope, before any parameter binding exists in the parameter scope. So foo inside @Inject(foo.KEY) resolves to the outer binding, not to the same-named parameter property.

This pattern is extremely common in NestJS code, where @Inject(someConfig.KEY) is used to inject registerAs(...)-style typed configs and the constructor parameter property is typically named after the config. The false positive forces a churn-y workaround: either alias the import (import { foo as _foo }) or rename the parameter property.

Minimal reproduction

const foo = { KEY: 'token' };

declare function Inject(token: unknown): ParameterDecorator;

export class C {
  constructor(@Inject(foo.KEY) private readonly foo: number) {}
}

Actual

! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'.
   ,-[test.ts:1:7]
 1 | const foo = { KEY: 'token' };
   :       ^|^
   :        `-- 'foo' is declared here

Expected

No diagnostics — foo is used in @Inject(foo.KEY).

ESLint with @typescript-eslint/parser + @typescript-eslint/eslint-plugin does not report this — its scope analyser correctly counts the decorator expression as a use of the outer binding.

Notes / related issues

Metadata

Metadata

Assignees

Labels

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions