feat(#72): SPI v1 slice 1c-ii.e — inline arrow handler synthesis#110
Conversation
Closes the synthesis gap left open by slices 1c-ii.c and 1c-ii.d. When the route/middleware argument is an ArrowFunction or FunctionExpression instead of an Identifier — the canonical Express idiom (app.get('/p', (req, res) => {...})) — the detector now mints a synthetic SpiSymbol for the anonymous callback.
Synthesis details:
* kind: 'function' (closest SpiSymbolKind for a callable). * id: symbol:<file_id>/function/<binding>.<method>.L<line>. Deterministic across builds for the same source. Collisions at the same line on the same binding/method are rare; if encountered the existing symbol is reused. * range: the handler node's own source range. * framework_role: 'express_route' for HTTP method calls, 'express_middleware' for app.use(...). * Pushed to BOTH the flat ctx.symbols list (so buildSpi's sort/return picks it up) and ctx.symbolsByFile[fileId] (so the per-file index stays consistent).
DetectExpressFrameworkContext gains a symbols: SpiSymbol[] field. build.ts passes the parent symbols array through. Without this, synthesized symbols would only live in the per-file map and never make it back into the final SemanticProgramIndex.
Edge emission for synthetic routes: same route_handler edge from the binding to the synthetic symbol, with confidence 'high' and source 'framework-decorator'. Middleware synthesis emits no edge (parity with slice 1c-ii.d's tag-only design).
Lexical-shadow defense still applies: the receiver-identity check from slice 1c-ii.c gates whether we even attempt synthesis. If the receiver is an inner-scope shadowed local, no synthesis happens.
Tests: 7 new in the inline-handler-synthesis suite. Cover arrow synthesis with deterministic name pattern, route_handler edge emission, multiple inline handlers in the same file (each gets its own symbol), plain function-expression handlers, inline middleware synthesis, shadowed-receiver still skips, end-to-end projection (synthetic symbol → ExtractionNode with framework=express, node_kind=route).
Plus 2 pre-existing 'inline handler is deferred' tests updated to assert the now-delivered behavior (they were skip-assertions that became inverted once slice 1c-ii.e closed the gap).
What's left for slice 1c-ii.f (the v0.14.0 trigger): route_path metadata on synthetic symbols (requires extending SpiSymbol with a framework_metadata field), mounted-router prefix resolution (app.use('/api', usersRouter) → routes mounted at /api/...), full byte-equivalent port of the remaining surface in extract/frameworks/express.ts.
📝 WalkthroughWalkthroughPR ChangesExpress inline handler synthesis
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/pipeline/spi/framework-express.ts (1)
264-313: 💤 Low valuePotential ID collision for multiple inline handlers on the same line.
The deterministic ID
symbol:${fileId}/function/${binding}.${method}.L${line}will collide if two distinct inline handlers appear on the same line with the same binding and method (e.g.,app.get("/a", ()=>{}); app.get("/b", ()=>{})on one line). The comment acknowledges this but states "the second push is a no-op thanks to the seenEdgeKeys dedupe downstream" — however,mintSyntheticHandlerSymbolitself returns the first symbol for the second handler (line 294-296), meaning both route registrations point to the sameSpiSymbol.This is an accepted edge case per the comment, but worth noting that the two callbacks will share identity in the SPI. If column-level disambiguation is ever needed, the ID scheme would need adjustment.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pipeline/spi/framework-express.ts` around lines 264 - 313, The current deterministic id in mintSyntheticHandlerSymbol can collide for multiple inline handlers on the same line; fix by incorporating column info into the synthetic symbol name/id (e.g., include range.start.character or a small hash of handler.pos) so each handler on the same line gets a unique id; update the constructed name and id (the variables name and id inside mintSyntheticHandlerSymbol) to append the column (or short hash) and keep the rest of the logic (symbolsByFile dedupe and framework_role update) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/pipeline/spi/framework-express.ts`:
- Around line 264-313: The current deterministic id in
mintSyntheticHandlerSymbol can collide for multiple inline handlers on the same
line; fix by incorporating column info into the synthetic symbol name/id (e.g.,
include range.start.character or a small hash of handler.pos) so each handler on
the same line gets a unique id; update the constructed name and id (the
variables name and id inside mintSyntheticHandlerSymbol) to append the column
(or short hash) and keep the rest of the logic (symbolsByFile dedupe and
framework_role update) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 849fd075-be77-436d-ab53-519a598ec724
📒 Files selected for processing (3)
src/pipeline/spi/build.tssrc/pipeline/spi/framework-express.tstests/unit/spi-framework-express.test.ts
Builds on slices 1c-ii.b/c/d (Express factory/route/middleware). Closes the synthesis gap left open by 1c-ii.c and 1c-ii.d.
Summary
When the route/middleware argument is an `ArrowFunction` or `FunctionExpression` instead of an `Identifier` — the canonical Express idiom `app.get('/p', (req, res) => {...})` — the detector now mints a synthetic `SpiSymbol` for the anonymous callback.
Implementation notes
What's deferred to slice 1c-ii.f (v0.14.0 trigger)
Test plan
Refs #72, #107, #108, #109.
Summary by CodeRabbit
New Features
Tests