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
1 change: 1 addition & 0 deletions packages/comparisons/src/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -16321,6 +16321,7 @@
"name": "setHasExistenceChecks",
"plugin": "ts",
"preset": "stylistic",
"status": "implemented",
"strictness": "strict"
},
"oxlint": [
Expand Down
102 changes: 102 additions & 0 deletions packages/site/src/content/docs/rules/ts/setHasExistenceChecks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
description: "Prefer `Set.has()` over `Array.includes()` for repeated existence checks."
title: "setHasExistenceChecks"
topic: "rules"
---

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

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

When an array is only used for existence checks via `.includes()`, converting it to a `Set` and using `.has()` provides better performance.
`Array.includes()` has O(n) time complexity, while `Set.has()` has O(1) time complexity.

This rule reports when:

- A `const` variable is initialized with an array (literal, `Array()`, `Array.from()`, `Array.of()`, or array methods like `.filter()`, `.map()`, etc.)
- All usages of that variable are `.includes()` calls with exactly one argument
- Either there are multiple `.includes()` calls, or a single call is inside a loop or function

## Examples

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

```ts
const items = [1, 2, 3];
function check(value: number) {
return items.includes(value);
}
```

```ts
const items = [1, 2, 3];
items.includes(1);
items.includes(2);
```

```ts
const items = [1, 2, 3];
for (const value of values) {
items.includes(value);
}
```

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

```ts
const items = new Set([1, 2, 3]);
function check(value: number) {
return items.has(value);
}
```

```ts
const items = new Set([1, 2, 3]);
items.has(1);
items.has(2);
```

```ts
// Single call, not in loop/function - acceptable
const items = [1, 2, 3];
items.includes(1);
```

```ts
// Has other usages besides .includes()
const items = [1, 2, 3];
items.push(4);
items.includes(1);
```

```ts
// Exported arrays are not reported
export const items = [1, 2, 3];
items.includes(1);
items.includes(2);
```

</TabItem>
</Tabs>

## Options

This rule is not configurable.

## When Not To Use It

If the array is very small (a few elements), the performance difference between `Array.includes()` and `Set.has()` is negligible.
You may also disable this rule if you need to preserve the array type for compatibility with APIs that expect arrays.

## Further Reading

- [MDN: Set.prototype.has()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has)
- [MDN: Array.prototype.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)

## Equivalents in Other Linters

<RuleEquivalents pluginId="ts" ruleId="setHasExistenceChecks" />
2 changes: 2 additions & 0 deletions packages/ts/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ import regexUnnecessaryEscapes from "./rules/regexUnnecessaryEscapes.ts";
import returnAssignments from "./rules/returnAssignments.ts";
import selfAssignments from "./rules/selfAssignments.ts";
import sequences from "./rules/sequences.ts";
import setHasExistenceChecks from "./rules/setHasExistenceChecks.ts";
import setterReturns from "./rules/setterReturns.ts";
import shadowedRestrictedNames from "./rules/shadowedRestrictedNames.ts";
import singleVariableDeclarations from "./rules/singleVariableDeclarations.ts";
Expand Down Expand Up @@ -488,6 +489,7 @@ export const ts = createPlugin({
returnAssignments,
selfAssignments,
sequences,
setHasExistenceChecks,
setterReturns,
shadowedRestrictedNames,
singleVariableDeclarations,
Expand Down
208 changes: 208 additions & 0 deletions packages/ts/src/rules/setHasExistenceChecks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { ruleTester } from "./ruleTester.ts";
import rule from "./setHasExistenceChecks.ts";

ruleTester.describe(rule, {
invalid: [
{
code: `
const items = [1, 2, 3];
function check(value: number) {
return items.includes(value);
}
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
function check(value: number) {
return items.includes(value);
}
`,
},
{
code: `
const items = [1, 2, 3];
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = [1, 2, 3];
for (const value of [1, 2]) {
items.includes(value);
}
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
for (const value of [1, 2]) {
items.includes(value);
}
`,
},
{
code: `
const items = Array(1, 2, 3);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = Array(1, 2, 3);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = new Array(1, 2, 3);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = new Array(1, 2, 3);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = Array.from([1, 2, 3]);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = Array.from([1, 2, 3]);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = Array.of(1, 2, 3);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = Array.of(1, 2, 3);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = [1, 2, 3].filter(Boolean);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = [1, 2, 3].filter(Boolean);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = [1, 2, 3].map((value) => value * 2);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = [1, 2, 3].map((value) => value * 2);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
{
code: `
const items = [1, 2, 3];
const check = (value: number) => items.includes(value);
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
const check = (value: number) => items.includes(value);
`,
},
{
code: `
const items = [1, 2, 3];
while (true) {
items.includes(1);
}
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
while (true) {
items.includes(1);
}
`,
},
{
code: `
const items = [1, 2, 3];
for (let index = 0; index < 10; index++) {
items.includes(index);
}
`,
snapshot: `
const items = [1, 2, 3];
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
for (let index = 0; index < 10; index++) {
items.includes(index);
}
`,
},
{
code: `
const items = [1, 2, 3].slice(0, 2);
items.includes(1);
items.includes(2);
`,
snapshot: `
const items = [1, 2, 3].slice(0, 2);
~~~~~
This array is only used for existence checks. Prefer \`Set\` with \`.has()\` for better performance.
items.includes(1);
items.includes(2);
`,
},
],
valid: [
`const items = [1, 2, 3]; items.includes(1);`,
`const items = [1, 2, 3]; items.push(4); items.includes(1);`,
`export const items = [1, 2, 3]; items.includes(1); items.includes(2);`,
`const items = [1, 2, 3]; items?.includes(1); items?.includes(2);`,
`const items = new Set([1, 2, 3]); items.has(1);`,
`const items = [1, 2, 3]; items.includes(1, 0);`,
`const items = [1, 2, 3]; const copy = items;`,
`const items = [1, 2, 3]; console.log(items);`,
`const items = [1, 2, 3]; items.length;`,
`const items = [1, 2, 3];`,
`let items = [1, 2, 3]; items.includes(1); items.includes(2);`,
],
});
Loading
Loading