diff --git a/src/scan.js b/src/scan.js index 4aaf77f..fa11ad2 100644 --- a/src/scan.js +++ b/src/scan.js @@ -96,11 +96,23 @@ function validateReview(review) { } return { errors }; } -// Scan backward from the last } to find its depth-matching { so that stray -// brace pairs in prose before the JSON block (e.g. `Foo{}`) are skipped. +// Return the JSON object that ends at the last }. Stray brace pairs in prose +// before the JSON block (e.g. `Foo{}`) must be skipped, but counting braces +// alone breaks on string values with unmatched braces (e.g. `impl Foo {`), so +// take the earliest { whose slice parses. If none does, fall back to the +// depth-matching { and let the caller report the parse error. function extractJson(text) { const lastBrace = text.lastIndexOf('}'); if (lastBrace === -1) return null; + for (let i = text.indexOf('{'); i !== -1 && i < lastBrace; i = text.indexOf('{', i + 1)) { + const candidate = text.slice(i, lastBrace + 1); + try { + JSON.parse(candidate); + return candidate; + } catch { + // not the start of the JSON block + } + } let depth = 0; for (let i = lastBrace; i >= 0; i--) { if (text[i] === '}') depth++; diff --git a/tests/scan.test.js b/tests/scan.test.js index cd33aab..fc0fd83 100644 --- a/tests/scan.test.js +++ b/tests/scan.test.js @@ -301,3 +301,23 @@ test('extractJson - handles JSON with brace pairs inside string values', () => { const json = '{"body":"use {} syntax","event":"COMMENT","comments":[]}'; assert.equal(extractJson(json), json); }); + +test('extractJson - handles JSON with unmatched braces inside string values', () => { + for (const body of ['The block opened by `impl Foo {` is never closed.', 'Drop the extra `}`.']) { + const json = JSON.stringify({ + body: 'Summary.', + event: 'COMMENT', + comments: [ + { path: 'src/a.rs', line: 10, side: 'RIGHT', body }, + { path: 'src/b.rs', line: 20, side: 'RIGHT', body: 'Second comment.' }, + ], + }, null, 2); + assert.equal(extractJson(json), json); + assert.equal(extractJson('Here is the review: {not json}\n```json\n' + json + '\n```'), json); + } +}); + +test('extractJson - falls back to the depth-matching brace when nothing parses', () => { + const broken = '{"body":"ok","event":"COMMENT","comments":[{"path":"a"},]}'; + assert.equal(extractJson('Some {prose}. ' + broken), broken); +});