feat(steps): a reply that sets no status is a 200; inline Express handlers keep their replies
- effects.ts implicitResponseStatus: a body-sending reply with no status in its chain (res.json / send / render, reply.send, c.json, NextResponse.json, JSONResponse / jsonify / render_template, Rails render, Laravel response()->json) is a 200; a variable status, end, sendStatus and redirects stay as they were - branch-guards callSiteInTree: a status set by the statement just before the reply (`res.status(202); res.json(user)`) is that reply's — looked back within the block, only a statement that IS the status call counts - steps.ts: explicit chain/args → set-before → implicit 200 - express.ts: an inline handler's reply calls (`res.status(404).json(…)`, `res.json(user)`) are references at their own line and column instead of framework noise, so the route's own reply box exists - tests: servers fixture (inline route's 200 beside the service's 404; a 202 set before), ui-effects - CHANGELOG Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
co-authored by
Claude Fable 5
parent
77dc0ad2eb
commit
02430ccc32
@@ -450,6 +450,28 @@ export function responseStatus(text: string, args: string | null | undefined, _k
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status a reply sends when it sets none — 200 — for the calls that send a
|
||||
* body and default to it: Express / Koa / Fastify / Hono `res.json`,
|
||||
* `res.send`, `res.render`, `reply.send`, `c.json`; `NextResponse.json`;
|
||||
* Python's `JSONResponse`, `jsonify`, `render_template`, `HttpResponse`;
|
||||
* Rails' `render`; Laravel's `response()->json`. Null when the chain sets a
|
||||
* status of its own (`res.status(code).json` — a variable code is unknown,
|
||||
* not 200), when the call ends a response without a body (`end`,
|
||||
* `sendStatus`), or when the call is not one of these.
|
||||
*/
|
||||
export function implicitResponseStatus(text: string): number | null {
|
||||
const call = normaliseCall(text);
|
||||
if (/(?:^|\.)(?:status|sendStatus|code|Status|StatusCode|SendStatus|withStatus|with_status|writeHead)\(/.test(call)) return null;
|
||||
const bare = call.replace(/\([^()]*\)/g, '');
|
||||
if (/^(?:res|response|reply|rep|ctx|c|context)(?:\.(?:type|set|header|headers|append|cookie|clearCookie|vary|location|links|format))*\.(?:json|jsonp|send|render|sendFile|download|text|html|body|stream|file|view)$/.test(bare)) return 200;
|
||||
if (/^(?:NextResponse|Response)\.json$/.test(bare)) return 200;
|
||||
if (/^(?:JSONResponse|HTMLResponse|PlainTextResponse|ORJSONResponse|UJSONResponse|jsonify|render_template|render|make_response|HttpResponse|JsonResponse|send_file|send_from_directory)$/.test(bare)) return 200;
|
||||
if (/^(?:render|render_to_string|respond_with)$/.test(bare)) return 200;
|
||||
if (/^response\(\)->(?:json|view)$|^response->json$|^view$/.test(call.replace(/\s+/g, ''))) return 200;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The abbreviated argument list split on its top-level commas. */
|
||||
function splitArgs(args: string): string[] {
|
||||
const out: string[] = [];
|
||||
|
||||
@@ -42,7 +42,7 @@ import type { Edge, Language, Node, UnresolvedReference } from '../../types';
|
||||
import { badRequest, intParam, notFound } from './respond';
|
||||
import { createSiteReader } from './when';
|
||||
import type { SiteTrigger } from '../../graph/branch-guards';
|
||||
import { classifyEffect, responseStatus, type Effect } from './effects';
|
||||
import { classifyEffect, implicitResponseStatus, responseStatus, type Effect } from './effects';
|
||||
import { looksLikeComponent, routeRoots } from './route-roots';
|
||||
import { nextRouteForFile } from '../../resolution/frameworks/nextjs';
|
||||
import { splitRouteName } from './routes';
|
||||
@@ -639,7 +639,10 @@ export async function buildSteps(cg: CodeGraph, projectRoot: string, query: URLS
|
||||
if (effect.category === 'response') {
|
||||
// `NextResponse.json(user, { status: 201 })`: the code sits in an object
|
||||
// the abbreviation reduced to its keys; the site reader kept it.
|
||||
const status = responseStatus(text, args, ref.referenceKind) ?? (usable && typeof site.status === 'number' ? site.status : null);
|
||||
// — and a body-sending reply that sets none is a 200, so a success row
|
||||
// says so beside the 401s.
|
||||
const status =
|
||||
responseStatus(text, args, ref.referenceKind) ?? (usable && typeof site.status === 'number' ? site.status : null) ?? implicitResponseStatus(text);
|
||||
if (status !== null) wireSite.status = status;
|
||||
}
|
||||
link(step, target, 'effect', fold.chain, [...fold.whens, when], wireSite, null, trigger ?? (await triggerAt(fold.node, at)));
|
||||
|
||||
Reference in New Issue
Block a user