diff --git a/.changeset/cyan-bugs-cross.md b/.changeset/cyan-bugs-cross.md new file mode 100644 index 00000000..2cf1cae1 --- /dev/null +++ b/.changeset/cyan-bugs-cross.md @@ -0,0 +1,5 @@ +--- +"@primer/stylelint-config": patch +--- + +New rule: safegaurd alpha `display` color tokens diff --git a/__tests__/__fixtures__/color-vars.scss b/__tests__/__fixtures__/color-vars.scss index 9407b38c..06140b90 100644 --- a/__tests__/__fixtures__/color-vars.scss +++ b/__tests__/__fixtures__/color-vars.scss @@ -277,5 +277,8 @@ --color-calendar-graph-day-L3-border: rgba(27, 31, 35, 0.06); --color-calendar-graph-day-L2-border: rgba(27, 31, 35, 0.06); --color-calendar-graph-day-L1-border: rgba(27, 31, 35, 0.06); + --display-blue-fgColor: blue; + --display-blue-bgColor-muted: blue; + --display-yellow-bgColor-emphasis: yellow; } } diff --git a/__tests__/no-display-colors.js b/__tests__/no-display-colors.js new file mode 100644 index 00000000..ef2d9fe1 --- /dev/null +++ b/__tests__/no-display-colors.js @@ -0,0 +1,34 @@ +const path = require('path') +const {messages, ruleName} = require('../plugins/no-display-colors') + +// eslint-disable-next-line no-undef +testRule({ + plugins: ['./plugins/no-display-colors.js'], + ruleName, + config: [ + true, + { + files: [path.join(__dirname, '__fixtures__/color-vars.scss')], + }, + ], + + accept: [ + {code: '.x { color: var(--fgColor-accent); }'}, + {code: '.x { line-height: var(--text-display-lineHeight); }'}, + ], + + reject: [ + { + code: '.x { color: var(--display-blue-fgColor); }', + message: messages.rejected('--display-blue-fgColor'), + line: 1, + column: 6, + }, + { + code: '.x { color: var(--display-yellow-bgColor-emphasis); }', + message: messages.rejected('--display-yellow-bgColor-emphasis'), + line: 1, + column: 6, + }, + ], +}) diff --git a/index.js b/index.js index c97d73e9..a7845ba5 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,7 @@ module.exports = { './plugins/typography', './plugins/utilities', './plugins/new-color-vars-have-fallback', + './plugins/no-display-colors', ], rules: { 'alpha-value-notation': 'number', diff --git a/plugins/no-display-colors.js b/plugins/no-display-colors.js new file mode 100644 index 00000000..51001d95 --- /dev/null +++ b/plugins/no-display-colors.js @@ -0,0 +1,53 @@ +const stylelint = require('stylelint') +const matchAll = require('string.prototype.matchall') + +const ruleName = 'primer/no-display-colors' +const messages = stylelint.utils.ruleMessages(ruleName, { + rejected: varName => `${varName} is in alpha and should be used with caution with approval from the Primer team`, +}) + +// Match CSS variable references (e.g var(--display-blue-fgColor)) +// eslint-disable-next-line no-useless-escape +const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g + +module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}) => { + if (!enabled) { + return noop + } + + const {verbose = false} = options + // eslint-disable-next-line no-console + const log = verbose ? (...args) => console.warn(...args) : noop + + // Keep track of declarations we've already seen + const seen = new WeakMap() + + return (root, result) => { + root.walkRules(rule => { + rule.walkDecls(decl => { + if (seen.has(decl)) { + return + } else { + seen.set(decl, true) + } + + for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) { + log(`Found variable reference ${variableName}`) + if (variableName.match(/^--display-.*/)) { + stylelint.utils.report({ + message: messages.rejected(variableName), + node: decl, + result, + ruleName, + }) + } + } + }) + }) + } +}) + +function noop() {} + +module.exports.ruleName = ruleName +module.exports.messages = messages