Accessor Uses Expected Field

ID

javascript.accessor_uses_expected_field

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

JavaScript

Tags

code-style, copy-paste, field

Description

Reports a class get x() whose single return does not read the field that backs x, or a set x(v) whose single assignment does not write it — while that field exists in the class.

The backing field is looked for under the two spellings JavaScript actually uses: the private field #x and the underscore convention _x. The property’s own name cannot back it — a class cannot hold both a get x() and a field x, and a getter that read this.x would recurse forever. If neither spelling is declared in the class, nothing is reported: an accessor with no same-named field is a computed property, not a mistake.

The body must be exactly one statement — a return for a getter, an assignment for a setter. Anywhere in that one statement counts as touching the field, so return this.#x * this.scale; and return this.#x ?? this.#fallback(); are both fine.

Object-literal accessors are out of scope: an object literal has no private fields, so there is no declared backing field to compare against.

Rationale

// Bad
class Point {
  #x = 0;
  #y = 0;

  get x() { return this.#x; }
  get y() { return this.#x; }   // reports the wrong coordinate
  set y(value) { this.#x = value; }
}
  • An accessor pair is written by copying the one above it and editing the name. Editing the name and forgetting the field is the single most common way this goes wrong, and the result reads correctly at every call site: point.y is a valid expression that returns a number.

  • Nothing else catches it. The class compiles, the property exists, and the type of the value is right — only its meaning is wrong. #y quietly becomes write-only, or never written at all, so even a test that round-trips the property can pass.

  • get and set are syntax in JavaScript, not a naming convention, so the rule is not guessing that this method was meant as an accessor. That is what lets it be strict: an accessor for y that never mentions #y, in a class that declares #y, has no innocent reading.

The two guards are what keep it quiet elsewhere. Requiring the field to exist excludes every computed property — get fullName() { return this.#first + " " + this.#last; } has no #fullName to miss. Requiring a single statement excludes the accessors that validate, cache or convert, where the field may legitimately be touched on a line other than the one being read.

Remediation

Point the accessor at its own field.

// Good
class Point {
  #x = 0;
  #y = 0;

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

If the property really is derived from another field, drop the same-named field or rename it, so the class no longer claims a backing store it does not use.

© 2026 Xygeni. All rights reserved