comment-stripper
Version:
Webpack loader that strips comments which can be used on its own too
138 lines (114 loc) • 3.17 kB
JavaScript
;
const { Node, Block, Quote } = require('./Node');
const languages = require('./languages');
const constants = {
ESCAPED_CHAR_REGEX: /^\\./,
QUOTE_REGEX: /^['"`]/,
NEWLINE_REGEX: /^\r*\n/
};
const parse = (input, options = {}) => {
if (typeof input !== 'string') {
throw new TypeError('Expected input to be a string');
}
const cst = new Block({ type: 'root', nodes: [] });
const stack = [cst];
const name = options.language?.toLowerCase() || 'javascript';
const lang = languages[name];
if (typeof lang === 'undefined') {
throw new Error(`Language "${name}" is not supported by strip-comments`);
}
const { LINE_REGEX, BLOCK_OPEN_REGEX, BLOCK_CLOSE_REGEX } = lang;
let block = cst;
let remaining = input;
let token;
let prev;
/**
* Helpers
*/
const consume = (value = remaining[0] || '') => {
remaining = remaining.slice(value.length);
return value;
};
const scan = (regex, type = 'text') => {
const match = regex.exec(remaining);
if (match) {
consume(match[0]);
return { type, value: match[0], match };
}
};
const push = node => {
if (prev?.type === 'text' && node.type === 'text') {
prev.value += node.value;
return;
}
block.push(node);
if (node.nodes) {
stack.push(node);
block = node;
}
prev = node;
};
const pop = () => {
if (block.type === 'root') {
throw new SyntaxError('Unclosed block comment');
}
prev = undefined;
stack.pop();
block = stack[stack.length - 1];
};
/**
* Parse input string
*/
while (remaining !== '') {
// escaped characters
if ((token = scan(constants.ESCAPED_CHAR_REGEX))) {
push(new Node(token));
continue;
}
// quoted strings
if (block.type === 'root') {
if ((token = scan(constants.QUOTE_REGEX))) {
push(new Quote(token.match[0]));
push(new Node(token));
continue;
}
}
if (block.type === 'quote' && remaining.startsWith(block.char)) {
push(new Node({ type: 'text', value: consume(remaining[0]) }));
pop();
continue;
}
// block comment open
if (BLOCK_OPEN_REGEX && options.block && block.type === 'root') {
if ((token = scan(BLOCK_OPEN_REGEX, 'open'))) {
push(new Block({ type: 'block' }));
push(new Node(token));
continue;
}
}
// block comment close
if (BLOCK_CLOSE_REGEX && block.type === 'block' && options.block) {
if ((token = scan(BLOCK_CLOSE_REGEX, 'close'))) {
token.newline = token.match[1] || '';
push(new Node(token));
pop();
continue;
}
}
// line comment
if (LINE_REGEX && block.type === 'root' && options.line) {
if ((token = scan(LINE_REGEX, 'line'))) {
push(new Node(token));
continue;
}
}
// Plain text (skip "C" since some languages use "C" to start comments)
if ((token = scan(/^[a-zABD-Z0-9\r\n\t ]+/))) {
push(new Node(token));
continue;
}
push(new Node({ type: 'text', value: consume() }));
}
return cst;
};
module.exports = parse;