-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New rule: check the new color CSS vars have a fallback (#122)
* testing new rule to flag new css without fallback * added css vars to jsomn * rename, add index, add docs, remove scale * lint * lint * lint... * fix import * more lint * format * Create wet-lies-visit.md * fix name * oops --------- Co-authored-by: langermank <[email protected]>
- Loading branch information
1 parent
7f4c467
commit 3bc226a
Showing
7 changed files
with
476 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"eslint-plugin-primer-react": patch | ||
--- | ||
|
||
New rule: new-color-css-vars-have-fallback: checks that if a new color var is used, it has a fallback value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
## Ensure new Primitive v8 color CSS vars have a fallback | ||
|
||
This rule is temporary as we begin testing v8 color tokens behind a feature flag. If a color token is used without a fallback, the color will only render if the feature flag is enabled. This rule is an extra safety net to ensure we don't accidentally ship code that relies on the feature flag. | ||
|
||
## Rule Details | ||
|
||
This rule refers to a JSON file that lists all the new color tokens | ||
|
||
```json | ||
["--fgColor-default", "--fgColor-muted", "--fgColor-onEmphasis"] | ||
``` | ||
|
||
If it finds that one of these tokens is used without a fallback, it will throw an error. | ||
|
||
👎 Examples of **incorrect** code for this rule | ||
|
||
```jsx | ||
<Button sx={{color: 'var(--fgColor-muted)'}}>Test</Button> | ||
``` | ||
|
||
👍 Examples of **correct** code for this rule: | ||
|
||
```jsx | ||
<Button sx={{color: 'var(--fgColor-muted, var(--color-fg-muted))'}}>Test</Button> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const rule = require('../new-color-css-vars-have-fallback') | ||
const {RuleTester} = require('eslint') | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}) | ||
|
||
ruleTester.run('new-color-css-vars-have-fallback', rule, { | ||
valid: [ | ||
{ | ||
code: `<circle stroke="var(--fgColor-muted, var(--color-fg-muted))" strokeWidth="2" />`, | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: `<circle stroke="var(--fgColor-muted)" strokeWidth="2" />`, | ||
errors: [ | ||
{ | ||
message: | ||
'Expected a fallback value for CSS variable --fgColor-muted. New color variables fallbacks, check primer.style/primitives to find the correct value.', | ||
}, | ||
], | ||
}, | ||
], | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
const cssVars = require('../utils/new-color-css-vars-map') | ||
|
||
const reportError = (propertyName, valueNode, context) => { | ||
// performance optimisation: exit early | ||
if (valueNode.type !== 'Literal' && valueNode.type !== 'TemplateElement') return | ||
// get property value | ||
const value = valueNode.type === 'Literal' ? valueNode.value : valueNode.value.cooked | ||
// return if value is not a string | ||
if (typeof value !== 'string') return | ||
// return if value does not include variable | ||
if (!value.includes('var(')) return | ||
|
||
const varRegex = /var\([^(),)]+\)/g | ||
|
||
const match = value.match(varRegex) | ||
// return if no matches | ||
if (!match) return | ||
const vars = match.flatMap(match => | ||
match | ||
.slice(4, -1) | ||
.trim() | ||
.split(/\s*,\s*/g), | ||
) | ||
for (const cssVar of vars) { | ||
// return if no repalcement exists | ||
if (!cssVars?.includes(cssVar)) return | ||
// report the error | ||
context.report({ | ||
node: valueNode, | ||
message: `Expected a fallback value for CSS variable ${cssVar}. New color variables fallbacks, check primer.style/primitives to find the correct value.`, | ||
}) | ||
} | ||
} | ||
|
||
const reportOnObject = (node, context) => { | ||
const propertyName = node.key.name | ||
if (node.value?.type === 'Literal') { | ||
reportError(propertyName, node.value, context) | ||
} else if (node.value?.type === 'ConditionalExpression') { | ||
reportError(propertyName, node.value.consequent, context) | ||
reportError(propertyName, node.value.alternate, context) | ||
} | ||
} | ||
|
||
const reportOnProperty = (node, context) => { | ||
const propertyName = node.name.name | ||
if (node.value?.type === 'Literal') { | ||
reportError(propertyName, node.value, context) | ||
} else if (node.value?.type === 'JSXExpressionContainer' && node.value.expression?.type === 'ConditionalExpression') { | ||
reportError(propertyName, node.value.expression.consequent, context) | ||
reportError(propertyName, node.value.expression.alternate, context) | ||
} | ||
} | ||
|
||
const reportOnValue = (node, context) => { | ||
if (node?.type === 'Literal') { | ||
reportError(undefined, node, context) | ||
} else if (node?.type === 'JSXExpressionContainer' && node.expression?.type === 'ConditionalExpression') { | ||
reportError(undefined, node.value.expression.consequent, context) | ||
reportError(undefined, node.value.expression.alternate, context) | ||
} | ||
} | ||
|
||
const reportOnTemplateElement = (node, context) => { | ||
reportError(undefined, node, context) | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
}, | ||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
create(context) { | ||
return { | ||
// sx OR style property on elements | ||
['JSXAttribute:matches([name.name=sx], [name.name=style]) ObjectExpression Property']: node => | ||
reportOnObject(node, context), | ||
// property on element like stroke or fill | ||
['JSXAttribute[name.name!=sx][name.name!=style]']: node => reportOnProperty(node, context), | ||
// variable that is a value | ||
[':matches(VariableDeclarator, ReturnStatement) > Literal']: node => reportOnValue(node, context), | ||
// variable that is a value | ||
[':matches(VariableDeclarator, ReturnStatement) > TemplateElement']: node => | ||
reportOnTemplateElement(node, context), | ||
} | ||
}, | ||
} |
Oops, something went wrong.