Skip to content

feat(neo4j): project decorators, matching python - #143

Merged
rahlk merged 1 commit into
mainfrom
feat/issue-82-neo4j-decorators
Sep 3, 2026
Merged

feat(neo4j): project decorators, matching python#143
rahlk merged 1 commit into
mainfrom
feat/issue-82-neo4j-decorators

Conversation

@rahlk

@rahlk rahlk commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #82.

Decorators have been captured as structured TSDecorator in the JSON since the beginning — name,
checker-resolved qualified_name, positional and keyword arguments, spans — on types, callables,
fields and parameters. None of it reached Neo4j. The entire projection mentioned decorators once,
in a comment (rows.ts:62), so a query could see @Controller in analysis.json and not in the
graph. Python has projected them since #128.

What this adds

Mirrors python's _project_decorator (codeanalyzer/neo4j/project.py:645):

  • :TSDecorator, merged on the resolved qualified_name when the checker supplies one, so
    @Get('/') and @Get('/:id') land on one node instead of two spellings of the same
    decorator.
  • TS_DECORATED_BY from the decorated node to it, carrying the per-application facts —
    positional_arguments (raw source fragments) and keyword_arguments_json (sorted-key JSON,
    because Neo4j has no map property type; python encodes the same field the same way, so the two
    projections stay diffable).

Like python's, the node carries no _module and is never pruned — it is shared across modules, so
anything application-specific on it would accumulate across every project in the database. The
per-application facts live on the relationship for exactly that reason.

One deliberate divergence from python

Python attaches at classes and callables. This attaches at types, callables and fields.

TS carries decorators on fields in the schema already, and property decorators are a first-class TS
idiom — Angular @Input, TypeORM @Column, NestJS. Dropping them would lose the metadata most TS
decorator queries are actually after. Parameters are not projected as nodes in either analyzer, so
parameter decorators remain unprojected in both.

Verification

  • bun test — 238 pass, 0 fail (3 new)
  • container tests runbun run test:container, 4 pass against live Neo4j, which is what
    exercises the auto-derived tsdecorator_name uniqueness constraint in real DDL
  • bun run typecheck clean, schema.neo4j.json regenerated

The new test pins the two properties that row counts alone would not catch: the node is shared
across applications, and the arguments are per-application and therefore on the relationship.

Verified on the existing sample-app fixture, which was already decorated:

{k: 'Controller', p: {name: 'Controller', qualified_name: 'Controller'}},
{k: 'Get',        p: {name: 'Get',        qualified_name: 'Get'}}

{f: '…/UserController/list', t: 'Get',        p: {positional_arguments: ['"/"'],      keyword_arguments_json: '{}'}},
{f: '…/UserController/show', t: 'Get',        p: {positional_arguments: ['"/:id"'],   keyword_arguments_json: '{}'}},
{f: '…/UserController',      t: 'Controller', p: {positional_arguments: ['"/users"'], keyword_arguments_json: '{}'}}

Note on #82's stated shape

The issue title proposes decorators: string[] on TSCallable. That would be worse than what
the analyzer already captures — it flattens structured decorators to bare names, drops the
arguments and the resolved FQN, and covers only callables when the schema carries decorators on
four node types. This implements the python-parity shape instead, which is what "full parity"
requires. Say the word if you want the flattened form as well as, or instead of, this.

SCHEMA_VERSION stays at 2.1.0 — per the standing decision that only 2.0.0 is meaningful until
the design settles and every analyzer re-baselines together.

Decorators were captured as structured `TSDecorator` in the JSON from the start —
name, checker-resolved `qualified_name`, positional and keyword arguments, spans —
on types, callables, fields and parameters. None of it reached Neo4j: the whole
projection mentioned decorators once, in a comment. A query could see `@Controller`
in analysis.json and not in the graph.

Mirror python's `_project_decorator`:

- `:TSDecorator` merged on the resolved `qualified_name` when there is one, so
  `@Get('/')` and `@Get('/:id')` collapse to a single node rather than two spellings
  of one decorator.
- `TS_DECORATED_BY` from the decorated node to it, carrying the per-application
  facts: `positional_arguments` (raw source fragments) and `keyword_arguments_json`
  (sorted-key JSON, since Neo4j has no map property type — python encodes the same
  field the same way, so the two projections stay diffable).

Like python's, the node carries no `_module` and is never pruned: it is shared
across modules, so anything application-specific stored on it would accumulate
across every project in the database.

Attached at types, callables and fields. Python attaches at classes and callables
only, but TS carries decorators on fields too and property decorators are a
first-class TS idiom (Angular `@Input`, TypeORM `@Column`), so dropping them would
lose the metadata most TS decorator queries are actually after. Parameters are not
projected as nodes in either analyzer, so parameter decorators stay unprojected.

SCHEMA_VERSION stays at 2.1.0 — every analyzer re-baselines together later.
@rahlk
rahlk merged commit c316f71 into main Sep 3, 2026
1 check passed
@rahlk
rahlk deleted the feat/issue-82-neo4j-decorators branch September 3, 2026 02:46
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.

v2 Neo4j build: project decorators (decorators: string[] on TSCallable)

1 participant