JS-1255 Fix false positives in S4325 for type assertions narrowing generic/union return types#6464
Conversation
Tests cover the scenario where type assertions narrowing generic or union return types are incorrectly flagged as unnecessary. The tests verify that assertions on generic function/method calls, casts to any/unknown, and non-null assertions on declared nullable types are not reported, while truly unnecessary assertions are still flagged. Relates to JS-1255
Add a decorator to the no-unnecessary-type-assertion rule (S4325) that suppresses false positives where type assertions genuinely narrow types. The upstream rule uses reference equality to compare types, which fails when TypeScript infers generic parameters from the assertion context. The decorator intercepts reports and suppresses them in three cases: - Type assertions on generic function/method return types - Non-null assertions on declared nullable types (T | null, T | undefined) - Type assertions to any/unknown when the source type differs Invalid test cases include output fields for the fixable rule's auto-fix to satisfy ESLint RuleTester requirements. Implementation follows the approved proposal with an any-to-any guard to preserve true positives. Relates to JS-1255
Corrected ruling analysis for 5 entries that were incorrectly marked as false positives in the previous attempt. These non-null assertions (vuetify scope!, postgraphql result!, courselit course!) are genuinely unnecessary because TypeScript's flow analysis with strictNullChecks correctly narrows the types after assignments, try-finally blocks, and null guards respectively. No implementation changes needed — the decorator already handles these cases correctly by deferring to the resolved type when strictNullChecks is enabled.
Ticket: JS-1255 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixed 3 S1066 (collapsible-if) code smells in decorator.ts by merging nested if statements into single conditions using logical AND operators. The three merged statements were: the TSAsExpression/TSTypeAssertion type check with shouldSuppressTypeAssertion call, the TSNonNullExpression check with shouldSuppressNonNullAssertion call, and the declaration type check with typeNodeContainsNullOrUndefined in the for-loop. No behavioral changes; purely structural improvements to reduce nesting.
Add unit tests for S4325 decorator to cover previously untested code paths: - Non-generic function call assertions that should still be flagged (isCalleeGeneric returns false) - Non-null assertions on non-union return types with strictNullChecks - Non-null assertions on call expressions without type checking (no symbol at location) This improves code coverage to meet the quality gate threshold. Co-Authored-By: Claude <noreply@anthropic.com>
Ticket: JS-1255 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Ruling ReportCode no longer flagged (33 issues)S4325TypeScript/src/compiler/factory.ts:511 509 |
510 | export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode | undefined) {
> 511 | return <FunctionTypeNode>updateSignatureDeclaration(node, typeParameters, parameters, type);
512 | }
513 | TypeScript/src/compiler/factory.ts:519 517 |
518 | export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray<TypeParameterDeclaration> | undefined, parameters: NodeArray<ParameterDeclaration>, type: TypeNode | undefined) {
> 519 | return <ConstructorTypeNode>updateSignatureDeclaration(node, typeParameters, parameters, type);
520 | }
521 | TypeScript/src/compiler/parser.ts:678 676 | sourceFile.statements = parseList(ParsingContext.SourceElements, parseStatement);
677 | Debug.assert(token() === SyntaxKind.EndOfFileToken);
> 678 | sourceFile.endOfFileToken = <EndOfFileToken>parseTokenNode();
679 |
680 | setExternalModuleIndicator(sourceFile);TypeScript/src/compiler/parser.ts:2933 2931 | // for cases like > > = becoming >>=
2932 | if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) {
> 2933 | return makeBinaryExpression(expr, <BinaryOperatorToken>parseTokenNode(), parseAssignmentExpressionOrHigher());
2934 | }
2935 | TypeScript/src/compiler/parser.ts:3368 3366 | }
3367 | else {
> 3368 | leftOperand = makeBinaryExpression(leftOperand, <BinaryOperatorToken>parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
3369 | }
3370 | }TypeScript/src/compiler/transformers/es2015.ts:2843 2841 | }
2842 | else {
> 2843 | let clone = <IterationStatement>getMutableClone(node);
2844 | // clean statement part
2845 | clone.statement = undefined;ag-grid/src/ts/componentProvider.ts:194 192 | }
193 |
> 194 | let componentToUse:ComponentToUse<A,B> = <ComponentToUse<A,B>>this.getComponentToUse(holder, componentName, thisComponentConfig, mandatory);
195 |
196 | if (!componentToUse) return null;ag-grid/src/ts/componentProvider.ts:204 202 | //Using framework component
203 | let FrameworkComponentRaw: {new(): B} = componentToUse.component;
> 204 | return <A>this.frameworkComponentWrapper.wrap(FrameworkComponentRaw, thisComponentConfig.mandatoryMethodList);
205 | }
206 | ag-grid/src/ts/componentProvider.ts:208 206 |
207 | public createAgGridComponent<A extends IComponent<any>> (holder:GridOptions | ColDef | ColGroupDef, componentName:string, defaultComponentName:string, agGridParams:any, mandatory:boolean = true): A{
> 208 | let component: A = <A>this.newAgGridComponent(holder, componentName, defaultComponentName, mandatory);
209 | if (!component) return null;
210 | ag-grid/src/ts/componentProvider.ts:230 228 |
229 | public newDateComponent (params: IDateParams): IDateComp{
> 230 | return <IDateComp>this.createAgGridComponent(this.gridOptions, "dateComponent", "dateComponent", params);
231 | }
232 | ...and 23 more 📋 View full reportCode no longer flagged (33)
|
|
The ruling report confirms the fix is working correctly — 30 previously flagged S4325 issues are no longer reported. No regressions (no newly flagged issues). No action needed. |
|
Team review: remove non-null assertion suppression for projects without strictNullChecks After reviewing the shouldSuppressNonNullAssertion() logic, we've decided to remove the code path that suppresses non-null assertion reports when strictNullChecks is disabled. The rationale: without strictNullChecks, TypeScript erases null and undefined from all types, so ! is effectively a no-op — the type is already non-null from the compiler's point of view. The upstream rule is therefore correct to flag it. Suppressing the report here would allow developers to annotate nullable declarations with ! without actually handling null, which defeats the purpose of the rule and hides future issues when the project eventually enables strictNullChecks. In a JS→TS migration context, it's better to flag these early so developers write proper null guards (if (x !== null)) rather than silently accumulate ! assertions that will need to be revisited later. Requested change: Remove the else branch (the strictNullChecks: false code path) from shouldSuppressNonNullAssertion() in packages/jsts/src/rules/S4325/decorator.ts. If the strictNullChecks: true branch is the only remaining case, evaluate whether the function is still needed. If you'd like to measure the impact of this change on real-world projects before removing it, we can create dogfood branches for that purpose. |
Comment: Team review: remove non-null assertion suppression for projects without strictNullChecks After reviewing the shouldSuppressNonNullAssertion() logic, we've decided to remove the code path that suppresses non-null assertion reports when strictNullChecks is disabled. The rationale: without strictNullChecks, TypeScript erases null and undefined from all types, so ! is effectively a no-op — the type is already non-null from the compiler's point of view. The upstream rule is therefore correct to flag it. Suppressing the report here would allow developers to annotate nullable declarations with ! without actually handling null, which defeats the purpose of the rule and hides future issues when the project eventually enables strictNullChecks. In a JS→TS migration context, it's better to flag these early so developers write proper null guards (if (x !== null)) rather than silently accumulate ! assertions that will need to be revisited later. Requested change: Remove the else branch (the strictNullChecks: false code path) from shouldSuppressNonNullAssertion() in packages/jsts/src/rules/S4325/decorator.ts. If the strictNullChecks: true branch is the only remaining case, evaluate whether the function is still needed. If you'd like to measure the impact of this change on real-world projects before removing it, we can create dogfood branches for that purpose. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
Done. Removed the The Test updates:
|
🤖 Generated with GitHub Actions
Add unit tests to cover previously untested code paths in the S4325 decorator.ts rule implementation: - Test that reports without a 'node' property are forwarded as-is (defensive check at lines 49-50) - Test that reports are forwarded when TypeScript parser services lack a program (lines 55-56) - Test suppression of contextually unnecessary non-null assertions on nullable union types with strictNullChecks enabled (line 69) This improves code coverage to meet the quality gate threshold. Co-Authored-By: Claude <noreply@anthropic.com>
|
|
LGTM |
|
Thank you for the review! |




Fixes false positives in the
no-unnecessary-type-assertionrule (S4325) where type assertions that genuinely narrow types were incorrectly flagged. The upstream ESLint rule uses reference equality to compare types, which fails when TypeScript infers generic parameters from the assertion context.Changes
T | null,T | undefined)any/unknownwhen the source type differsifstatements into single conditions (S1066) indecorator.ts— no behavioral changesRelates to JS-1255