blob: c581aed8da93151f2abad365229373c02fdb0b29 [file] [log] [blame]
'use strict';
// State machine states reused between parses for better perf
const STATES = {
NORMAL: 0,
IN_SINGLE_QUOTE: 1,
IN_DOUBLE_QUOTE: 2,
IN_COMMENT: 3,
};
/**
* CSS Comment Parser with context awareness
* Properly handles comments inside strings, URLs, and escaped characters
*
* @param {string} input
* @return {[number, number, number][]}
*/
module.exports = function commentParser(input) {
/** @type {[number, number, number][]} */
const tokens = [];
const length = input.length;
let pos = 0;
let state = STATES.NORMAL;
let tokenStart = 0;
let commentStart = 0;
while (pos < length) {
const char = input[pos];
const nextChar = pos + 1 < length ? input[pos + 1] : '';
switch (state) {
case STATES.NORMAL:
if (char === '/' && nextChar === '*') {
// Found comment start - add non-comment token if needed
if (pos > tokenStart) {
tokens.push([0, tokenStart, pos]);
}
commentStart = pos;
state = STATES.IN_COMMENT;
pos += 2; // Skip /*
continue;
} else if (char === '"') {
state = STATES.IN_DOUBLE_QUOTE;
} else if (char === "'") {
state = STATES.IN_SINGLE_QUOTE;
}
break;
case STATES.IN_SINGLE_QUOTE:
if (char === '\\' && nextChar) {
// Skip escaped character
pos += 2;
continue;
} else if (char === "'") {
state = STATES.NORMAL;
}
break;
case STATES.IN_DOUBLE_QUOTE:
if (char === '\\' && nextChar) {
// Skip escaped character
pos += 2;
continue;
} else if (char === '"') {
state = STATES.NORMAL;
}
break;
case STATES.IN_COMMENT:
if (char === '*' && nextChar === '/') {
// Found comment end
tokens.push([1, commentStart + 2, pos]);
tokenStart = pos + 2;
state = STATES.NORMAL;
pos += 2; // Skip */
continue;
}
break;
}
pos++;
}
// Handle remaining content
if (state === STATES.IN_COMMENT) {
// Unclosed comment - treat as comment to end
tokens.push([1, commentStart + 2, length]);
} else if (tokenStart < length) {
// Add final non-comment token
tokens.push([0, tokenStart, length]);
}
return tokens;
};