| '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) |
| |
| // SCSS PATCH { |
| const COMMA = ','.charCodeAt(0) |
| const HASH = '#'.charCodeAt(0) |
| // } SCSS PATCH |
| |
| const RE_AT_END = /[\t\n\f\r "#'()/;[\\\]{}]/g |
| const RE_WORD_END = /[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g |
| const RE_BAD_BRACKET = /.[\n"'(/\\]/ |
| const RE_HEX_ESCAPE = /[\da-f]/i |
| |
| const RE_NEW_LINE = /[\n\f\r]/g // SCSS PATCH |
| |
| // SCSS PATCH function name was changed |
| module.exports = function scssTokenize (input, options = {}) { |
| let css = input.css.valueOf() |
| let ignore = options.ignoreErrors |
| |
| let code, next, quote, content, escape |
| let escaped, prev, n, currentToken |
| |
| let length = css.length |
| let pos = 0 |
| let buffer = [] |
| let returned = [] |
| |
| let brackets // SCSS PATCH |
| |
| function position () { |
| return pos |
| } |
| |
| function unclosed (what) { |
| throw input.error('Unclosed ' + what, pos) |
| } |
| |
| function endOfFile () { |
| return returned.length === 0 && pos >= length |
| } |
| |
| // SCSS PATCH { |
| function interpolation () { |
| let deep = 1 |
| let stringQuote = false |
| let stringEscaped = false |
| while (deep > 0) { |
| next += 1 |
| if (css.length <= next) unclosed('interpolation') |
| |
| code = css.charCodeAt(next) |
| n = css.charCodeAt(next + 1) |
| |
| if (stringQuote) { |
| if (!stringEscaped && code === stringQuote) { |
| stringQuote = false |
| stringEscaped = false |
| } else if (code === BACKSLASH) { |
| stringEscaped = !stringEscaped |
| } else if (stringEscaped) { |
| stringEscaped = false |
| } |
| } else if (code === SINGLE_QUOTE || code === DOUBLE_QUOTE) { |
| stringQuote = code |
| } else if (code === CLOSE_CURLY) { |
| deep -= 1 |
| } else if (code === HASH && n === OPEN_CURLY) { |
| deep += 1 |
| } |
| } |
| } |
| // } SCSS PATCH |
| |
| 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 |
| } |
| |
| // SCSS PATCH { |
| case COMMA: { |
| currentToken = ['word', ',', pos, pos + 1] |
| break |
| } |
| // } SCSS PATCH |
| |
| case OPEN_PARENTHESES: { |
| prev = buffer.length ? buffer.pop()[1] : '' |
| n = css.charCodeAt(pos + 1) |
| |
| // SCSS PATCH { |
| if (prev === 'url' && n !== SINGLE_QUOTE && n !== DOUBLE_QUOTE) { |
| brackets = 1 |
| escaped = false |
| next = pos + 1 |
| while (next <= css.length - 1) { |
| n = css.charCodeAt(next) |
| if (n === BACKSLASH) { |
| escaped = !escaped |
| } else if (n === OPEN_PARENTHESES) { |
| brackets += 1 |
| } else if (n === CLOSE_PARENTHESES) { |
| brackets -= 1 |
| if (brackets === 0) break |
| } |
| next += 1 |
| } |
| |
| content = css.slice(pos, next + 1) |
| currentToken = ['brackets', content, pos, next] |
| pos = next |
| // } SCSS PATCH |
| } 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: { |
| // SCSS PATCH { |
| quote = code |
| next = pos |
| |
| escaped = false |
| while (next < length) { |
| next++ |
| if (next === length) unclosed('string') |
| |
| code = css.charCodeAt(next) |
| n = css.charCodeAt(next + 1) |
| |
| if (!escaped && code === quote) { |
| break |
| } else if (code === BACKSLASH) { |
| escaped = !escaped |
| } else if (escaped) { |
| escaped = false |
| } else if (code === HASH && n === OPEN_CURLY) { |
| interpolation() |
| } |
| } |
| // } SCSS PATCH |
| |
| 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: |
| // SCSS PATCH { |
| n = css.charCodeAt(pos + 1) |
| |
| if (code === HASH && n === OPEN_CURLY) { |
| next = pos |
| interpolation() |
| content = css.slice(pos, next + 1) |
| currentToken = ['word', content, pos, next] |
| pos = next |
| } else if (code === SLASH && n === ASTERISK) { |
| // } SCSS PATCH |
| 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 |
| |
| // SCSS PATCH { |
| } else if (code === SLASH && n === SLASH) { |
| RE_NEW_LINE.lastIndex = pos + 1 |
| RE_NEW_LINE.test(css) |
| if (RE_NEW_LINE.lastIndex === 0) { |
| next = css.length - 1 |
| } else { |
| next = RE_NEW_LINE.lastIndex - 2 |
| } |
| |
| content = css.slice(pos, next + 1) |
| currentToken = ['comment', content, pos, next, 'inline'] |
| |
| pos = next |
| // } SCSS PATCH |
| } 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 |
| } |
| } |