postcss-discard-comments
Version:
Discard comments in your CSS files with PostCSS.
150 lines (126 loc) • 3.35 kB
JavaScript
;
// State machine states reused between parses for better perf
const STATES = {
NORMAL: 0,
IN_SINGLE_QUOTE: 1,
IN_DOUBLE_QUOTE: 2,
IN_COMMENT: 3,
};
/**
* @typedef {object} ParserContext
* @property {string} input
* @property {[number, number, number][]} tokens
* @property {number} length
* @property {number} pos
* @property {number} state
* @property {number} tokenStart
* @property {number} commentStart
*/
/**
* @param {ParserContext} context
* @param {string} nextChar
* @return {boolean}
*/
function handleNormalState(context, nextChar) {
const { input, pos } = context;
const char = input[pos];
if (char === '/' && nextChar === '*') {
if (pos > context.tokenStart) {
context.tokens.push([0, context.tokenStart, pos]);
}
context.commentStart = pos;
context.state = STATES.IN_COMMENT;
context.pos += 2;
return true;
}
if (char === '"') {
context.state = STATES.IN_DOUBLE_QUOTE;
} else if (char === "'") {
context.state = STATES.IN_SINGLE_QUOTE;
}
return false;
}
/**
* @param {ParserContext} context
* @param {string} nextChar
* @param {string} quote
* @return {boolean}
*/
function handleQuoteState(context, nextChar, quote) {
const char = context.input[context.pos];
if (char === '\\' && nextChar) {
context.pos += 2;
return true;
}
if (char === quote) {
context.state = STATES.NORMAL;
}
return false;
}
/**
* @param {ParserContext} context
* @param {string} nextChar
* @return {boolean}
*/
function handleCommentState(context, nextChar) {
const { input, pos } = context;
if (input[pos] === '*' && nextChar === '/') {
context.tokens.push([1, context.commentStart + 2, pos]);
context.tokenStart = pos + 2;
context.state = STATES.NORMAL;
context.pos += 2;
return true;
}
return false;
}
/**
* 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 {ParserContext} */
const context = {
input,
tokens: [],
length: input.length,
pos: 0,
state: STATES.NORMAL,
tokenStart: 0,
commentStart: 0,
};
while (context.pos < context.length) {
const nextChar =
context.pos + 1 < context.length ? context.input[context.pos + 1] : '';
let handled;
switch (context.state) {
case STATES.NORMAL:
handled = handleNormalState(context, nextChar);
break;
case STATES.IN_SINGLE_QUOTE:
handled = handleQuoteState(context, nextChar, "'");
break;
case STATES.IN_DOUBLE_QUOTE:
handled = handleQuoteState(context, nextChar, '"');
break;
case STATES.IN_COMMENT:
handled = handleCommentState(context, nextChar);
break;
}
if (handled) {
continue;
}
context.pos++;
}
// Handle remaining content
if (context.state === STATES.IN_COMMENT) {
// Unclosed comment - treat as comment to end
context.tokens.push([1, context.commentStart + 2, context.length]);
} else if (context.tokenStart < context.length) {
// Add final non-comment token
context.tokens.push([0, context.tokenStart, context.length]);
}
return context.tokens;
};