fix: PR JS

This commit is contained in:
Brennan Wilkes (Text Groove) 2026-01-22 12:06:42 -08:00
parent a29c3920e5
commit 3ba5658149

View file

@ -216,104 +216,70 @@ function makePrettyObjBlock(objIndent, obj) {
} }
function applyInsertionsToArrayText({ function applyInsertionsToArrayText({
src, src,
propName, propName,
incoming, incoming,
keyFn, keyFn,
normalizeFn, normalizeFn,
}) { }) {
const span = findJsonArraySpan(src, propName); const span = findJsonArraySpan(src, propName);
if (!span) die(`Could not find "${propName}" array in ${filePath}`); if (!span) die(`Could not find "${propName}" array in ${filePath}`);
const before = src.slice(0, span.open + 1); // includes '[' const before = src.slice(0, span.open + 1); // includes '['
const inner = src.slice(span.open + 1, span.close); // between [ and ] const inner = src.slice(span.open + 1, span.close); // between [ and ]
const after = src.slice(span.close); // starts with ']' const after = src.slice(span.close); // starts with ']'
const itemIndent = detectItemIndent(inner, span.fieldIndent); const itemIndent = detectItemIndent(inner, span.fieldIndent);
const rawBlocks = splitArrayObjectBlocks(inner); // Parse existing objects to build a dedupe set (does NOT modify inner text)
const rawBlocks = splitArrayObjectBlocks(inner);
const existing = []; const seen = new Set();
const seen = new Set(); for (const raw of rawBlocks) {
try {
for (const raw of rawBlocks) { const obj = JSON.parse(raw);
try { const k = keyFn(obj);
const obj = JSON.parse(raw); if (k) seen.add(k);
const k = keyFn(obj); } catch {
existing.push({ raw, obj, key: k }); // ignore unparsable blocks for dedupe purposes
if (k) seen.add(k); }
} catch {
// If parsing fails, keep the raw block as-is, but don't use it for keying
existing.push({ raw, obj: null, key: "" });
} }
}
const toAdd = [];
const toAdd = []; for (const x of incoming) {
for (const x of incoming) { const nx = normalizeFn(x);
const nx = normalizeFn(x); const k = keyFn(nx);
const k = keyFn(nx); if (!k || seen.has(k)) continue;
if (!k || seen.has(k)) continue; seen.add(k);
seen.add(k); toAdd.push(nx);
toAdd.push({ obj: nx, key: k });
}
if (!toAdd.length) return src; // nothing to do
// Insert each new item into sorted position by key (lex)
// We rebuild the list of raw blocks but preserve existing raw blocks untouched.
const outBlocks = existing.slice(); // keep {raw,obj,key}
function findInsertIndex(k) {
for (let i = 0; i < outBlocks.length; i++) {
const kk = outBlocks[i]?.key || "";
if (!kk) continue; // unknown blocks: keep them where they are
if (kk > k) return i;
} }
return outBlocks.length;
} if (!toAdd.length) return src;
// Sort additions so results are deterministic // Deterministic order for new items only (doesn't reorder existing)
toAdd.sort((a, b) => a.key.localeCompare(b.key)); const addBlocks = toAdd
.map((obj) => ({ obj, key: keyFn(obj) }))
for (const add of toAdd) { .sort((a, b) => String(a.key).localeCompare(String(b.key)))
const idx = findInsertIndex(add.key); .map((x) => makePrettyObjBlock(itemIndent, x.obj));
const raw = makePrettyObjBlock(itemIndent, add.obj);
outBlocks.splice(idx, 0, { raw, obj: add.obj, key: add.key });
}
// Rebuild inner text, preserving inline-empty formatting if it was empty
let newInner = "";
if (outBlocks.length === 0) {
newInner = inner; // shouldn't happen, but keep original
} else {
// Determine if original was inline empty: "links": []
const wasInlineEmpty = /^\s*$/.test(inner); const wasInlineEmpty = /^\s*$/.test(inner);
let newInner;
if (wasInlineEmpty) { if (wasInlineEmpty) {
// Convert to pretty multi-line on first insert (minimal and stable) // "links": [] -> pretty multiline
newInner = newInner =
"\n" + "\n" + addBlocks.join(",\n") + "\n" + span.fieldIndent;
outBlocks.map((x) => x.raw).join(",\n") +
"\n" +
span.fieldIndent;
} else { } else {
// Keep pretty multi-line (same join style as JSON.stringify) // Keep existing whitespace EXACTLY; append before trailing whitespace
// Ensure leading/trailing newlines similar to original const m = inner.match(/\s*$/);
const trimmed = inner.replace(/^\s+|\s+$/g, ""); const tail = m ? m[0] : "";
const hadLeadingNL = /^\s*\n/.test(inner); const body = inner.slice(0, inner.length - tail.length).replace(/\s*$/, ""); // end at last non-ws
const hadTrailingNL = /\n\s*$/.test(inner);
newInner = body + ",\n" + addBlocks.join(",\n") + tail;
const body = outBlocks.map((x) => x.raw).join(",\n");
newInner =
(hadLeadingNL ? "\n" : "") +
body +
(hadTrailingNL ? "\n" + span.fieldIndent : "");
// If original didn't have trailing newline before ']', keep it tight
if (!hadTrailingNL) newInner = "\n" + body + "\n" + span.fieldIndent;
} }
return before + newInner + after;
} }
return before + newInner + after;
}
/* ---------------- Apply edits ---------------- */ /* ---------------- Apply edits ---------------- */