From dfba2bc34fffa748b9a00d974dbcd08fce76ee4c Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 Sep 2024 08:45:16 -0500 Subject: [PATCH] fix: update attr balancing logic --- src/index.ts | 15 ++++++++----- test/astro.test.ts | 55 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 80e2ed1..72935a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -233,14 +233,19 @@ export function parse(input: string | ReturnType): any { } else { // Ensure attr expressions are balanced if (!isBalanced(token[3])) { - token[3] += '>' - let infiniteloop = 0; + const max = token[3].length + str.slice(DOM_PARSER_RE.lastIndex).length; while (!isBalanced(token[3])) { - if (infiniteloop === 999) throw new Error('Infinite loop detected!'); - const chunk = str.substring(DOM_PARSER_RE.lastIndex, DOM_PARSER_RE.lastIndex + str.substring(DOM_PARSER_RE.lastIndex).indexOf('>')); + if (token[3].length > max) { + // TODO: better error stacktrace + throw new Error(`Syntax error!\n${token[3]}`); + } + const gtIndex = str.substring(DOM_PARSER_RE.lastIndex).indexOf('>'); + if (gtIndex != -1) { + token[3] += '>' + } + const chunk = str.substring(DOM_PARSER_RE.lastIndex, DOM_PARSER_RE.lastIndex + gtIndex); token[3] += chunk; DOM_PARSER_RE.lastIndex += chunk.length + 1; - infiniteloop++; } } tag = { diff --git a/test/astro.test.ts b/test/astro.test.ts index 7a179da..7296a9a 100644 --- a/test/astro.test.ts +++ b/test/astro.test.ts @@ -17,19 +17,64 @@ let value: string; expect(output).toEqual(input); }); - it("does not hang on unmatched braces", async () => { + it("handles invalid expressions", async () => { const input = `--- -let value: string; +let value = { awesome: { }}; --- -

Hello {world}

+

Hello {world!}

{ - const value: string = item.split('{').reverse().join('{'); + const value: string = item.split('').reverse().join(''); return value; })}>Hello world`; const doc = parse(input); const output = await render(doc); expect(output).toEqual(input); }); -}); + it("gracefully handles elements inside attributes", async () => { + const input = `--- +let value: string; +--- + +

Hello {world}

+
  • {item}
  • )}>Hello world
    `; + const doc = parse(input); + expect(doc.children.at(-1).attributes.items).toEqual('{astro.map((item: string) =>
  • {item}
  • )}'); + const output = await render(doc); + expect(output).toEqual(input); + }); + + it("fails with error on unmatched braces", async () => { + const input = ` +
  • {item}
  • )}>Hello world
    `; + let error: unknown; + try { + parse(input); + } catch (e) { + error = e; + } + expect(error).toBeInstanceOf(Error); + }); + + it("handles single-line comments", async () => { + const input = '
  • \n//{{{((([[[[{item}\n
  • )}>Hello world
    '; + const doc = parse(input); + const output = await render(doc); + expect(output).toEqual(input); + }); + + it("handles multi-line comments", async () => { + const input = '
  • \n/* \n * {{{ }\n */\n{item}
  • )}>Hello world
    '; + const doc = parse(input); + const output = await render(doc); + expect(output).toEqual(input); + }); + + it("handles regex", async () => { + const input = '
  • \n{/{{{{/gmi.test(item)}\n
  • )}>Hello world
    '; + const doc = parse(input); + const output = await render(doc); + expect(output).toEqual(input); + }); +});