Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/bright-conditions-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@flint.fyi/rule-data": patch
"@flint.fyi/ts": patch
---

Add the checker-backed `ts/unnecessaryConditions` rule to report conditions with statically known outcomes.
2 changes: 1 addition & 1 deletion packages/e2e/tests/directives/directives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("directives", () => {

<red>✖ Found <bold>4 reports</bold> across <bold>3 files</bold>.</fg>
<red></fg>
<dim>Finished in <time> on 4 files with 139 rules.</fg>
<dim>Finished in <time> on 4 files with 140 rules.</fg>
<dim></fg>"
`);
// cspell:enable
Expand Down
4 changes: 4 additions & 0 deletions packages/e2e/tests/typescript/fixtures/src/with-issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export function debug(value: unknown): void {
export function calculate(a: number, b: number): number {
return a + b;
}

if (true) {
console.log("known condition");
}
7 changes: 4 additions & 3 deletions packages/e2e/tests/typescript/typescript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ describe("typescript", () => {
"<dim>Linting with <cyan><bold>flint.config.ts</bold></fg><dim>...</fg>

<underline><cwd>/fixtures/src/with-issues.ts</underline>
<dim> 2:2</fg> Debugger statements should not be used in production code. <yellow>ts/debuggerStatements</fg>
<dim> 2:2</fg> Debugger statements should not be used in production code. <yellow>ts/debuggerStatements</fg>
<dim> 10:5</fg> This condition is always truthy. <yellow>ts/unnecessaryConditions</fg>

<red>✖ Found <bold>1 report</bold> across <bold>1 file</bold>.</fg>
<red>✖ Found <bold>2 reports</bold> across <bold>1 file</bold>.</fg>
<red></fg>
<dim>Finished in <time> on 2 files with 138 rules.</fg>
<dim>Finished in <time> on 2 files with 139 rules.</fg>
<dim></fg>"
`);
});
Expand Down
3 changes: 2 additions & 1 deletion packages/rule-data/src/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -28762,7 +28762,8 @@
"flint": {
"name": "unnecessaryConditions",
"plugin": "ts",
"preset": "logical"
"preset": "logical",
"status": "implemented"
},
"oxlint": [
{
Expand Down
75 changes: 75 additions & 0 deletions packages/site/src/content/docs/rules/ts/unnecessaryConditions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
description: "Reports conditions whose outcomes are statically known."
title: "unnecessaryConditions"
topic: "rules"
---

import { TabItem, Tabs } from "@astrojs/starlight/components";
import { RuleEquivalents } from "~/components/RuleEquivalents";
import RuleSummary from "~/components/RuleSummary.astro";

<RuleSummary plugin="ts" rule="unnecessaryConditions" />

This checker-backed rule uses TypeScript's flow-narrowed types to find truthiness, nullability, comparisons, switch cases, optional chains, logical assignments, and built-in Array predicate callbacks whose results are known.
It requires `strictNullChecks` so that the checker can distinguish nullish values from other values.

## Examples

<Tabs>
<TabItem label="❌ Incorrect">

```ts
declare const label: string;

if (label) {
console.log(label);
}
```

```ts
const settings = { theme: "dark" };
const theme = settings?.theme;
```

</TabItem>
<TabItem label="✅ Correct">

```ts
declare const label: string | undefined;

if (label) {
console.log(label);
}
```

```ts
declare const settings: { theme: string } | undefined;
const theme = settings?.theme;
```

</TabItem>
</Tabs>

Optional-chain diagnostics offer a suggestion that removes only the unnecessary `?.` token.
The rule does not apply an unconditional fix because preserving evaluation and control-flow intent can require a larger edit.

## Options

This rule has no options.

## When Not To Use It

Do not use this rule in projects that cannot enable `strictNullChecks` or whose generated declarations do not accurately describe runtime nullability.
The rule intentionally does not inspect arbitrary assertion calls or user-defined type-predicate calls, and it does not infer that unrelated object interfaces can never overlap.
When `noUncheckedIndexedAccess` is disabled, it conservatively retains checks on array elements, dynamic tuple indexes, and index-signature accesses because those reads can produce `undefined` at runtime.

## Rule Equivalents

<RuleEquivalents pluginId="ts" ruleId="unnecessaryConditions" />

## Further Reading

- [MDN: Truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy)
- [MDN: Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)
- [MDN: Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
- [TypeScript: Narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
2 changes: 2 additions & 0 deletions packages/ts/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ import unnecessaryBooleanCasts from "./rules/unnecessaryBooleanCasts.ts";
import unnecessaryCatches from "./rules/unnecessaryCatches.ts";
import unnecessaryComparisons from "./rules/unnecessaryComparisons.ts";
import unnecessaryConcatenation from "./rules/unnecessaryConcatenation.ts";
import unnecessaryConditions from "./rules/unnecessaryConditions.ts";
import unnecessaryEscapes from "./rules/unnecessaryEscapes.ts";
import unnecessaryMathClamps from "./rules/unnecessaryMathClamps.ts";
import unnecessaryNumericFractions from "./rules/unnecessaryNumericFractions.ts";
Expand Down Expand Up @@ -604,6 +605,7 @@ export const ts = createPlugin({
unnecessaryCatches,
unnecessaryComparisons,
unnecessaryConcatenation,
unnecessaryConditions,
unnecessaryEscapes,
unnecessaryMathClamps,
unnecessaryNumericFractions,
Expand Down
Loading
Loading