| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 | let featureQueries = require('caniuse-lite/data/features/css-featurequeries.js')let { feature } = require('caniuse-lite')let { parse } = require('postcss')let Browsers = require('./browsers')let brackets = require('./brackets')let Value = require('./value')let utils = require('./utils')let data = feature(featureQueries)let supported = []for (let browser in data.stats) {  let versions = data.stats[browser]  for (let version in versions) {    let support = versions[version]    if (/y/.test(support)) {      supported.push(browser + ' ' + version)    }  }}class Supports {  constructor(Prefixes, all) {    this.Prefixes = Prefixes    this.all = all  }  /**   * Return prefixer only with @supports supported browsers   */  prefixer() {    if (this.prefixerCache) {      return this.prefixerCache    }    let filtered = this.all.browsers.selected.filter(i => {      return supported.includes(i)    })    let browsers = new Browsers(      this.all.browsers.data,      filtered,      this.all.options    )    this.prefixerCache = new this.Prefixes(      this.all.data,      browsers,      this.all.options    )    return this.prefixerCache  }  /**   * Parse string into declaration property and value   */  parse(str) {    let parts = str.split(':')    let prop = parts[0]    let value = parts[1]    if (!value) value = ''    return [prop.trim(), value.trim()]  }  /**   * Create virtual rule to process it by prefixer   */  virtual(str) {    let [prop, value] = this.parse(str)    let rule = parse('a{}').first    rule.append({ prop, value, raws: { before: '' } })    return rule  }  /**   * Return array of Declaration with all necessary prefixes   */  prefixed(str) {    let rule = this.virtual(str)    if (this.disabled(rule.first)) {      return rule.nodes    }    let result = { warn: () => null }    let prefixer = this.prefixer().add[rule.first.prop]    prefixer && prefixer.process && prefixer.process(rule.first, result)    for (let decl of rule.nodes) {      for (let value of this.prefixer().values('add', rule.first.prop)) {        value.process(decl)      }      Value.save(this.all, decl)    }    return rule.nodes  }  /**   * Return true if brackets node is "not" word   */  isNot(node) {    return typeof node === 'string' && /not\s*/i.test(node)  }  /**   * Return true if brackets node is "or" word   */  isOr(node) {    return typeof node === 'string' && /\s*or\s*/i.test(node)  }  /**   * Return true if brackets node is (prop: value)   */  isProp(node) {    return (      typeof node === 'object' &&      node.length === 1 &&      typeof node[0] === 'string'    )  }  /**   * Return true if prefixed property has no unprefixed   */  isHack(all, unprefixed) {    let check = new RegExp(`(\\(|\\s)${utils.escapeRegexp(unprefixed)}:`)    return !check.test(all)  }  /**   * Return true if we need to remove node   */  toRemove(str, all) {    let [prop, value] = this.parse(str)    let unprefixed = this.all.unprefixed(prop)    let cleaner = this.all.cleaner()    if (      cleaner.remove[prop] &&      cleaner.remove[prop].remove &&      !this.isHack(all, unprefixed)    ) {      return true    }    for (let checker of cleaner.values('remove', unprefixed)) {      if (checker.check(value)) {        return true      }    }    return false  }  /**   * Remove all unnecessary prefixes   */  remove(nodes, all) {    let i = 0    while (i < nodes.length) {      if (        !this.isNot(nodes[i - 1]) &&        this.isProp(nodes[i]) &&        this.isOr(nodes[i + 1])      ) {        if (this.toRemove(nodes[i][0], all)) {          nodes.splice(i, 2)          continue        }        i += 2        continue      }      if (typeof nodes[i] === 'object') {        nodes[i] = this.remove(nodes[i], all)      }      i += 1    }    return nodes  }  /**   * Clean brackets with one child   */  cleanBrackets(nodes) {    return nodes.map(i => {      if (typeof i !== 'object') {        return i      }      if (i.length === 1 && typeof i[0] === 'object') {        return this.cleanBrackets(i[0])      }      return this.cleanBrackets(i)    })  }  /**   * Add " or " between properties and convert it to brackets format   */  convert(progress) {    let result = ['']    for (let i of progress) {      result.push([`${i.prop}: ${i.value}`])      result.push(' or ')    }    result[result.length - 1] = ''    return result  }  /**   * Compress value functions into a string nodes   */  normalize(nodes) {    if (typeof nodes !== 'object') {      return nodes    }    nodes = nodes.filter(i => i !== '')    if (typeof nodes[0] === 'string') {      let firstNode = nodes[0].trim()      if (        firstNode.includes(':') ||        firstNode === 'selector' ||        firstNode === 'not selector'      ) {        return [brackets.stringify(nodes)]      }    }    return nodes.map(i => this.normalize(i))  }  /**   * Add prefixes   */  add(nodes, all) {    return nodes.map(i => {      if (this.isProp(i)) {        let prefixed = this.prefixed(i[0])        if (prefixed.length > 1) {          return this.convert(prefixed)        }        return i      }      if (typeof i === 'object') {        return this.add(i, all)      }      return i    })  }  /**   * Add prefixed declaration   */  process(rule) {    let ast = brackets.parse(rule.params)    ast = this.normalize(ast)    ast = this.remove(ast, rule.params)    ast = this.add(ast, rule.params)    ast = this.cleanBrackets(ast)    rule.params = brackets.stringify(ast)  }  /**   * Check global options   */  disabled(node) {    if (!this.all.options.grid) {      if (node.prop === 'display' && node.value.includes('grid')) {        return true      }      if (node.prop.includes('grid') || node.prop === 'justify-items') {        return true      }    }    if (this.all.options.flexbox === false) {      if (node.prop === 'display' && node.value.includes('flex')) {        return true      }      let other = ['order', 'justify-content', 'align-items', 'align-content']      if (node.prop.includes('flex') || other.includes(node.prop)) {        return true      }    }    return false  }}module.exports = Supports
 |