'use strict' /** * Cloned from https://github.com/postcss/postcss/blob/master/lib/tokenize.es6 * */ const SINGLE_QUOTE = '\''.charCodeAt(0); const DOUBLE_QUOTE = '"'.charCodeAt(0); const BACKSLASH = '\\'.charCodeAt(0); const SLASH = '/'.charCodeAt(0); const NEWLINE = '\n'.charCodeAt(0); const SPACE = ' '.charCodeAt(0); const FEED = '\f'.charCodeAt(0); const TAB = '\t'.charCodeAt(0); const CR = '\r'.charCodeAt(0); const OPEN_SQUARE = '['.charCodeAt(0); const CLOSE_SQUARE = ']'.charCodeAt(0); const OPEN_PARENTHESES = '('.charCodeAt(0); const CLOSE_PARENTHESES = ')'.charCodeAt(0); const OPEN_CURLY = '{'.charCodeAt(0); const CLOSE_CURLY = '}'.charCodeAt(0); const SEMICOLON = ';'.charCodeAt(0); const ASTERISK = '*'.charCodeAt(0); const COLON = ':'.charCodeAt(0); const AT = '@'.charCodeAt(0); const RE_AT_END = /[ \n\t\r\f\{\(\)'"\\;/\[\]#]/g; const RE_WORD_END = /[ \n\t\r\f\(\)\{\}:;@!'"\\\]\[#]|\/(?=\*)/g; const RE_BAD_BRACKET = /.[\\\/\("'\n]/; const RE_HEX_ESCAPE = /[a-f0-9]/i; module.exports = function tokenizer(input, options) { options = options || {} let css = input.css.valueOf(); let ignore = options.ignoreErrors; let code, next, quote, lines, last, content, escape, nextLine, nextOffset, escaped, escapePos, prev, n, currentToken; let length = css.length; let offset = -1; let line = 1; let pos = 0; let buffer = []; let returned = []; function unclosed(what) { throw input.error('Unclosed ' + what, line, pos - offset); } function endOfFile() { return returned.length === 0 && pos >= length; } function nextToken() { if ( returned.length ) return returned.pop(); if ( pos >= length ) return; code = css.charCodeAt(pos); if ( code === NEWLINE || code === FEED || code === CR && css.charCodeAt(pos + 1) !== NEWLINE ) { offset = pos; line += 1; } switch ( code ) { case NEWLINE: case SPACE: case TAB: case CR: case FEED: next = pos; do { next += 1; code = css.charCodeAt(next); if ( code === NEWLINE ) { offset = next; line += 1; } } while ( code === SPACE || code === NEWLINE || code === TAB || code === CR || code === FEED ); currentToken = ['space', css.slice(pos, next)]; pos = next - 1; break; case OPEN_SQUARE: currentToken = ['[', '[', line, pos - offset]; break; case CLOSE_SQUARE: currentToken = [']', ']', line, pos - offset]; break; case OPEN_CURLY: currentToken = ['{', '{', line, pos - offset]; break; case CLOSE_CURLY: currentToken = ['}', '}', line, pos - offset]; break; case COLON: currentToken = [':', ':', line, pos - offset]; break; case SEMICOLON: currentToken = [';', ';', line, pos - offset]; break; case OPEN_PARENTHESES: prev = buffer.length ? buffer.pop()[1] : ''; n = css.charCodeAt(pos + 1); if ( prev === 'url' && n !== SINGLE_QUOTE && n !== DOUBLE_QUOTE && n !== SPACE && n !== NEWLINE && n !== TAB && n !== FEED && n !== CR ) { next = pos; do { escaped = false; next = css.indexOf(')', next + 1); if ( next === -1 ) { if ( ignore ) { next = pos; break; } else { unclosed('bracket'); } } escapePos = next; while ( css.charCodeAt(escapePos - 1) === BACKSLASH ) { escapePos -= 1; escaped = !escaped; } } while ( escaped ); currentToken = ['brackets', css.slice(pos, next + 1), line, pos - offset, line, next - offset ]; pos = next; } else { next = css.indexOf(')', pos + 1); content = css.slice(pos, next + 1); if ( next === -1 || RE_BAD_BRACKET.test(content) ) { currentToken = ['(', '(', line, pos - offset]; } else { currentToken = ['brackets', content, line, pos - offset, line, next - offset ]; pos = next; } } break; case CLOSE_PARENTHESES: currentToken = [')', ')', line, pos - offset]; break; case SINGLE_QUOTE: case DOUBLE_QUOTE: quote = code === SINGLE_QUOTE ? '\'' : '"'; next = pos; do { escaped = false; next = css.indexOf(quote, next + 1); if ( next === -1 ) { if ( ignore ) { next = pos + 1; break; } else { unclosed('string'); } } escapePos = next; while ( css.charCodeAt(escapePos - 1) === BACKSLASH ) { escapePos -= 1; escaped = !escaped; } } while ( escaped ); content = css.slice(pos, next + 1); lines = content.split('\n'); last = lines.length - 1; if ( last > 0 ) { nextLine = line + last; nextOffset = next - lines[last].length; } else { nextLine = line; nextOffset = offset; } currentToken = ['string', css.slice(pos, next + 1), line, pos - offset, nextLine, next - nextOffset ]; offset = nextOffset; line = nextLine; pos = next; break; case AT: RE_AT_END.lastIndex = pos + 1; RE_AT_END.test(css); if ( RE_AT_END.lastIndex === 0 ) { next = css.length - 1; } else { next = RE_AT_END.lastIndex - 2; } currentToken = ['at-word', css.slice(pos, next + 1), line, pos - offset, line, next - offset ]; pos = next; break; case BACKSLASH: next = pos; escape = true; while ( css.charCodeAt(next + 1) === BACKSLASH ) { next += 1; escape = !escape; } code = css.charCodeAt(next + 1); if ( escape && (code !== SLASH && code !== SPACE && code !== NEWLINE && code !== TAB && code !== CR && code !== FEED ) ) { next += 1; if ( RE_HEX_ESCAPE.test(css.charAt(next)) ) { while ( RE_HEX_ESCAPE.test(css.charAt(next + 1)) ) { next += 1; } if ( css.charCodeAt(next + 1) === SPACE ) { next += 1; } } } currentToken = ['word', css.slice(pos, next + 1), line, pos - offset, line, next - offset ]; pos = next; break; default: if ( code === SLASH && css.charCodeAt(pos + 1) === ASTERISK ) { next = css.indexOf('*/', pos + 2) + 1; if ( next === 0 ) { if ( ignore ) { next = css.length; } else { unclosed('comment'); } } content = css.slice(pos, next + 1); lines = content.split('\n'); last = lines.length - 1; if ( last > 0 ) { nextLine = line + last; nextOffset = next - lines[last].length; } else { nextLine = line; nextOffset = offset; } currentToken = ['comment', content, line, pos - offset, nextLine, next - nextOffset ]; offset = nextOffset; line = nextLine; pos = next; } else if ( code === SLASH && css.charCodeAt(pos + 1) === SLASH ) { next = css.indexOf('\n', pos + 2); if (next === -1) { next = css.length - 1 } else { next = next - 1 } content = '/*' + css.slice(pos + 2, next + 1); content = content.replace(/\*\//g, '*\\/') + ' */' currentToken = ['comment', content, line, pos - offset, line, next - offset ]; pos = next; } else { RE_WORD_END.lastIndex = pos + 1; RE_WORD_END.test(css); if ( RE_WORD_END.lastIndex === 0 ) { next = css.length - 1; } else { next = RE_WORD_END.lastIndex - 2; } currentToken = ['word', css.slice(pos, next + 1), line, pos - offset, line, next - offset ]; buffer.push(currentToken); pos = next; } break; } pos++; return currentToken; } function back(token) { returned.push(token); } return { back, nextToken, endOfFile }; }