What version of Oxlint are you using?
1.65.0 (also reproduces on 1.30.0)
What command did you run?
What does your .oxlintrc.json (or oxlint.config.ts) config file look like?
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
What version of Oxlint are you using?
1.65.0 (also reproduces on 1.30.0)
What command did you run?
What does your
.oxlintrc.json(oroxlint.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'sno-unused-varsreports 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
fooinside@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 injectregisterAs(...)-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
Actual
Expected
No diagnostics —
foois used in@Inject(foo.KEY).ESLint with
@typescript-eslint/parser+@typescript-eslint/eslint-plugindoes not report this — its scope analyser correctly counts the decorator expression as a use of the outer binding.Notes / related issues
eslint(no-unused-vars)false positive when namespace is shadowed by generic type parameter #17927 — closed namespace-shadow case (namespaceshadowed by generic).