feat(steps): a run of calls that happens once per item says so
A body drawn once, with nothing to say it repeats, is a quiet lie about the order — so the reading now reads the loops a site is written inside, the same way it reads its conditions: one climb up the same ancestors, per language, `for` / `foreach` / `for … in` / `while` / `do` / `repeat`, with the header as written (`item of items`, `queue.length > 0`) and where the loop starts. Loops and forks nest in either direction, and neither reading knows about the other, so the block builder merges them by where each construct BEGINS: on one ancestor chain the outer one always starts first, which rebuilds the nesting from the positions alone. A `for` inside an `if` and an `if` inside a `for` come out the way the code has them. With it, the per-framework readings are pinned: an Express handler with its helper drawn inside the reply it builds, a FastAPI `raise HTTPException` ending the arm it is in, a Spring early `return` as the other arm of its `if` (with the comparison flipped, not wrapped), an ASP.NET handler's two outcomes, and a Nest controller read on through the service it delegates to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
co-authored by
Claude Opus 5
parent
9acab0020f
commit
7b6704a70d
@@ -19,6 +19,7 @@ import {
|
||||
decoratorsForFile,
|
||||
guardLabel,
|
||||
guardsForFile,
|
||||
loopsForFile,
|
||||
memberTypesForFile,
|
||||
siteKey,
|
||||
supportsBranchGuards,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
type BranchGuard,
|
||||
type CallSiteText,
|
||||
type DefinitionDecorators,
|
||||
type SiteLoop,
|
||||
type SiteTrigger,
|
||||
} from '../../graph/branch-guards';
|
||||
import { resolveProjectFile } from '../security';
|
||||
@@ -106,6 +108,8 @@ export interface SiteReader {
|
||||
* What {@link SiteReader.when} joins; empty when unconditional or unreadable.
|
||||
*/
|
||||
guards(caller: { filePath: string; language: Language }, site: { line?: number; column?: number }): Promise<BranchGuard[]>;
|
||||
/** The loops the site is written inside, outermost first — a run of calls that happens once per item. */
|
||||
loops(caller: { filePath: string; language: Language }, site: { line?: number; column?: number }): Promise<SiteLoop[]>;
|
||||
/** What the site passes, abbreviated (`'userEmail', values.email`); null when unreadable. '' for an empty list. */
|
||||
args(caller: { filePath: string; language: Language }, site: { line?: number; column?: number }): Promise<string | null>;
|
||||
/** What fires the site — the JSX prop, `on*` option or runs-later call it is written under; null when nothing binds it. */
|
||||
@@ -175,6 +179,15 @@ export function createSiteReader(cg: CodeGraph, projectRoot: string, maxSites =
|
||||
async when(caller, site) {
|
||||
return guardLabel(await guards(caller, site));
|
||||
},
|
||||
async loops(caller, site) {
|
||||
// Not counted against the budget: the tree is parsed for the site's
|
||||
// guards anyway, and this is a second climb up the same nodes.
|
||||
if (!site.line || !supportsBranchGuards(caller.language)) return [];
|
||||
const file = resolve(caller);
|
||||
if (!file) return [];
|
||||
const key = { line: site.line, column: typeof site.column === 'number' ? site.column : null };
|
||||
return (await loopsForFile(file.abs, file.language, [key])).get(siteKey(key)) ?? [];
|
||||
},
|
||||
async args(caller, site) {
|
||||
if (!site.line || sites >= maxSites || !supportsBranchGuards(caller.language)) return null;
|
||||
const file = resolve(caller);
|
||||
|
||||
Reference in New Issue
Block a user