Skip to content

feat(extraction): instantiates + decorates graph edges#3

Open
mschreib28 wants to merge 2 commits into
mainfrom
upstream/feat/decorator-and-constructor-edges
Open

feat(extraction): instantiates + decorates graph edges#3
mschreib28 wants to merge 2 commits into
mainfrom
upstream/feat/decorator-and-constructor-edges

Conversation

@mschreib28
Copy link
Copy Markdown
Owner

Summary\n\nTwo new structural edges that fill significant gaps in the call graph for modern JS/TS / Java / C# / Python / Kotlin codebases.\n\n### instantiates edges from new Foo(...)\n\nPreviously the extractor only recognised call_expression; new_expression (and equivalents in other grammars) was silently ignored, so constructor invocations produced zero graph edges.\n\nAdds an INSTANTIATION_KINDS set covering new_expression, object_creation_expression, and instance_creation_expression. Wired into both the top-level visitNode dispatcher AND the per-function-body visitForCallsAndStructure walker — new calls inside function bodies were being missed by the body walker even after the top-level dispatch was hooked up.\n\nHandles three subtleties:\n- Generic types: new Map<K, V>() strips the angle-bracket suffix to produce 'Map', so resolution can match the class node.\n- Qualified constructors: new ns.Foo() keeps the trailing identifier ('Foo') — same shape as the existing extractCall does for member-call.\n- Nested calls: children are still walked, so new Foo(bar()) produces both an instantiates ref and a calls ref.\n\n### decorates edges from @Decorator annotations\n\nTree-sitter places decorator nodes BEFORE the symbol they apply to, so a naive walk-time dispatch saw the wrong nodeStack head (file/class instead of class/method). The fix runs decorator extraction from inside extractClass / extractFunction / extractMethod / extractProperty / extractField, after the symbol's node id is known.\n\nLooks for decorator nodes in two places:\n- Direct named children of the declaration (method/property style).\n- Preceding siblings in the parent (TypeScript class style: @Foo class X {} parses as parent { decorator, class_decl }).\n\nHandles two subtleties:\n- Sibling boundary: walks BACKWARD from the declaration and stops at the first non-decorator separator. Without that stop, @A class Foo {} @B class Bar {} would attribute @A to Bar. Caught by reviewer; covered by a regression test.\n- Tree-sitter wrapper identity: parent/namedChild returns fresh JS wrapper objects, so sibling === declNode is unreliable — uses startIndex comparison instead.\n\nAlso recognises Java's marker_annotation (no-args annotations like @Override/@Deprecated) alongside decorator/annotation.\n\n## Test plan\n\nVerified live on a synthetic NestJS-shape fixture:\n\n\nsrc kind tgt\nUserController decorates Controller (class decorator)\nlist decorates Get (method decorator)\nbootstrap instantiates UserService (new ...())\nbootstrap instantiates UserController\n\n\n- [x] 6 new extraction tests covering new Foo() / generic-stripping / qualified-new / @Foo class X / two-adjacent-decorated-classes regression / @Foo method()\n- [x] npx vitest run386 passed (was 380, +6 new)\n- [x] npx tsc --noEmit clean\n- [x] npm run build succeeds\n\n🤖 Generated with Claude Code\n


Copied from colbymchenry/codegraph#134

Two new structural edges that fill gaps in the call graph for
modern JS/TS / Java / C# / Python / Kotlin codebases.

1) `instantiates` edges from `new Foo(...)`:

The bulk-extraction and visitFunctionBody dispatchers only
recognised `call_expression`; `new_expression` (and the equivalent
`object_creation_expression` / `instance_creation_expression` in
other grammars) was silently ignored. Adds INSTANTIATION_KINDS,
extractInstantiation(), and dispatch from BOTH the top-level
visitNode and the per-function-body walker. Children are still
descended so nested calls inside constructor args (`new Foo(bar())`)
get their own `calls` refs.

Output: a `bootstrap` function that does `new UserService(); new
UserController(svc)` now produces two `instantiates` edges to those
class nodes — previously zero edges.

2) `decorates` edges from `@Decorator` annotations:

Tree-sitter places decorator nodes BEFORE the symbol they apply to
in the AST, so the original walk-time dispatch saw the wrong
nodeStack head (file/class instead of class/method). Replaced with
extractDecoratorsFor(declNode, decoratedId) that runs from inside
extractClass / extractFunction / extractMethod after the symbol's
node id is known.

Looks for decorator nodes in two places:
  - Direct named children of the declaration (method/property style)
  - Preceding siblings in the parent (TypeScript class style:
    @foo class X {} parses as parent { decorator, class_decl })

Sibling check uses startIndex comparison rather than reference
identity — tree-sitter web bindings return fresh JS wrappers from
parent/namedChild navigation, so `===` is unreliable. Took a debug
session to spot this; flagging in the comment so the next reader
doesn't re-introduce the bug.

Output: a `@Controller` class decorator + `@Get` method decorator
on a NestJS-style controller now produce two `decorates` edges
(class→Controller, method→Get) with the correct source nodes.

Verified live on a synthetic NestJS-shape fixture; all 380
existing tests pass.
…ric constructors, property/field decorators, marker_annotation, tests

Five fixes from independent semantic review:

- extractDecoratorsFor sibling walk now iterates BACKWARD from the
  declaration and stops at the first non-decorator/annotation
  separator. Previous version walked forward up to declStart and
  consumed every decorator-typed sibling — so two adjacent
  decorated classes (`@A class Foo {} @b class Bar {}`) had `@A`
  spuriously attributed to `Bar`.

- extractInstantiation strips the type-argument suffix from the
  constructor field text. `new Map<K, V>()` was producing
  referenceName 'Map<K, V>' (the constructor field is a generic_type
  node) and resolution always failed.

- extractProperty and extractField now call extractDecoratorsFor
  after their createNode calls. NestJS-style `@Inject() private
  svc: Foo` and Java field annotations were being silently dropped.

- consider() in extractDecoratorsFor recognises 'marker_annotation'
  in addition to 'decorator'/'annotation'. Java's tree-sitter grammar
  emits marker_annotation for arg-less annotations like @OverRide
  and @deprecated; without this every Java marker annotation was
  silently skipped.

- 6 new extraction tests covering: instantiates ref for new Foo(),
  generic-type stripping (`new Container<string>()` -> 'Container'),
  qualified-new keeps trailing identifier (`new ns.Foo()` -> 'Foo'),
  decorates ref for @foo class X {}, regression for adjacent
  decorated classes (each gets its OWN decorator), decorates ref
  for @foo method().

Full test suite: 386 passed (was 380, +6 new extraction tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants