consistent-existence-index-check
Enforce one form for checking whether an object has a property
Enforce one form for checking whether an object has a property — Object.hasOwn, in, or one of the hasOwnProperty spellings. This rule is part of eslint-plugin-conventions.
The name is about property existence, not array indexing. Despite
indexin the rule id, this rule never looks atindexOforincludes. It readskey in obj,obj.hasOwnProperty(key),Object.prototype.hasOwnProperty.call(obj, key)andObject.hasOwn(obj, key).
Quick Summary
| Aspect | Details |
|---|---|
| Severity | Warning (code quality) |
| Auto-Fix | ⚠️ One conversion only — see below |
| Category | Quality |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Consistency, and keeping prototype lookups out by default |
Rule Details
JavaScript has four ways to ask whether an object has a property, and they do not all answer the same question. This rule picks one and reports the others.
| Form | Answers for an inherited key | Looks the method up on obj |
|---|---|---|
Object.hasOwn(obj, key) | no | no |
Object.prototype.hasOwnProperty.call(obj, key) | no | no |
obj.hasOwnProperty(key) | no | yes |
key in obj | yes | no |
Why This Matters
| Issue | Impact | Solution |
|---|---|---|
| 🛡️ Prototype chain | in is true for inherited keys — the pollution direction | Default to Object.hasOwn |
| 💥 Dispatch | obj.hasOwnProperty throws on a null-prototype object | Never call it through obj |
| 🔄 Consistency | Four spellings of one question | Standardize on one |
Examples
The default preference is Object.hasOwn.
❌ Incorrect
key in obj; // answers true for INHERITED keys
obj.hasOwnProperty(key); // looks the method up ON obj
Object.prototype.hasOwnProperty.call(obj, key); // the long way round✅ Correct
Object.hasOwn(obj, key);What is and is not autofixed
Only Object.prototype.hasOwnProperty.call(obj, key) → Object.hasOwn(obj, key).
Those two ask the same question through the same dispatch, so the rewrite is safe.
Everything else is reported without a fix, because rewriting it would change what the code does:
inwalks the prototype chain and the own-property checks do not, so the two disagree on an inherited key.obj.hasOwnProperty(key)looks the method up onobj: it throws on a null-prototype object and calls whatever a shadowing own property points at.- a surplus or sequence-expression argument would change the argument list the call is handed.
Those reports also carry a different message, because the ordinary one
("Use <preferred> instead of <current>") is an instruction to perform exactly
the rewrite the rule just declined to make. Following it breaks working code:
Object.hasOwn is declared hasOwn(o: object, v: PropertyKey): boolean and is
not a type predicate, so rewriting 'on' in target loses the narrowing in
performed and the following target.on(...) stops typechecking; and at runtime
'on' in emitter is true while Object.hasOwn(emitter, 'on') is false,
because on lives on the prototype.
So a non-rewritable site reads:
Fix: Change this site by hand: "in" and "Object.hasOwn" disagree on an inherited keynaming what actually differs — an inherited key, method dispatch on the object, or the argument list — instead of ordering the rewrite.
In TypeScript, in also narrows
in is a type guard. Object.hasOwn is not, and TypeScript has no plan to make it
one — it cannot, because hasOwn is an ordinary call whose return type says nothing
about its arguments. So on a discriminated union the two forms are not
interchangeable even when they agree at runtime:
type A = { kind: 'a'; reason: string };
type B = { kind: 'b' };
declare const x: A | B;
if ('reason' in x) x.reason; // narrows to A — compiles
if (Object.hasOwn(x, 'reason')) x.reason; // TS2339: Property 'reason' does not exist on type 'A | B'Measured on one TypeScript codebase, every one of 48 reports of this rule sat on a
discriminated union, and following each of them would have replaced working narrowing
with a cast. A TypeScript project that uses in for narrowing wants
preferred: 'in', and that is not the same as wanting a prototype-chain lookup.
The rule never rewrites across this boundary on its own — see the autofix section above — so the reports are a prompt, not a silent change.
Options
preferred: 'Object.hasOwn' | 'in' | 'hasOwnProperty' — default 'Object.hasOwn'.
Set 'in' when a prototype-chain lookup is what the code means, or when the project
uses in to narrow unions in TypeScript. In plain JavaScript it is a choice worth
making deliberately; it is not a good default there, which is why it is no longer
one.
Configuration Examples
Basic Usage
{
rules: {
// Default: preferred is 'Object.hasOwn'
'conventions/consistent-existence-index-check': 'warn',
// Or state a different preference deliberately — a TypeScript project that
// narrows unions with `in` wants this one
// 'conventions/consistent-existence-index-check': ['warn', { preferred: 'in' }],
}
}Related Rules
prefer-object-has-own— eslint core, same direction
Further Reading
- Object.hasOwn() - MDN — why it was added
- in operator - MDN — including the prototype-chain behaviour
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Dynamic Variable References
Why: Static analysis cannot trace values stored in variables or passed through function parameters.
// ❌ NOT DETECTED - Value from variable
const value = externalSource();
processValue(value); // Variable origin not trackedMitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.
Imported Values
Why: When values come from imports, the rule cannot analyze their origin or construction.
// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not trackedMitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.
analytics-event-naming
Analytics event names (track/capture) must follow ANALYTICS_PHILOSOPHY.md principle 4: lowercase snake_case, category:object_action, fixed verb list
expiring-todo-comments
Add expiration conditions to TODO comments to prevent forgotten tasks. This rule is part of eslint-plugin-conventions and provides LLM-optimized error messa