Skip to content

Static class properties incorrectly parsed with instance scope instead of static scope #2144

@Haixing-Hu

Description

@Haixing-Hu

Bug Description

JSDoc incorrectly parses static class properties with instance scope (#) instead of static scope (.), resulting in wrong longnames and scope classification.

Expected Behavior

Static class properties should be parsed with:

  • Longname: ClassName.propertyName (using . separator)
  • Scope: static

Actual Behavior

Static class properties are parsed with:

  • Longname: ClassName#propertyName (using # separator)
  • Scope: instance

Minimal Test Case

Input JavaScript:

/** Sample class with static and instance properties. */
class TestClass {
    /** Instance property. */
    instanceProp = 'instance';

    /** Static property. */
    static staticProp = 'static';

    /** Static method (works correctly). */
    static staticMethod() {
        return 'static method';
    }
}

Expected JSDoc Output:

  • TestClass#instanceProp with scope instance
  • TestClass.staticProp with scope static ❌ (currently fails)
  • TestClass.staticMethod with scope static

Actual JSDoc Output:

  • TestClass#instanceProp with scope instance
  • TestClass#staticProp with scope instance ❌ (incorrect)
  • TestClass.staticMethod with scope static

Environment

  • JSDoc version: 4.x (latest)
  • Node.js version: 23.1.0
  • Operating System: MacOS 15.6.1 (24G90)

Root Cause Analysis

The bug is located in packages/jsdoc-parse/lib/parser.js at lines 366-373 in the astnodeToMemberof() function:

} else if (isClassProperty(node)) {
  doclet = this._getDocletById(node.enclosingScope.nodeId);

  if (!doclet) {
    result.memberof = LONGNAMES.ANONYMOUS + SCOPE.PUNC.INSTANCE;
  } else {
    result.memberof = doclet.longname + SCOPE.PUNC.INSTANCE;  // ❌ Always uses INSTANCE
  }
}

This code always uses SCOPE.PUNC.INSTANCE (#) for class properties, regardless of whether they are static or not.

Proposed Fix

The fix should check the node.static property, similar to how static methods are handled in lines 384-390:

} else if (isClassProperty(node)) {
  doclet = this._getDocletById(node.enclosingScope.nodeId);

  if (!doclet) {
    result.memberof = LONGNAMES.ANONYMOUS + (node.static === true ? SCOPE.PUNC.STATIC : SCOPE.PUNC.INSTANCE);
  } else {
    result.memberof = doclet.longname + (node.static === true ? SCOPE.PUNC.STATIC : SCOPE.PUNC.INSTANCE);
  }
}

Test Case

I can provide a complete test case that demonstrates this bug:

Test fixture: test/fixtures/staticproperties.js fixtures-staticproperties.js
Test spec: test/specs/documentation/staticproperties.js specs-staticproperties.js

The test shows that:

  • Static methods are parsed correctly ✅
  • Static getters/setters are parsed correctly ✅
  • Static properties are parsed incorrectly ❌

Impact

This bug affects:

  • Documentation generation for classes with static properties
  • Template engines that rely on correct scope classification
  • Any tooling that processes JSDoc output and expects correct longnames

Additional Notes

  • Static methods and static getters/setters work correctly
  • Only static properties (field declarations) are affected
  • The bug has been confirmed with a minimal reproducible test case

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions