All files / plugins no-undefined-vars.js

92.5% Statements 37/40
54.55% Branches 6/11
88.89% Functions 8/9
94.87% Lines 37/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 803x 3x 3x 3x 3x   3x 3x 4x       3x       3x   3x 6x       6x   6x 6x     6x   6x 6x 6x 6x     6x     6x 6x 2x                         3x 3x     6x 6x 1x   1x 2x 2x 286x 286x       1x           3x 3x  
const fs = require('fs')
const stylelint = require('stylelint')
const matchAll = require('string.prototype.matchall')
const globby = require('globby')
const TapMap = require('tap-map')
 
const ruleName = 'primer/no-undefined-vars'
const messages = stylelint.utils.ruleMessages(ruleName, {
  rejected: varName => `${varName} is not defined`
})
 
// Match CSS variable definitions (e.g. --color-text-primary:)
const variableDefinitionRegex = /(--[\w|-]*):/g
 
// Match CSS variable references (e.g var(--color-text-primary))
// eslint-disable-next-line no-useless-escape
const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g
 
module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}) => {
  Iif (!enabled) {
    return noop
  }
 
  const {files = ['**/*.scss', '!node_modules'], verbose = false} = options
  // eslint-disable-next-line no-console
  const log = verbose ? (...args) => console.warn(...args) : noop
  const definedVariables = getDefinedVariables(files, log)
 
  // Keep track of declarations we've already seen
  const seen = new WeakMap()
 
  return (root, result) => {
    root.walkRules(rule => {
      rule.walkDecls(decl => {
        Iif (seen.has(decl)) {
          return
        } else {
          seen.set(decl, true)
        }
 
        for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) {
          if (!definedVariables.has(variableName)) {
            stylelint.utils.report({
              message: messages.rejected(variableName),
              node: decl,
              result,
              ruleName
            })
          }
        }
      })
    })
  }
})
 
const cwd = process.cwd()
const cache = new TapMap()
 
function getDefinedVariables(files, log) {
  const cacheKey = JSON.stringify({files, cwd})
  return cache.tap(cacheKey, () => {
    const definedVariables = new Set()
 
    for (const file of globby.sync(files)) {
      const css = fs.readFileSync(file, 'utf-8')
      for (const [, variableName] of matchAll(css, variableDefinitionRegex)) {
        log(`${variableName} defined in ${file}`)
        definedVariables.add(variableName)
      }
    }
 
    return definedVariables
  })
}
 
function noop() {}
 
module.exports.ruleName = ruleName
module.exports.messages = messages