Skip to content

docs(spec): entrypoint detection for typescript, matching python - #150

Merged
rahlk merged 5 commits into
mainfrom
spec/entrypoint-parity
Sep 6, 2026
Merged

docs(spec): entrypoint detection for typescript, matching python#150
rahlk merged 5 commits into
mainfrom
spec/entrypoint-parity

Conversation

@rahlk

@rahlk rahlk commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Spec for TypeScript entrypoint detection at parity with codeanalyzer-python's #27. Committed as provenance; no code.

Starting point

TypeScript has none. grep -rniE "entry_?point" src/ returns one hit, and it is a bundler comment in dataflow/pool.ts.

python typescript
entrypoint code codeanalyzer/entrypoints/, 5 files
source references 73 0
schema PyEntrypoint, PyEntrypointReport
Neo4j is_entrypoint, entrypoint_frameworks
CLI --entrypoint-rules

Without this, the analyzer emits a call graph with no distinguished roots — so a consumer cannot ask what is reachable from outside the application, which is the first question any taint or attack-surface query asks.

What the spec commits to

Mirror python's contract exactly: TSEntrypoint/TSEntrypointReport, entrypoints + is_entrypoint on TSCallable and TSType, the same Neo4j properties, a declarative rules file with a stage-0 framework gate, --entrypoint-rules for user rules, graded declared|certain|heuristic confidence, via: dispatch modelling, and a coverage report. Level-free L1 post-pass; rule loading is a hard error, detection is best-effort and never aborts the analysis.

The part that cannot be ported

Python's engine has two matchers — decorators and base classes — and they cover its ecosystem. They do not cover TypeScript's:

framework declared how matcher
NestJS, Angular decorators python has it
Express, Koa, Fastify app.get('/p', h) — a call call-site
Next.js, Remix, SvelteKit app/**/route.ts, +server.ts file convention
AWS Lambda exported handler export name
CLI tools, packages bin/main in package.json manifest

So the rules format has to be designed for three matcher kinds python never needed, rather than copied and extended later. Two decisions are called out explicitly: whether a file-convention entrypoint can hang off a module (python's schema has no module-level entrypoints, so this would diverge from the shared vocabulary), and how call-site matching stays level-free — recommendation is a syntactic L1 match at confidence: heuristic rather than gating Express to -a >= 2 and breaking "identical at every -a".

What TS has going for it

Better positioned than python was: decorators are structured and checker-resolved as of #143qualified_name is the direct analog of the Jedi definition path python matches on, and positional_arguments/keyword_arguments are exactly what route: {from: positional, index: 0} needs. Heritage is resolved to can:// ids, so transitive base matching is a graph walk rather than a name match. And the stage-0 gate has two ready sources in TSImport and the artifact layer's TSDependency, where python had to regex manifests.

Recommendation on #72

#72 ("entrypoint finders (Express/Angular routes)") is scoped too narrowly — two frameworks and no engine. Built as written it would produce hardcoded detectors with no rules file, no confidence grading, no coverage report and no extensibility, then need rewriting for parity. Retitle it or close it in favour of the engine-shaped unit.

Caveats it states plainly

  • Under-approximation is the designed failure mode and is invisible without the report, so the report ships in the same change as detection, never after
  • False positives are worse than misses — a locally defined Controller in a non-NestJS project must not register, which is why the stage-0 gate is not an optimisation
  • The call-site matcher is the weakest link and should ship as heuristic; Express handlers registered via a variable or helper will be missed, and that belongs in the report's unresolved counts
  • Not validated against a labelled corpus

@rahlk

rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Amended in 4656824 to propagate codeanalyzer-python 1.4.1's entrypoint changes (#182, #185). Four things came across — the heuristic tier, the import-table resolver, the unresolved counter, and the report projected to Neo4j — and one thing they exposed:

TSDecorator.qualified_name is syntactic. Documented as checker-resolved; actually getFullName(), the written text, with no checker call. Measured: a decorator imported as import { Get as HttpGet } emits HttpGet. The earlier draft of this spec called it the analog of the Jedi definition path — corrected in the amendment, and on #143 and #72 where I made the same claim.

That makes the import-table resolver the primary resolution mechanism in TS (python's is a fallback behind Jedi), and adds a unit 0: fix qualified_name to be the import-table resolution or absent, as its own PR ahead of the decorator matcher, since it changes emitted values and also corrects #143's node-merge collapse.

Rules-format decision (made with the user): one heuristics: block with decorators: and calls:. Python's heuristic tier and this spec's call-site matcher were the same mechanism arrived at independently — written spelling, runs last, heuristic, never doubles — so they share a block rather than being two mechanisms with identical semantics and different names. calls: is a cross-language vocabulary change; python's loader rejects unknown keys by design, so a shared rules file is a hard error there until it accepts the key. Filed as a python issue.

@rahlk

rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Unit 0 is up as its own PR, per the amended decomposition: qualified_name becomes the import-table resolution or absent, name becomes the written spelling. Mutation-checked, container tests run. It closes #151 and lands independently of this spec.

@rahlk

rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Unit 1 is up: the contract — schema, Neo4j projection (report on :TSApplication, flags on :TSClass/:TSCallable), and the per-run pass skeleton. Class-only stamping mutation-checked; level invariance tested across a warm cache; container tests run. Closes #153.

rahlk added a commit that referenced this pull request Sep 6, 2026
…keleton (#154)

Unit 1 of the entrypoint spec (#150): the CONTRACT, before any detector exists.
The fields were de-advertised in #60 because nothing populated them; this lands
their shape so they exist at every -a and a consumer can already tell "no
entrypoints" from "no pass ran".

`TSEntrypoint` and `TSEntrypointReport` mirror python's `PyEntrypoint` /
`PyEntrypointReport` field for field. `entrypoints: []` and `is_entrypoint: false`
are stamped on every callable and every CLASS by a per-run pass -- like heritage,
so the cached tree stays free of them and the wire always carries them. Interfaces,
enums, aliases and namespaces never carry them: python stamps PyClass, and nothing
else can be an entrypoint. `entrypoint_report` sits on the application root, empty.

Neo4j mirrors python's projection exactly: `is_entrypoint` / `entrypoint_frameworks`
on :TSClass and :TSCallable, and on :TSApplication the report as
`entrypoint_frameworks` plus `entrypoint_report_json` (sorted-key JSON, since Neo4j
has no map type and python encodes it the same way).

Best-effort by contract: the pass records into `report.errors` rather than throwing,
so a failure here loses flags, never the analysis.

Additive; SCHEMA_VERSION unmoved (#144). The root-envelope key list in
schema-v2.test.ts gains the one new key.
@rahlk

rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Units 2–5 are implemented (#156#158#160#161, stacked). Appended a "Decisions taken during implementation" section to the spec recording how each open question was settled — file-convention entrypoints attach to callables (no module-level field); manifest entrypoints attach to the entry module's top-level-called free functions; module call sites captured INTERNAL and kept in the cache; http_methods never carries a non-HTTP token (app.all still open); one heuristics: block with decorators: and calls: (python #187). Follow-up coverage items: #162.

@rahlk
rahlk merged commit 2f130cf into main Sep 6, 2026
1 check passed
@rahlk
rahlk deleted the spec/entrypoint-parity branch September 6, 2026 12:52
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.

1 participant