diff --git a/src/Loader/Utils.js b/src/Loader/Utils.js index 4bb02fed..e5b0fc9f 100644 --- a/src/Loader/Utils.js +++ b/src/Loader/Utils.js @@ -217,7 +217,9 @@ const stringifyJSON = (data) => { */ function stripComments(code) { const len = code.length; - let inStr = null; // "'", '"', or '`' + let inStr = null; // "'", '"', or '`' + let inRegex = false; + let regexClass = false; // inside [...] let inBlockComment = false; let inLineComment = false; let out = ''; @@ -226,6 +228,7 @@ function stripComments(code) { while (i < len) { const char = code[i]; const next = code[i + 1]; + const prev = code[i - 1]; // end of line comment if (inLineComment && (char === '\n' || char === '\r')) { @@ -247,15 +250,7 @@ function stripComments(code) { continue; } - // string start - if (!inStr && (char === '"' || char === "'" || char === '`')) { - inStr = char; - out += char; - i++; - continue; - } - - // string end (skip escaped) + // inside string if (inStr) { out += char; if (char === '\\') { @@ -263,8 +258,22 @@ function stripComments(code) { i += 2; continue; } - if (char === inStr) { - inStr = null; + if (char === inStr) inStr = null; + i++; + continue; + } + + // inside RegExp + if (inRegex) { + out += char; + if (regexClass) { + if (char === ']' && prev !== '\\') regexClass = false; + } else { + if (char === '[' && prev !== '\\') { + regexClass = true; + } else if (char === '/' && prev !== '\\') { + inRegex = false; // flags follow + } } i++; continue; @@ -284,6 +293,28 @@ function stripComments(code) { continue; } + // string start + if (char === '"' || char === "'" || char === '`') { + inStr = char; + out += char; + i++; + continue; + } + + // RegExp start (простая, но практичная эвристика) + if (char === '/' && next !== '/' && next !== '*') { + let j = i - 1; + while (j >= 0 && /\s/.test(code[j])) j--; + const prevSym = j >= 0 ? code[j] : ''; + if (j < 0 || /[({\[=:+\-*,!&|?;<>]/.test(prevSym)) { + inRegex = true; + regexClass = false; + out += char; + i++; + continue; + } + } + out += char; i++; } diff --git a/test/cases/_preprocessor/js-tmpl-hbs-compile-helpers-strict/src/helpers/my-helpers.js b/test/cases/_preprocessor/js-tmpl-hbs-compile-helpers-strict/src/helpers/my-helpers.js index 096483d5..cbf39665 100644 --- a/test/cases/_preprocessor/js-tmpl-hbs-compile-helpers-strict/src/helpers/my-helpers.js +++ b/test/cases/_preprocessor/js-tmpl-hbs-compile-helpers-strict/src/helpers/my-helpers.js @@ -40,7 +40,7 @@ const complexHelper = function (content, options) { const SEP = /(\s| |)+/gi; const parts = content.split(SEP).filter(Boolean); const lastWord = parts.pop() || ''; - const firstPart = parts.join(''); + const firstPart = parts.join(''); // magic comment if (!firstPart.trim()) { out = `

${escapeHTML(lastWord)}

`; diff --git a/test/stripComments.test.js b/test/stripComments.test.js index 45c3036c..da5393d2 100644 --- a/test/stripComments.test.js +++ b/test/stripComments.test.js @@ -95,12 +95,23 @@ describe('stripComments', () => { expect(stripComments(code)).toBe(expected); }); - test('does not remove slashes inside regex literals (known limitation)', () => { - // NOTE: This will fail! The function does not support regex detection. - // Documented as a limitation. + test('correctly preserves regex with double slashes', () => { const code = `const re = /\\/\\/.*$/; // regex`; - // The actual output will be incorrect. - // This test documents the limitation (not a failure if you comment it out). - // expect(stripComments(code)).toBe(`const re = /\\/\\/.*$/; `); + expect(stripComments(code)).toBe(`const re = /\\/\\/.*$/; `); + }); + + test('preserves RegExp with double slashes inside', () => { + expect(stripComments('const re = /https?:\\/\\/example\\.com/; // trailing comment')) + .toBe('const re = /https?:\\/\\/example\\.com/; '); + }); + + test('does not break on block comment inside RegExp character class', () => { + expect(stripComments('const re = /[/*]/; // comment')) + .toBe('const re = /[/*]/; '); + }); + + test('does not treat RegExp with /* */ as comment', () => { + expect(stripComments('const r = /a*/*test/; // trailing')) + .toBe('const r = /a*/*test/; '); }); });