All files / src index.js

95.74% Statements 45/47
88.24% Branches 15/17
100% Functions 4/4
100% Lines 44/44
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 1143x 3x   3x 3x 3x 3x 3x             3x             3x   3x     44x 44x                               44x 44x           44x   1784x 1784x 44x 69x       44x   44x 69x   30x     39x         1784x   1784x 10x 10x     1774x 132x 132x   132x 132x 132x 132x 725x           44x       44x 44x 156x 156x           156x 156x     44x      
const babylon = require('babylon')
const traverse = require('babel-traverse').default
 
const isStyled = require('./utils/styled').isStyled
const isHelper = require('./utils/styled').isHelper
const isStyledImport = require('./utils/styled').isStyledImport
const getCSS = require('./utils/general').getCSS
const getKeyframes = require('./utils/general').getKeyframes
 
// TODO Make it work for the UMD build, i.e. global vars
// TODO ENFORCE THESE RULES
// value-no-vendor-prefix – don't allow vendor prefixes
// property-no-vendor-prefix – don't allow vendor prefixes
 
const ignoredRules = [
  // Don't throw if there's no styled-components in a file
  'no-empty-source',
  // We don't care about end-of-source newlines, users cannot control them
  'no-missing-end-of-source-newline',
]
 
const sourceMapsCorrections = {}
 
module.exports = (/* options */) => ({
  // Get string for stylelint to lint
  code(input, filepath) {
    sourceMapsCorrections[filepath] = {}
    const ast = babylon.parse(input, {
      sourceType: 'module',
      plugins: [
        'jsx',
        'flow',
        'objectRestSpread',
        'decorators',
        'classProperties',
        'exportExtensions',
        'asyncGenerators',
        'functionBind',
        'functionSent',
        'dynamicImport',
      ],
    })
 
    let extractedCSS = ''
    const importedNames = {
      default: false,
      css: false,
      keyframes: false,
      injectGlobal: false,
    }
    traverse(ast, {
      enter(path) {
        const node = path.node
        if (isStyledImport(node)) {
          const imports = node.specifiers.filter((specifier) => (
            specifier.type === 'ImportDefaultSpecifier'
            || specifier.type === 'ImportSpecifier'
          ))
 
          Iif (imports.length <= 0) return
 
          imports.forEach((singleImport) => {
            if (singleImport.imported) {
              // Is helper method
              importedNames[singleImport.imported.name] = singleImport.local.name
            } else {
              // Is default import
              importedNames.default = singleImport.local.name
            }
          })
        }
 
        const helper = isHelper(node, importedNames)
 
        if (helper === 'keyframes') {
          extractedCSS += getKeyframes(node)
          return
        }
 
        if (isStyled(node, importedNames.default) || helper === 'injectGlobal' || helper === 'css') {
          const css = getCSS(node)
          extractedCSS += css
          // Save which line in the extracted CSS is which line in the source
          const fullCSSLength = extractedCSS.split(/\n/).length
          const currentCSSLength = css.split(/\n/).length
          const currentCSSStart = (fullCSSLength - currentCSSLength) + 1
          for (let i = 0; i < currentCSSLength + 1; i++) {
            sourceMapsCorrections[filepath][currentCSSStart + i] = node.loc.start.line + i
          }
        }
      },
    })
 
    return extractedCSS
  },
  // Fix sourcemaps
  result(stylelintResult, filepath) {
    const lineCorrection = sourceMapsCorrections[filepath]
    const newWarnings = stylelintResult.warnings.reduce((prevWarnings, warning) => {
      Iif (ignoredRules.includes(warning.rule)) return prevWarnings
      const correctedWarning = Object.assign(warning, {
        // Replace "brace" with "backtick" in warnings, e.g.
        // "Unexpected empty line before closing backtick" (instead of "brace")
        text: warning.text.replace(/brace/, 'backtick'),
        line: lineCorrection[warning.line],
      })
      prevWarnings.push(correctedWarning)
      return prevWarnings
    }, [])
 
    return Object.assign(stylelintResult, { warnings: newWarnings })
  },
})