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 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 | 3x 3x 3x 3x 3x 3x 3x 12x 3x 3x 3x 3x 13x 13x 13x 13x 13x 13x 15x 6x 13x 2x 2x 2x 2x 6x 2x 4x 4x 13x 11x 11x 11x 11x 11x 3x 3x 13x 13x 1x 1x 1x 1x 3x 3x 3x 286x 286x 3x 2x 2x 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 = /^\s*(--[\w|-]+):/gm // Match CSS variables defined with the color-mode-var mixin (e.g. @include color-mode-var(my-feature, ...)) const colorModeVariableDefinitionRegex = /^\s*@include\s+color-mode-var\s*\(\s*['"]?([^'",]+)['"]?/gm // 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) => { function checkVariable(variableName, node) { if (!definedVariables.has(variableName)) { stylelint.utils.report({ message: messages.rejected(variableName), node, result, ruleName }) } } root.walkAtRules(rule => { Eif (rule.name === 'include' && rule.params.startsWith('color-mode-var')) { const innerMatch = rule.params.match(/^color-mode-var\s*\(\s*(.*)\s*\);?\s*$/) Iif (innerMatch.length !== 2) { return } const [, params] = innerMatch const [, lightValue, darkValue] = params.split(',').map(str => str.trim()) for (const v of [lightValue, darkValue]) { for (const [, variableName] of matchAll(v, variableReferenceRegex)) { checkVariable(variableName, rule) } } } }) root.walkRules(rule => { rule.walkDecls(decl => { Iif (seen.has(decl)) { return } else { seen.set(decl, true) } for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) { checkVariable(variableName, decl) } }) }) } }) const cwd = process.cwd() const cache = new TapMap() function getDefinedVariables(globs, log) { const cacheKey = JSON.stringify({globs, cwd}) return cache.tap(cacheKey, () => { const definedVariables = new Set() const files = globby.sync(globs) log(`Scanning ${files.length} SCSS files for CSS variables`) for (const file of files) { log(`==========\nLooking for CSS variable definitions in ${file}`) const css = fs.readFileSync(file, 'utf-8') for (const [, variableName] of matchAll(css, variableDefinitionRegex)) { log(`${variableName} defined in ${file}`) definedVariables.add(variableName) } for (const [, variableName] of matchAll(css, colorModeVariableDefinitionRegex)) { log(`--color-${variableName} defined via color-mode-var in ${file}`) definedVariables.add(`--color-${variableName}`) } } return definedVariables }) } function noop() {} module.exports.ruleName = ruleName module.exports.messages = messages |