Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
view-container / src / vendor / postcss / parser.ts
Size: Mime:
import Declaration from './declaration'
import tokenizer from './tokenize'
import Comment from './comment'
import AtRule from './at-rule'
import Root from './root'
import Rule from './rule'

export default class Parser {
  input: any
  pos: number
  root: Root
  current: Root | any
  semicolon: boolean
  spaces: string
  tokens: any

  constructor(input: any) {
    this.input = input

    this.pos = 0
    this.root = new Root()
    this.current = this.root
    this.spaces = ''
    this.semicolon = false

    this.root.source = { input, start: { line: 1, column: 1 } }
  }

  tokenize() {
    this.tokens = tokenizer(this.input)
  }

  loop() {
    let token
    while (this.pos < this.tokens.length) {
      token = this.tokens[this.pos]

      switch (token[0]) {
        case 'space':
        case ';':
          this.spaces += token[1]
          break

        case '}':
          this.end(token)
          break

        case 'comment':
          this.comment(token)
          break

        case 'at-word':
          this.atrule(token)
          break

        case '{':
          this.emptyRule(token)
          break

        default:
          this.other()
          break
      }

      this.pos += 1
    }
    this.endFile()
  }

  comment(token: string | string[]) {
    const node = new Comment()
    this.init(node, token[2], token[3])
    node.source.end = { line: token[4], column: token[5] }

    const text = token[1].slice(2, -2)
    if (/^\s*$/.test(text)) {
      node.text = ''
      node.raws.left = text
      node.raws.right = ''
    } else {
      const match = text.match(/^(\s*)([^]*[^\s])(\s*)$/)
      node.text = match[2]
      node.raws.left = match[1]
      node.raws.right = match[3]
    }
  }

  emptyRule(token: string | string[]) {
    const node = new Rule()
    this.init(node, token[2], token[3])
    node.selector = ''
    node.raws.between = ''
    this.current = node
  }

  other() {
    let token
    let end = false
    let type = null
    let colon = false
    let bracket = null
    let brackets = []

    let start = this.pos
    while (this.pos < this.tokens.length) {
      token = this.tokens[this.pos]
      type = token[0]

      if (type === '(' || type === '[') {
        if (!bracket) bracket = token
        brackets.push(type === '(' ? ')' : ']')
      } else if (brackets.length === 0) {
        if (type === ';') {
          if (colon) {
            this.decl(this.tokens.slice(start, this.pos + 1))
            return
          } else {
            break
          }
        } else if (type === '{') {
          this.rule(this.tokens.slice(start, this.pos + 1))
          return
        } else if (type === '}') {
          this.pos -= 1
          end = true
          break
        } else if (type === ':') {
          colon = true
        }
      } else if (type === brackets[brackets.length - 1]) {
        brackets.pop()
        if (brackets.length === 0) bracket = null
      }

      this.pos += 1
    }
    if (this.pos === this.tokens.length) {
      this.pos -= 1
      end = true
    }

    if (brackets.length > 0) this.unclosedBracket(bracket)

    if (end && colon) {
      while (this.pos > start) {
        token = this.tokens[this.pos][0]
        if (token !== 'space' && token !== 'comment') break
        this.pos -= 1
      }
      this.decl(this.tokens.slice(start, this.pos + 1))
      return
    }

    this.unknownWord(start)
  }

  rule(tokens) {
    tokens.pop()

    let node = new Rule()
    this.init(node, tokens[0][2], tokens[0][3])

    node.raws.between = this.spacesFromEnd(tokens)
    this.raw(node, 'selector', tokens)
    this.current = node
  }

  decl(tokens: any[]) {
    const node = new Declaration()
    this.init(node)

    let last = tokens[tokens.length - 1]
    if (last[0] === ';') {
      this.semicolon = true
      tokens.pop()
    }
    if (last[4]) {
      node.source.end = { line: last[4], column: last[5] }
    } else {
      node.source.end = { line: last[2], column: last[3] }
    }

    while (tokens[0][0] !== 'word') {
      node.raws.before += tokens.shift()[1]
    }
    node.source.start = { line: tokens[0][2], column: tokens[0][3] }

    node.prop = ''
    while (tokens.length) {
      let type = tokens[0][0]
      if (type === ':' || type === 'space' || type === 'comment') {
        break
      }
      node.prop += tokens.shift()[1]
    }

    node.raws.between = ''

    let token
    while (tokens.length) {
      token = tokens.shift()

      if (token[0] === ':') {
        node.raws.between += token[1]
        break
      } else {
        node.raws.between += token[1]
      }
    }

    if (node.prop[0] === '_' || node.prop[0] === '*') {
      node.raws.before += node.prop[0]
      node.prop = node.prop.slice(1)
    }
    node.raws.between += this.spacesFromStart(tokens)
    this.precheckMissedSemicolon(tokens)

    for (let i = tokens.length - 1; i > 0; i--) {
      token = tokens[i]
      if (token[1] === '!important') {
        node.important = true
        let string = this.stringFrom(tokens, i)
        string = this.spacesFromEnd(tokens) + string
        if (string !== ' !important') node.raws.important = string
        break
      } else if (token[1] === 'important') {
        let cache = tokens.slice(0)
        let str = ''
        for (let j = i; j > 0; j--) {
          let type = cache[j][0]
          if (str.trim().indexOf('!') === 0 && type !== 'space') {
            break
          }
          str = cache.pop()[1] + str
        }
        if (str.trim().indexOf('!') === 0) {
          node.important = true
          node.raws.important = str
          tokens = cache
        }
      }

      if (token[0] !== 'space' && token[0] !== 'comment') {
        break
      }
    }

    this.raw(node, 'value', tokens)

    if (node.value.indexOf(':') !== -1) this.checkMissedSemicolon(tokens)
  }

  atrule(token) {
    let node = new AtRule()
    node.name = token[1].slice(1)
    if (node.name === '') {
      this.unnamedAtrule(node, token)
    }
    this.init(node, token[2], token[3])

    let last = false
    let open = false
    let params = []

    this.pos += 1
    while (this.pos < this.tokens.length) {
      token = this.tokens[this.pos]

      if (token[0] === ';') {
        node.source.end = { line: token[2], column: token[3] }
        this.semicolon = true
        break
      } else if (token[0] === '{') {
        open = true
        break
      } else if (token[0] === '}') {
        this.end(token)
        break
      } else {
        params.push(token)
      }

      this.pos += 1
    }
    if (this.pos === this.tokens.length) {
      last = true
    }

    node.raws.between = this.spacesFromEnd(params)
    if (params.length) {
      node.raws.afterName = this.spacesFromStart(params)
      this.raw(node, 'params', params)
      if (last) {
        token = params[params.length - 1]
        node.source.end = { line: token[4], column: token[5] }
        this.spaces = node.raws.between
        node.raws.between = ''
      }
    } else {
      node.raws.afterName = ''
      node.params = ''
    }

    if (open) {
      node.nodes = []
      this.current = node
    }
  }

  end(token) {
    if (this.current.nodes && this.current.nodes.length) {
      this.current.raws.semicolon = this.semicolon
    }
    this.semicolon = false

    this.current.raws.after = (this.current.raws.after || '') + this.spaces
    this.spaces = ''

    if (this.current.parent) {
      this.current.source.end = { line: token[2], column: token[3] }
      this.current = this.current.parent
    } else {
      this.unexpectedClose(token)
    }
  }

  endFile() {
    if (this.current.parent) this.unclosedBlock()
    if (this.current.nodes && this.current.nodes.length) {
      this.current.raws.semicolon = this.semicolon
    }
    this.current.raws.after = (this.current.raws.after || '') + this.spaces
  }

  // Helpers

  init(node, line, column) {
    this.current.push(node)

    node.source = { start: { line, column }, input: this.input }
    node.raws.before = this.spaces
    this.spaces = ''
    if (node.type !== 'comment') this.semicolon = false
  }

  raw(node, prop, tokens) {
    let token, type
    let length = tokens.length
    let value = ''
    let clean = true
    for (let i = 0; i < length; i += 1) {
      token = tokens[i]
      type = token[0]
      if (type === 'comment' || (type === 'space' && i === length - 1)) {
        clean = false
      } else {
        value += token[1]
      }
    }
    if (!clean) {
      let raw = tokens.reduce((all, i) => all + i[1], '')
      node.raws[prop] = { value, raw }
    }
    node[prop] = value
  }

  spacesFromEnd(tokens) {
    let lastTokenType
    let spaces = ''
    while (tokens.length) {
      lastTokenType = tokens[tokens.length - 1][0]
      if (lastTokenType !== 'space' && lastTokenType !== 'comment') break
      spaces = tokens.pop()[1] + spaces
    }
    return spaces
  }

  spacesFromStart(tokens) {
    let next
    let spaces = ''
    while (tokens.length) {
      next = tokens[0][0]
      if (next !== 'space' && next !== 'comment') break
      spaces += tokens.shift()[1]
    }
    return spaces
  }

  stringFrom(tokens, from) {
    let result = ''
    for (let i = from; i < tokens.length; i++) {
      result += tokens[i][1]
    }
    tokens.splice(from, tokens.length - from)
    return result
  }

  colon(tokens) {
    let brackets = 0
    let token, type, prev
    for (let i = 0; i < tokens.length; i++) {
      token = tokens[i]
      type = token[0]

      if (type === '(') {
        brackets += 1
      } else if (type === ')') {
        brackets -= 1
      } else if (brackets === 0 && type === ':') {
        if (!prev) {
          this.doubleColon(token)
        } else if (prev[0] === 'word' && prev[1] === 'progid') {
          continue
        } else {
          return i
        }
      }

      prev = token
    }
    return false
  }

  // Errors

  unclosedBracket(bracket) {
    throw this.input.error('Unclosed bracket', bracket[2], bracket[3])
  }

  unknownWord(start) {
    let token = this.tokens[start]
    throw this.input.error('Unknown word', token[2], token[3])
  }

  unexpectedClose(token) {
    throw this.input.error('Unexpected }', token[2], token[3])
  }

  unclosedBlock() {
    let pos = this.current.source.start
    throw this.input.error('Unclosed block', pos.line, pos.column)
  }

  doubleColon(token) {
    throw this.input.error('Double colon', token[2], token[3])
  }

  unnamedAtrule(node, token) {
    throw this.input.error('At-rule without name', token[2], token[3])
  }

  precheckMissedSemicolon(tokens) {
    // Hook for Safe Parser
    tokens
  }

  checkMissedSemicolon(tokens) {
    let colon = this.colon(tokens)
    if (colon === false) return

    let founded = 0
    let token
    for (let j = colon - 1; j >= 0; j--) {
      token = tokens[j]
      if (token[0] !== 'space') {
        founded += 1
        if (founded === 2) break
      }
    }
    throw this.input.error('Missed semicolon', token[2], token[3])
  }
}