From 9a7d0fbc503f775a2706b737575a5eb12b5712f9 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Sat, 22 Aug 2026 07:13:18 +0500 Subject: [PATCH] Fix: skip regex literals in Astro fences and url() protocol-relative slashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quote-bearing regexes made the frontmatter closer miss the closing ---, and url(//…) plus interpolations were treated as SCSS line comments that hid live font-family. Prepared with AI assistance. Co-authored-by: Cursor --- cli/engine/engines/regex/detect-text.mjs | 16 +++++++++++++- tests/detect-antipatterns.test.js | 27 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cli/engine/engines/regex/detect-text.mjs b/cli/engine/engines/regex/detect-text.mjs index 952ff78cf..26ca4f390 100644 --- a/cli/engine/engines/regex/detect-text.mjs +++ b/cli/engine/engines/regex/detect-text.mjs @@ -294,6 +294,7 @@ function blankHtmlAndCssCommentsOutsideScripts(text) { function blankCssLineComments(text) { let output = ''; let state = 'code'; + let urlDepth = 0; for (let i = 0; i < text.length; i++) { const char = text[i]; const next = text[i + 1]; @@ -317,7 +318,7 @@ function blankCssLineComments(text) { continue; } const prev = output.length ? output[output.length - 1] : ''; - if (char === '/' && next === '/' && prev !== ':' && prev !== '(' && prev !== '\\') { + if (char === '/' && next === '/' && urlDepth === 0 && prev !== ':' && prev !== '(' && prev !== '\\') { output += ' '; i++; state = 'line'; @@ -325,6 +326,12 @@ function blankCssLineComments(text) { } if (char === "'") state = 'single'; else if (char === '"') state = 'double'; + if (char === '(') { + const behind = output.replace(/\s+$/, ''); + if (urlDepth > 0 || /url$/i.test(behind)) urlDepth++; + } else if (char === ')' && urlDepth) { + urlDepth--; + } output += char; } return output; @@ -367,6 +374,13 @@ function findAstroFrontmatterClose(text) { cursor = commentEnd + 2; continue; } + if (char === '/' && next !== '/' && next !== '*') { + const close = findRegexLiteralEnd(text, cursor); + if (close !== -1) { + cursor = close + 1; + continue; + } + } cursor++; } return -1; diff --git a/tests/detect-antipatterns.test.js b/tests/detect-antipatterns.test.js index 75fc7fe92..1f022b910 100644 --- a/tests/detect-antipatterns.test.js +++ b/tests/detect-antipatterns.test.js @@ -577,6 +577,33 @@ describe('detectText — broken images in source comments', () => { expect(findings).toHaveLength(1); expect(findings[0].line).toBe(3); }); + + test('ignores frontmatter comments after a regex literal that contains quotes', () => { + const source = [ + '---', + 'const re = /["\']/;', + '// Comment-only image', + '---', + '
ok
', + ].join('\n'); + + expect(detectText(source, 'hero.astro').filter(r => r.antipattern === 'broken-image')).toHaveLength(0); + }); + + test('keeps live font-family after a protocol-relative URL in SCSS', () => { + const sources = [ + '.hero { background: url( //cdn.example.com/i.png); font-family: Inter; }', + '.hero { background: url(#{$prefix}//cdn.example.com/i.png); font-family: Inter; }', + ]; + + for (const source of sources) { + expect(detectText(source, 'hero.scss').filter(r => r.antipattern === 'overused-font')).toHaveLength(1); + } + expect(detectText( + '.hero { background: url(@{prefix}//cdn.example.com/i.png); font-family: Inter; }', + 'hero.less', + ).filter(r => r.antipattern === 'overused-font')).toHaveLength(1); + }); }); describe('detectText — CSS borders', () => {