Skip to content

feat(typescript): add tsc_only toggle and surface synthesized anonymous callables#179

Merged
rahlk merged 2 commits into
mainfrom
fix/issue-174
Jun 27, 2026
Merged

feat(typescript): add tsc_only toggle and surface synthesized anonymous callables#179
rahlk merged 2 commits into
mainfrom
fix/issue-174

Conversation

@rahlk

@rahlk rahlk commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Make the TypeScript backend's call-graph intent explicit and stop riding the obsolete --call-graph-provider both default, and materialize the anonymous callbacks the Jelly resolver produces so call-graph edges to them don't dangle.

Motivation and Context

Per #174, the cants (codeanalyzer-typescript) binary's --call-graph-provider <tsc|jelly|both> machinery is obsolete — the resolver-based tsc path is the only supported direction. The SDK never passed the flag, so it implicitly rode the binary default. This surfaces an explicit tsc_only knob (--tsc-only) instead.

Separately, the Jelly resolver emits call edges to first-party anonymous callbacks that the symbol table never names (the canonicalizer returns null for them). Those edges previously pointed at nodes that didn't exist. This adds a slim TSSynthesizedCallable (mirroring TSExternalSymbol) keyed by the synthesized <enclosing-signature>:<line:col> so the edges resolve. The tsc resolver emits an empty map.

Closes #174.

How Has This Been Tested?

  • Local run against a TypeScript project with tsc_only=True (passes --tsc-only) and default (Jelly) — call graph builds with no dangling anonymous/external nodes.
  • Neo4j backend returns :AnonymousCallable nodes scoped to the application's modules.

Breaking Changes

None. tsc_only defaults to False (binary chooses its default); synthesized_callables defaults to an empty map, so existing call-graph consumers are unaffected. TSBackend gains TSCodeAnalyzerConfig as the first union member while remaining backward-compatible with a bare CodeAnalyzerConfig.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the Codellm-Devkit Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • New config: TSCodeAnalyzerConfig(tsc_only: bool = False) in cldk/analysis/commons/backend_config.py, added to the TSBackend union and threaded through TypeScriptAnalysis via getattr(..., "tsc_only", False).
  • New model: TSSynthesizedCallable (+ TSApplication.synthesized_callables), with get_synthesized_callables() added to the TSAnalysisBackend ABC and implemented for the in-process and Neo4j backends (:AnonymousCallable node + R.synthesized reconstructor).
  • codeanalyzer-typescript pinned 0.4.0 -> 0.4.3 in both dependencies and [tool.backend-versions]; --tsc-only requires >= 0.4.2.

rahlk added 2 commits June 27, 2026 14:54
…us callables

Drop reliance on the obsolete `--call-graph-provider both`. Add a configurable
`tsc_only` knob on the TypeScript codeanalyzer config that passes `--tsc-only`
(codeanalyzer-typescript >= 0.4.2) to pin the resolver-based call graph.

Materialize Jelly-resolved anonymous callbacks as `TSSynthesizedCallable` so
anonymous call-graph edges don't dangle. Wire `get_synthesized_callables`
through the in-process, Neo4j, and facade backends, and add the nodes to the
NetworkX call graph.

Bump codeanalyzer-typescript 0.4.0 -> 0.4.3.

Closes #174
…d) on the Neo4j backend

The existence check is keyed off the path, not the backend: any non-None
project_path is resolved and must be a directory on every backend, while None
skips validation entirely (the Neo4j backends read their graph out of band over
Bolt). Spell this out in `_normalize_project_path`, the java/python/typescript
factory docstrings, and the README Neo4j example.
@rahlk rahlk marked this pull request as ready for review June 27, 2026 19:12
@rahlk rahlk merged commit 8abbd7f into main Jun 27, 2026
@rahlk rahlk deleted the fix/issue-174 branch June 27, 2026 19:12
rahlk added a commit that referenced this pull request Jun 27, 2026
The lockfile still pinned codeanalyzer-typescript 0.4.0 while pyproject was
bumped to 0.4.3 (#179); regenerate so the two agree. No runtime deps added
(black stays in the test dependency group, not runtime).
rahlk added a commit that referenced this pull request Jun 27, 2026
…181)

* feat(python): add bulk/projected accessors to avoid N+1 reconstruction

Add set-at-a-time, field-projected reads to the Python facade so callers can
enumerate the application in one round-trip instead of paying the per-entity
reconstruction get_all_methods_in_application() does (tens of thousands of Bolt
round-trips on large apps via the Neo4j backend).

New `PyCallableOverview` projection model and three accessors on the
PythonAnalysisBackend ABC, both backends, and the facade:

- get_callables_overview() -> List[PyCallableOverview]: every callable
  (methods, module-level and nested functions) as a lightweight projection.
- get_method_bodies(signatures) -> Dict[str, str]: batch source-body fetch.
- get_decorated_callables(markers) -> List[PyCallableOverview]: overviews
  filtered by decorator (fills the get_methods_with_decorators gap).

The in-process and Neo4j backends enumerate the same callable set (a "method"
is one a class declares directly, mirroring PY_HAS_METHOD). Offline unit tests
cover the in-process walk; the Neo4j test module asserts byte-for-byte parity
against it when a server is reachable.

Refs #180

* perf(python): reuse one Neo4j read session instead of one per query

PyNeo4jBackend._run opened a fresh driver session on every call, so the N+1
reconstruction fan-out (get_symbol_table / get_all_methods_in_application) paid
session-acquisition overhead on each of its tens of thousands of queries. Reuse
a single lazily-opened session for the backend's lifetime, dropping it on error
so the next call reopens cleanly. Closed in close().

Refs #180

* chore: sync uv.lock with codeanalyzer-typescript 0.4.3

The lockfile still pinned codeanalyzer-typescript 0.4.0 while pyproject was
bumped to 0.4.3 (#179); regenerate so the two agree. No runtime deps added
(black stays in the test dependency group, not runtime).

* feat(python): add get_callsites_for batch accessor (#180 item 3)

Batch call-site fetch keyed by owning signature, the last of the four bulk
accessors from #180. One projected Cypher statement on the Neo4j backend (an
OPTIONAL MATCH over PY_HAS_CALLSITE so an existing callable with no call sites
still gets an empty-list entry, matching the in-process backend); one
symbol-table walk in-process. Added to the ABC, both backends, and the facade,
with offline and Neo4j-parity test coverage.

Refs #180
@rahlk rahlk added the bug Something isn't working label Jun 27, 2026
@rahlk rahlk mentioned this pull request Jun 27, 2026
9 tasks
rahlk added a commit that referenced this pull request Jun 27, 2026
Minor release: new backward-compatible API since v1.2.0.

- Python facade bulk/projected accessors (get_callables_overview,
  get_method_bodies, get_decorated_callables, get_callsites_for) + the
  PyCallableOverview model (#181).
- TypeScript tsc_only toggle (TSCodeAnalyzerConfig) and synthesized anonymous
  callables (TSSynthesizedCallable, get_synthesized_callables) (#179).
- Neo4j Python backend reuses a single read session.
- codeanalyzer-typescript 0.4.0 -> 0.4.3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove obsolete --call-graph-provider both from TypeScript backend; add a configurable tsc_only toggle

1 participant