| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 | 'use strict'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 = /[\t\n\f\r "#'()/;[\\\]{}]/gconst RE_WORD_END = /[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/gconst RE_BAD_BRACKET = /.[\n"'(/\\]/const RE_HEX_ESCAPE = /[\da-f]/imodule.exports = function tokenizer(input, options = {}) {  let css = input.css.valueOf()  let ignore = options.ignoreErrors  let code, next, quote, content, escape  let escaped, escapePos, prev, n, currentToken  let length = css.length  let pos = 0  let buffer = []  let returned = []  function position() {    return pos  }  function unclosed(what) {    throw input.error('Unclosed ' + what, pos)  }  function endOfFile() {    return returned.length === 0 && pos >= length  }  function nextToken(opts) {    if (returned.length) return returned.pop()    if (pos >= length) return    let ignoreUnclosed = opts ? opts.ignoreUnclosed : false    code = css.charCodeAt(pos)    switch (code) {      case NEWLINE:      case SPACE:      case TAB:      case CR:      case FEED: {        next = pos        do {          next += 1          code = css.charCodeAt(next)        } while (          code === SPACE ||          code === NEWLINE ||          code === TAB ||          code === CR ||          code === FEED        )        currentToken = ['space', css.slice(pos, next)]        pos = next - 1        break      }      case OPEN_SQUARE:      case CLOSE_SQUARE:      case OPEN_CURLY:      case CLOSE_CURLY:      case COLON:      case SEMICOLON:      case CLOSE_PARENTHESES: {        let controlChar = String.fromCharCode(code)        currentToken = [controlChar, controlChar, pos]        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 || ignoreUnclosed) {                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), pos, next]          pos = next        } else {          next = css.indexOf(')', pos + 1)          content = css.slice(pos, next + 1)          if (next === -1 || RE_BAD_BRACKET.test(content)) {            currentToken = ['(', '(', pos]          } else {            currentToken = ['brackets', content, pos, next]            pos = next          }        }        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 || ignoreUnclosed) {              next = pos + 1              break            } else {              unclosed('string')            }          }          escapePos = next          while (css.charCodeAt(escapePos - 1) === BACKSLASH) {            escapePos -= 1            escaped = !escaped          }        } while (escaped)        currentToken = ['string', css.slice(pos, next + 1), pos, next]        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), pos, next]        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), pos, next]        pos = next        break      }      default: {        if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) {          next = css.indexOf('*/', pos + 2) + 1          if (next === 0) {            if (ignore || ignoreUnclosed) {              next = css.length            } else {              unclosed('comment')            }          }          currentToken = ['comment', css.slice(pos, next + 1), pos, next]          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), pos, next]          buffer.push(currentToken)          pos = next        }        break      }    }    pos++    return currentToken  }  function back(token) {    returned.push(token)  }  return {    back,    nextToken,    endOfFile,    position  }}
 |