Description
When "options": { "typeAware": true } is enabled, typescript/no-unnecessary-type-assertion incorrectly flags type assertions on generic function calls (notably querySelector) as unnecessary.
The rule resolves the generic type parameter from the as assertion target instead of using the declared default, causing it to conclude the expression "already has" the asserted type.
Minimal Reproduction
Repo: https://github.com/EurFelux/oxlint-type-assertion-bug
git clone https://github.com/EurFelux/oxlint-type-assertion-bug
cd oxlint-type-assertion-bug
npm install
npx oxlint --tsconfig tsconfig.json repro.ts
repro.ts:
// querySelector<E extends Element = Element>(selectors: string): E | null
// Default E = Element, so return type should be Element | null
// BUG: oxlint says "This expression already has the type 'HTMLCanvasElement | null'"
// Expected: no error (this assertion narrows Element | null → HTMLCanvasElement | null)
export const a = document.querySelector('.foo') as HTMLCanvasElement | null
.oxlintrc.json:
{
"options": { "typeAware": true },
"rules": { "typescript/no-unnecessary-type-assertion": "error" }
}
Expected Behavior
document.querySelector('.foo') returns Element | null (generic E defaults to Element). Asserting to HTMLCanvasElement | null is a legitimate narrowing and should not be flagged.
Actual Behavior
x typescript-eslint(no-unnecessary-type-assertion): This assertion is unnecessary since it does not change the type of the expression.
,-[repro.ts:1:49]
1 | export const a = document.querySelector('.foo') as HTMLCanvasElement | null
: ... This expression already has the type 'HTMLCanvasElement | null'
`----
Root Cause
querySelector has overloads:
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
For querySelector('.foo'), only the third overload matches. The default for E is Element, so the return type should be Element | null.
However, tsgolint appears to infer E from the as assertion target (e.g., E = HTMLCanvasElement), then concludes the assertion is a no-op. The assertion context should not influence the generic parameter resolution for the pre-assertion expression type.
Impact
- Using
--fix silently removes necessary type assertions, causing TypeScript compilation errors
- After removal,
querySelector('.foo') returns Element, losing access to .style, .focus(), .value, etc.
- Affects any generic function with a default type parameter, not just
querySelector
Environment
- oxlint: 1.56.0
- oxlint-tsgolint: latest
- TypeScript: 5.8.3
- OS: macOS Darwin 25.3.0
- Requires:
"options": { "typeAware": true }
Without typeAware: true, the rule works correctly.
Description
When
"options": { "typeAware": true }is enabled,typescript/no-unnecessary-type-assertionincorrectly flags type assertions on generic function calls (notablyquerySelector) as unnecessary.The rule resolves the generic type parameter from the
asassertion target instead of using the declared default, causing it to conclude the expression "already has" the asserted type.Minimal Reproduction
Repo: https://github.com/EurFelux/oxlint-type-assertion-bug
git clone https://github.com/EurFelux/oxlint-type-assertion-bug cd oxlint-type-assertion-bug npm install npx oxlint --tsconfig tsconfig.json repro.tsrepro.ts:
.oxlintrc.json:
{ "options": { "typeAware": true }, "rules": { "typescript/no-unnecessary-type-assertion": "error" } }Expected Behavior
document.querySelector('.foo')returnsElement | null(genericEdefaults toElement). Asserting toHTMLCanvasElement | nullis a legitimate narrowing and should not be flagged.Actual Behavior
Root Cause
querySelectorhas overloads:For
querySelector('.foo'), only the third overload matches. The default forEisElement, so the return type should beElement | null.However, tsgolint appears to infer
Efrom theasassertion target (e.g.,E = HTMLCanvasElement), then concludes the assertion is a no-op. The assertion context should not influence the generic parameter resolution for the pre-assertion expression type.Impact
--fixsilently removes necessary type assertions, causing TypeScript compilation errorsquerySelector('.foo')returnsElement, losing access to.style,.focus(),.value, etc.querySelectorEnvironment
"options": { "typeAware": true }Without
typeAware: true, the rule works correctly.