prefer-ts-extras-set-has
Prefer setHas from ts-extras over set.has(...).
setHas(...) improves narrowing when checking membership in typed sets.
Targeted pattern scopeโ
This rule focuses on direct set.has(value) calls that can be migrated to setHas(set, value) with deterministic fixes.
set.has(value)call sites whose receiver resolves exclusively toSet/ReadonlySetbranches and can usesetHas(set, value).
Alias indirection, mixed unions with non-Set has methods (for example Set | Map), wrapper helpers, and non-canonical call shapes are excluded to keep setHas(set, value) migrations safe.
What this rule reportsโ
This rule reports set.has(value) call sites when setHas(set, value) is the intended replacement.
set.has(value)call sites that can safely usesetHas(set, value)without changing union-branch semantics.
Why this rule existsโ
setHas provides a canonical membership-check helper with strong narrowing behavior for typed sets.
- Set membership guards have one helper style.
- Candidate values can narrow after guard checks.
- Native/helper mixing is removed from set-heavy code.
โ Incorrectโ
const hasMonitor = monitorIds.has(candidateId);
โ Correctโ
const hasMonitor = setHas(monitorIds, candidateId);
Behavior and migration notesโ
- Runtime semantics match native
Set.prototype.has. - Equality semantics still follow SameValueZero.
- Narrowing benefits are strongest when checking unknown values against literal/unioned set members.
- If your codebase intentionally needs plain-boolean membership checks (without exposing type-guard narrowing at every call site), wrap
setHasonce in a local helper and call the helper consistently.
Additional examplesโ
โ Incorrect โ Additional exampleโ
if (allowed.has(input)) {
use(input);
}
โ Correct โ Additional exampleโ
if (setHas(allowed, input)) {
use(input);
}
โ Correct โ Repository-wide usageโ
const known = setHas(codes, candidate);
ESLint flat config exampleโ
import typefest from "eslint-plugin-typefest";
export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-ts-extras-set-has": "error",
},
},
];
Optionsโ
This rule accepts a single options object:
type PreferTsExtrasSetHasOptions = {
/**
* Controls how union receivers are matched.
*
* - "allBranches": report only when every union branch is Set-like.
* - "anyBranch": report when at least one union branch is Set-like.
*
* @default "allBranches"
*/
unionBranchMatchingMode?: "allBranches" | "anyBranch";
};
Default configuration:
{
unionBranchMatchingMode: "allBranches",
}
unionBranchMatchingMode: "allBranches" (default)โ
Mixed unions are ignored to prevent noisy reports and unsafe replacements:
declare const values: Set<number> | Map<number, number>;
const hasValue = values.has(2); // โ
Not reported by default
unionBranchMatchingMode: "anyBranch"โ
Mixed unions with Set-like branches are reported:
declare const values: Set<number> | Map<number, number>;
const hasValue = values.has(2); // โ Reported
When this option is enabled, mixed-union reports are intentionally suggestion-free (no autofix and no suggested replacement), because setHas(values, value) may not type-check until you narrow to a Set-like branch first.
Flat config example:
import typefest from "eslint-plugin-typefest";
export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-ts-extras-set-has": [
"error",
{ unionBranchMatchingMode: "anyBranch" },
],
},
},
];
When not to use itโ
Disable this rule if native .has() calls are required by local conventions.
You may also disable this rule in implementation-only layers that intentionally centralize setHas behind a boolean helper wrapper for stability and readability.
Package documentationโ
ts-extras package documentation:
Source file: source/set-has.ts
/**
A strongly-typed version of `Set#has()` that properly acts as a type guard.
When `setHas` returns `true`, the type is narrowed to the set's element type.
When it returns `false`, the type remains unchanged (i.e., `unknown` stays `unknown`).
It was [rejected](https://github.com/microsoft/TypeScript/issues/42641#issuecomment-774168319) from being done in TypeScript itself.
@example
```
import {setHas} from 'ts-extras';
const values = ['a', 'b', 'c'] as const;
const valueSet = new Set(values);
const valueToCheck: unknown = 'a';
if (setHas(valueSet, valueToCheck)) {
// We now know that the value is of type `typeof values[number]`.
} else {
// The value remains `unknown`.
}
```
@category Improved builtin
@category Type guard
*/
Rule catalog ID: R033