Accessor Recursion

ID

javascript.accessor_recursion

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

classes, control-flow, reliability

Description

Reports a getter that reads its own property on this, or a setter that writes it. The access does not reach a field — it re-enters the accessor.

Which direction recurses depends on which accessor is written, and the rule follows that exactly. A read inside a setter (set x(v) { if (this.x !== v) … }) calls the getter, and a write inside a getter calls the setter; both are ordinary code. A compound assignment or a ++ counts as both a read and a write, so it recurses in either accessor.

The private and public spellings are kept apart: get x() { return this.#x; } is the standard backing-field form and is not reported, because #x and x are compared as different names.

An access inside a nested function is not reported — that function rebinds this to its own call site. An arrow does not rebind it and is walked through. Object-literal accessors recurse in exactly the same way and are reported too.

Rationale

// Bad
class Point {
  get x() {
    return this.x;
  }

  set x(value) {
    this.x = value;
  }
}
  • Reading this.x inside get x() invokes get x(), which reads this.x again. Nothing terminates the loop.

  • The failure is RangeError: Maximum call stack size exceeded, and it names no property. The trace is a few thousand identical frames.

  • The mistake is one rename away at all times: return this._x; is correct, and dropping the underscore while cleaning up, while converting a field to an accessor, or while merging turns a working class into one that cannot be constructed.

Remediation

Read and write the backing field, not the property the accessor is named after.

// Good
class Point {
  #x = 0;

  get x() {
    return this.#x;
  }

  set x(value) {
    this.#x = value;
  }
}

© 2026 Xygeni. All rights reserved