feat(typescript): take up codeanalyzer-typescript 1.5.0 — bindings, UTF-8 spans, per-facet ids - #370
Merged
Merged
Conversation
1.4.0 (cants#182) adds TS_IMPORTS / TS_RE_EXPORTS edges, :TSModule.exports_json, :TSCallable.parameters_json and TS_READS_CONFIG_UNRESOLVED. get_imports, get_exports, get_method_parameters and get_unresolved_config_reads answer over Neo4j on such a graph instead of refusing; parameters and exports also reach TSCallable.parameters and TSModule.exports on a rebuilt node, so the symbol table and the accessors cannot disagree. The model change comes first and is not optional: every TS model is extra="forbid", so 1.4.0's new TSImport/TSExport.resolved_module makes TSAnalysis.model_validate_json raise outright on a 1.4.0 analysis.json (12 errors on the sample app). The field is added to both models and the four checked-in analysis.json fixtures are regenerated at the pin. A graph emitted before 1.4.0 is still attachable — the floor stays 1.3.0 — and those four still refuse there, because [] would read as "imports nothing" / "takes no parameters" / "every read resolved". The decision is measured from the application's own data, one statement asking whether any of the three carriers is present, never from analyzer_version: the pattern leg 3 established for Java's port probe, so a re-emitted graph starts answering with no change here. Three carriers OR-ed because each is null when empty on its own node and an emitter writes all or none. Cached, and paid only on first use of one of the four. parameters_json and exports_json are decoded strictly: a malformed value raises rather than collapsing to [], which would recreate the ambiguous empty this closes. Imports are the one lossy half and say so — the edge folds every binding between a module pair into sorted sets, so an alias, an import_kind and a span are not recoverable, and the emitter drops a relative specifier that resolved to no emitted module. Both directions are pinned. Offline, one stub answers the probe and one does not, over identical rows. Over the write gate the sample app's real parameters and import bindings are compared to the sources and across backends. Over the re-emitted superset-frontend reference graph every expectation is derived from the graph at run time — the count the aggregate implies, the encoded export lists, the edge count. Still open upstream and untouched by 1.4.0: cants#177 (declaration-merge id collision), #179 (:TSCallable.code one line short, still pinned by four strict xfails), #180, #181.
1.5.0 closes the four ceilings the 1.4.0 uptake documented, so this is mostly deletion — but one of them is breaking and needs SDK code. TSSpan.bytes are UTF-8 byte offsets now, on every node at every level (cants#179). They were UTF-16 code units, which Python sliced as code points and the Neo4j projection sliced as bytes: three units that agreed only on ASCII, which is why the mess hid. _Spanned.code now decodes the module's UTF-8 bytes the way JCompilationUnit.slice() already did — encoded once per module in the same validator that threads the source, not at all when the file is ASCII. Measured on the regenerated fixture: 28 spanned nodes live in non-ASCII modules and 14 of them would have been sliced wrong by the old character index. A graph-rebuilt node is unaffected; it carries its own code with a character-counted span and no _source_bytes. Everything else is removal, each verified rather than assumed: :TSCallable.code is no longer one line short over Neo4j. The four xfail(strict=True) marks pinned to that were run against a 1.5.0 graph first and all four XPASS(strict) — which is what strict was there to make loud — so the marks are gone and the tests are ordinary parity assertions again. Declaration merging mints one id per facet (cants#177). On the re-emitted superset graph no node carries two declaration labels any more, and 7 signatures name two nodes apiece (the second suffixed #interface / #type). This is a behaviour gain, not just a defect closing: both facets are now served — a class X + interface X pair answers from get_classes AND get_interfaces, where before the facet the last-written kind did not name was lost. The two live tests that asserted the collision now assert the split, both still deriving every expectation from the graph. The kind-alongside-label predicates stay: they are what keeps an accessor to its own facet now that a signature is no longer unique. cants#180 and #181 had no SDK workaround to remove. schema_version is still 2.0.0 across a change the analyzer marks breaking, so the contract version is not a signal that one landed. Said plainly in the changelog rather than left for the next reader to find.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #368. Takes the pin from 1.3.0 to 1.5.0 in two steps, and closes four upstream issues this SDK had filed.
The correctness fix
1.5.0 ships
fix(schema)!: span.bytes are UTF-8 byte offsets(codeanalyzer-typescript#179). The SDK sliced those offsets as Python characters, so on any non-ASCII file the text drifted after the first multi-byte character.Measured against the regenerated fixture: 28 spanned nodes live in non-ASCII modules, and 14 of them were sliced wrong —
src/models.Robot.describereturned'scribe(): string {…'instead of'describe(): string {…'.Fixed by mirroring Java's
JCompilationUnit.slice()rather than inventing a second approach:_source_bytesis encoded once per module and leftNonewhen the source is ASCII, so an ASCII file pays nothing and takes the plain-index path. The new test asserts the drift count is non-zero, so it fails loudly rather than going vacuous if the corpus ever turns ASCII.Behaviour gained, not just defects closed
codeanalyzer-typescript#177 (declaration merging mints one id per facet) is a capability, not a bug fix. On the re-emitted graph, zero nodes now carry two declaration labels and 7 signatures name two nodes each. All 14 facets are reachable:
class X+interface Xanswers from bothget_classes()andget_interfaces();const X(arrow) +interface Xresolves as a callable and appears inget_interfaces(). Previously the facet the last-writtenkinddidn't name was simply lost.Consequence worth stating: a signature is no longer unique per node, which is why the
kind-alongside-label predicates stay — they are what keeps an accessor to its own facet.codeanalyzer-typescript#179 — the four strict xfails came back
XPASS(strict)→ FAILED, which is whatstrict=Trueexists to produce. They are ordinary parity assertions now.The binding layer (1.4.0)
get_imports,get_exports,get_method_parametersover Neo4j, and the config-read pair now answer. The refusal path is kept, measured from the data: one probe OR-ing three existence checks over the application's own id prefixes, with no version literal anywhere in the decision. Three carriers because each is null-when-empty individually — an exports-only probe would false-refuse a real graph whose app has no export statements. Both directions pinned offline.parameters_json/exports_jsondecode through a helper that raises on malformed input rather than yielding[], so a decode failure cannot become the ambiguous empty this closes.Two facts worth recording
schema_versionstayed2.0.0across a!-marked breaking change. The contract version is not a signal that a breaking change landed — verified on the parsed payload, and called out in the CHANGELOG.extra="forbid"caught 1.4.0's newresolved_modulefield as 12 loud validation errors instead of silently dropping data. 1.5.0 needed no further model change, and itsschema.neo4j.jsondiff against 1.4.0 is empty.Verification
Full gate 1577 passed, 370 skipped, 85.08%. TypeScript live against the re-emitted 1.5.0 graph with every gate satisfied: 592 passed, 1 xfailed, 0 failed, 0 skipped. The remaining xfail is
CALLSITE_FACETS_ABSENT, a different and still-real gap.Re-emitted 7692: 1,841 modules unchanged, callables 11,085 → 11,116, classes 207 → 212 — that +31/+5 is #177 splitting facets into their own nodes.