Skip to content

Commit

Permalink
Creating stylelint plugin for no deprecated color vars (#99)
Browse files Browse the repository at this point in the history
* Creating stylelint plugin for no deprecated color vars

* Fix import

* Need primitives for dev mode

* Create cyan-gifts-rule.md

* Adding docs

* Adding unfixable test

* Adding plugin

* Lint fix

* Update .changeset/cyan-gifts-rule.md

Co-authored-by: Cole Bemis <colebemis@github.com>

* Use colors main path and config passing in deprecations location.

* Updating warning messages for null, arrays, and normal

* Use caret

* Unused variable

Co-authored-by: Cole Bemis <colebemis@github.com>
  • Loading branch information
jonrohan and colebemis authored Aug 31, 2021
1 parent 3b6be48 commit e83f61c
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-gifts-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint-config-primer": patch
---

Create `no-deprecated-colors` rule
5 changes: 5 additions & 0 deletions __tests__/__fixtures__/deprecations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"text.primary": "fg.default",
"text.secondary": ["fg.one", "fg.two"],
"text.white": null
}
39 changes: 39 additions & 0 deletions __tests__/no-deprecated-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const path = require('path')
const {ruleName} = require('../plugins/no-deprecated-colors')

// eslint-disable-next-line no-undef
testRule({
plugins: ['./plugins/no-deprecated-colors.js'],
ruleName,
config: [
true,
{
deprecatedFile: path.join(__dirname, '__fixtures__/deprecations.json')
}
],
fix: true,
accept: [{code: '.x { color: var(--color-fg-default, var(--color-text-primary)); }'}],
reject: [
{
code: '.x { border: 1px solid var(--color-text-primary); }',
fixed: '.x { border: 1px solid var(--color-fg-default); }',
message: `--color-text-primary is a deprecated color variable. Please use the replacement --color-fg-default. (primer/no-deprecated-colors)`,
line: 1,
column: 6
},
{
code: '.x { border: 1px solid var(--color-text-secondary); }',
unfixable: true,
message: `--color-text-secondary is a deprecated color variable. Please use one of (--color-fg-one, --color-fg-two). (primer/no-deprecated-colors)`,
line: 1,
column: 6
},
{
code: '.x { border: 1px solid var(--color-text-white); }',
unfixable: true,
message: `--color-text-white is a deprecated color variable. Please consult the primer color docs for a replacement. https://primer.style/primitives (primer/no-deprecated-colors)`,
line: 1,
column: 6
}
]
})
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'stylelint-order',
'stylelint-scss',
'./plugins/no-override',
'./plugins/no-deprecated-colors',
'./plugins/no-unused-vars',
'./plugins/no-undefined-vars',
'./plugins/no-scale-colors',
Expand Down Expand Up @@ -100,6 +101,7 @@ module.exports = {
}
],
'primer/no-override': true,
'primer/no-deprecated-colors': true,
// unused vars are not necessarily an error, since they may be referenced
// in other projects
'primer/no-unused-vars': [true, {severity: 'warning'}],
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dependencies": {
"anymatch": "^3.1.1",
"globby": "^11.0.1",
"lodash.kebabcase": "^4.1.1",
"postcss-value-parser": "^4.0.2",
"string.prototype.matchall": "^4.0.2",
"stylelint-no-unsupported-browser-features": "^4.1.4",
Expand All @@ -35,13 +36,15 @@
"tap-map": "^1.0.0"
},
"peerDependencies": {
"@primer/css": "*"
"@primer/css": "*",
"@primer/primitives": ">= 4.6.2"
},
"devDependencies": {
"@changesets/changelog-github": "0.2.7",
"@changesets/cli": "2.11.2",
"@github/prettier-config": "0.0.4",
"@primer/css": "^13.2.0",
"@primer/primitives": "^4.6.7",
"dedent": "0.7.0",
"eslint": "7.32.0",
"eslint-plugin-github": "4.2.0",
Expand Down
19 changes: 19 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This directory contains all of our custom stylelint plugins, each of which provi
- [Usage](#usage)
- [`primer/no-override`](#primerno-override)
- [`primer/no-unused-vars`](#primerno-unused-vars)
- [`primer/no-deprecated-colors`](#primerno-deprecated-colors)
- [`primer/no-undefined-vars`](#primerno-undefined-vars)
- [`primer/no-scale-colors`](#primerno-scale-colors)
- [`primer/colors`](#primercolors)
Expand Down Expand Up @@ -97,6 +98,24 @@ Because there isn't any good way for a stylelint plugin to know all of the files
- `verbose` is a boolean that enables chatty `console.warn()` messages telling you what the plugin found, which can aid in debugging more complicated project layouts.
## `primer/no-deprecated-colors`
This rule identifies deprecated color variables from [primer/primitives]](/~https://github.com/primer/primitives) deprecated.json file and suggests replacements.
```scss
body {
color: var(--color-fg-default);
}
/** ↑
* OK: --color-text-primary is defined */

body {
color: var(--color-text-primary);
}
/**
* FAIL: --color-text-primary is deprecated. */
```
## `primer/no-undefined-vars`
This rule prohibits any usages of undefined CSS variables.
Expand Down
86 changes: 86 additions & 0 deletions plugins/no-deprecated-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const stylelint = require('stylelint')
const kebabCase = require('lodash.kebabcase')
const matchAll = require('string.prototype.matchall')

const ruleName = 'primer/no-deprecated-colors'
const messages = stylelint.utils.ruleMessages(ruleName, {
rejected: (varName, replacement) => {
if (replacement === null) {
return `${varName} is a deprecated color variable. Please consult the primer color docs for a replacement. https://primer.style/primitives`
}

if (Array.isArray(replacement)) {
replacement = replacement.map(r => `--color-${kebabCase(r)}`)
return `${varName} is a deprecated color variable. Please use one of (${replacement.join(', ')}).`
}

replacement = `--color-${kebabCase(replacement)}`
return `${varName} is a deprecated color variable. Please use the replacement ${replacement}.`
}
})

// 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 = {}, context) => {
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()

// eslint-disable-next-line import/no-dynamic-require
const deprecatedColors = require(options.deprecatedFile || '@primer/primitives/dist/deprecations/colors.json')

const convertedCSSVars = Object.entries(deprecatedColors)
.map(([k, v]) => {
return [`--color-${kebabCase(k)}`, v]
})
.reduce((acc, [key, value]) => {
acc[key] = value
return acc
}, {})

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 in convertedCSSVars) {
let replacement = convertedCSSVars[variableName]

if (context.fix && replacement !== null && !Array.isArray(replacement)) {
replacement = `--color-${kebabCase(replacement)}`
decl.value = decl.value.replace(variableName, replacement)
return
}

stylelint.utils.report({
message: messages.rejected(variableName, replacement),
node: decl,
ruleName,
result
})
}
}
})
})
}
})

function noop() {}

module.exports.ruleName = ruleName
module.exports.messages = messages
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,11 @@
dependencies:
object-assign "^4.1.1"

"@primer/primitives@^4.6.7":
version "4.6.7"
resolved "https://registry.yarnpkg.com/@primer/primitives/-/primitives-4.6.7.tgz#4909d5267772a98d422ac76abd393676f5bd6e0f"
integrity sha512-tP9fgLD+M+jtyn8f9EpbMA3qCUrL11iT7R6DKmm6tvqBOSFNRvb0KiQypZdQRwbEvlDrGzj90wr4DSi/2MZVXg==

"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
Expand Down Expand Up @@ -3578,7 +3583,7 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=

lodash.kebabcase@4.1.1:
lodash.kebabcase@4.1.1, lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY=
Expand Down

0 comments on commit e83f61c

Please sign in to comment.