Skip to content

v1.108.246 - a class field initializer no longer donates members to its class

Choose a tag to compare

@jgravelle jgravelle released this 05 Aug 21:44

JS/TS phantom methods

class Host {
  handlers = { onDone(){} };
  real(){}
}

reported Host.onDone as a method - a member Host does not declare.

The tree-sitter JS grammar spells object-literal shorthand and a real class method with the same node type (method_definition), and the only discriminator is the parent; a field initializer was not treated as a boundary. A free function_declaration inside an arrow initializer reached the same wrong answer by a second, independent route: the container flag that promotes a function to a method survived into the initializer.

The sharpest consequence was not the spurious name. When a phantom collided with a real method, the disambiguator handed ~1 to the phantom and ~2 to the real one - a real symbol's id depended on the phantom existing. Distinct names mean the disambiguator no longer runs.

Members of a class field initializer now nest under the field: Host.onDone becomes Host.handlers.onDone, and T.innerFn becomes T.cb.innerFn with kind corrected to function. Affects javascript, typescript and tsx.

An object literal inside a FUNCTION is deliberately untouched. pluginCreator.prepare is ordinary lexical nesting, the same shape Python already emits for Host.real.inner. Reclassifying it would move ~190 real symbols in Next.js alone.

No re-index required. A parser change that deletes symbols forces a full re-parse; this one deletes nothing - the same symbols return under corrected names, and the corrected shape occurs zero times across 2,670 files of NestJS/Next.js/React. Existing indexes are unaffected.

Packaging

The source distribution carried vscode-extension/node_modules/ (128 entries), the extension's compiled out/, and a built .vsix. Only the ROOT .gitignore reaches hatchling, so a nested one hid all three from every review that looked at the working tree instead of the artifact.

1005 entries to 872, 3.86 MB to 3.23 MB. The extension source still ships - an sdist that cannot rebuild what the repo builds is a different defect.

No runtime behaviour changed in this half; the wheel is unaffected.

Tests

7032 passed, 7 skipped. test_js_class_field_phantom_methods.py (17) and test_sdist_exclusions.py (9), both proven non-vacuous against the pre-fix code.