Repository navigation
Description
Is your feature request related to a problem? Please describe.
PyClassAttribute (codeanalyzer/schema/py_schema.py:409) carries start_line and end_line and no columns at all. Every other span-bearing model either carries a real Span (start/end as [line, col] plus bytes:[lo,hi]) or carries flat line and column positions that byte offsets can be derived from.
That makes :PyAttribute the single exception to the flattened _SPAN on the Neo4j projection, landed in #202/#203 (PR #206): every other label that spreads _SPAN carries all six properties — start_line, end_line, start_column, end_column, start_byte, end_byte — and :PyAttribute carries only the line pair. With no columns there is nothing to derive byte offsets from, and emitting a fabricated column 0 would advertise a sliceable span that does not slice.
So a consumer that wants a class attribute's exact text cannot get it the way it gets every other node's: module.source[span.bytes]. It has to fall back to line-granular slicing, and it has to know that this one label is special.
Spec: docs/design/specs/2026-09-10-neo4j-json-parity.md, decision 3 (amended during implementation with this finding). Decision log: .claude/SCHEMA_DECISIONS.md, the 2026-09-10 section.
Describe the solution you'd like
PyClassAttributecarriesstart_columnandend_columnalongside the existing line pair.:PyAttributespreads_SPANwith all six properties, byte offsets computed bybyte_offsets()exactly as:PyVariablealready does.- The
:PyAttributeexception branch intest_every_span_bearing_node_carries_all_six_span_propertiesis deleted, not widened. - The spec amendment and the decision-log bullet are updated to record that the exception is closed.
Describe alternatives you've considered
Not stated in the original issue.
Additional context
Scope boundary
In scope: PyClassAttribute gains column positions, and :PyAttribute then spreads _SPAN like every other label so the exception disappears from both projections and from the conformance test.
Out of scope: converting PyClassAttribute to a real Span, and the same question for any other flat-position model. Adding the two columns is the smaller change and is enough to close the parity gap; the Span conversion is a separate call about whether flat positions should exist in the model at all.
This is a JSON-contract change — it adds fields to the payload — which is why #206 deliberately left it out.
Caveats and known risks
- Sibling analyzers: check whether codeanalyzer-java and codeanalyzer-typescript already spell attribute columns, and adopt their names verbatim if so. A term coined twice is permanently wrong.
- Additive to the payload, so the monotonicity gate is unaffected, but
schema_versionstays at2.0.0under the 2026-09-07 hold — the field is detectable by presence and consumers gate on the analyzer version floor. - The ast node for an attribute assignment target is not always where the interesting text is (an annotated assignment with a default spans further than the target), so the column pair has to be taken from the same node the line pair already comes from, or the two disagree.
- Whatever populates the line pair in
symbol_table_buildermay not have the column readily in hand; the fix is only as clean as that call site.
Definition of done
-
PyClassAttributecarriesstart_column/end_column, populated from the same ast node as the line pair. -
:PyAttributecarries all six_SPANproperties. - A test slices
:PyModule.sourceby an attribute's byte offsets and gets that attribute's own text back — the same proof the other labels already have. - The exception branch is removed from the conformance test and the test still passes.
- Both
schema.neo4j.jsonsnapshots regenerated. - Spec decision 3 and the
.claude/SCHEMA_DECISIONS.mdbullet updated to say the exception is closed.
Activity