fix(syntax): reject bare apis-container bodies and cross-mode duplicate api names

Bare bodies inside apis {} silently discarded their text with no error,
producing a do-nothing block. They now throw a ParseError naming the entry
and pointing at the response {} section. Duplicate-name detection for
dataApis moved from an incremental, order-dependent check (only saw prior
entries in the array) to a single post-parse pass over the whole ast.dataApis,
so it catches cross-mode duplicates (apis {} vs ssr { api }) regardless of
declaration order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 02:33:05 +05:30
co-authored by Claude Opus 5
parent 87a00de5f3
commit 953b1cd692
6 changed files with 90 additions and 28 deletions
+14 -9
View File
@@ -1,6 +1,6 @@
"use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: ed63852e856493749abcfd8160c27519e4b7e0d38e93e2b5cf64e94e29bf581f
// WRN editor compiler source hash: d23177f63433ab92bc7d40d5d5967ed8eae783843224be091cc48be13747df6e
// WRN editor compiler generator hash: a54ca847c758bc98d8e353ad6d70088df31de1820f6cf9d1c3462505f563e6b8
// Generated with TypeScript: 6.0.3
const __nodeRequire = require;
@@ -4798,7 +4798,6 @@ function parseApiSections(source) {
function hasRequestSection(source) {
return scanTopLevelBlocks(source, SECTION_NAMES).has("request");
}
const emptySections = { parameters: [], body: [], response: "", error: "" };
/**
* Parse the body of a page-level `apis { }` container: a sequence of
* `<name> <METHOD> <path> { ... }` entries with no leading `api` keyword
@@ -4820,13 +4819,17 @@ function parseApiEntries(source) {
}
const path = lx.readPath();
const entryBody = lx.readBalancedBraces();
const sections = parseApiSections(entryBody);
if (sections === null) {
throw new tokenizer_ts_1.LexError(`Api entry "${nameToken.value}" has a bare body; declare a "response { }" section instead`);
}
entries.push({
mode: "any",
name: nameToken.value,
method: methodToken.value.toUpperCase(),
path,
body: "",
sections: parseApiSections(entryBody) ?? emptySections,
sections,
});
}
return entries;
@@ -6793,12 +6796,7 @@ function parse(source) {
case "apis": {
lx.next();
const body = lx.readBalancedBraces();
for (const entry of (0, api_sections_ts_1.parseApiEntries)(body)) {
if (dataApis.some((block) => block.name === entry.name)) {
throw new ParseError(`Duplicate api entry "${entry.name}" in apis block`);
}
dataApis.push(entry);
}
dataApis.push(...(0, api_sections_ts_1.parseApiEntries)(body));
break;
}
default:
@@ -6872,6 +6870,13 @@ function parse(source) {
};
for (const name of namedLoads.keys())
visitLoad(name);
const seenApiNames = new Set();
for (const block of dataApis) {
if (seenApiNames.has(block.name)) {
throw new ParseError(`Duplicate api entry "${block.name}"`, "WRN-API-DUPLICATE");
}
seenApiNames.add(block.name);
}
return {
type: "page",
imports,