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 | 3x 3x 3x 3x 3x 3x 3x 6x 3x 3x 3x 3x 10x 10x 10x 10x 10x 10x 12x 3x 10x 2x 2x 2x 2x 6x 2x 4x 4x 10x 8x 8x 8x 8x 8x 3x 3x 10x 10x 1x 1x 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 = /(--[\w|-]*):/g // Match CSS variables defined with the color-mode-var mixin (e.g. @include color-mode-var(my-feature, ...)) const colorModeVariableDefinitionRegex = /color-mode-var\s*\(\s*['"]?([^'",]+)['"]?/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) => { 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*$/) 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(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) } 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 |