Skip to content

Commit f9eb07f

Browse files
joephelaavo
andauthored
Fe 1316 rule no conditional css (#50)
* Add rule no-conditional-css-prop * break into new package * remove unused peer deps * Apply suggestions from code review Co-authored-by: Anna Vo <[email protected]> * apply pr feedback * wording tweak --------- Co-authored-by: Anna Vo <[email protected]>
1 parent bd529a2 commit f9eb07f

11 files changed

+240
-0
lines changed

.changeset/ninety-flowers-promise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-config-widen-emotion': minor
3+
---
4+
5+
Add a new package for emotion specific rules.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# eslint-config-widen-emotion
2+
3+
Widen's shared ESLint config for Emotion.
4+
5+
## Installation
6+
7+
```bash
8+
yarn add -D eslint eslint-plugin-emotion
9+
```
10+
11+
## Usage
12+
13+
In your `eslint.config.mjs` file, add the following four entries to your extends
14+
list. If you don't need a specific configuration, simply remove it from the
15+
list.
16+
17+
```js
18+
import emotion from 'eslint-config-widen-emotion'
19+
20+
export default [
21+
...emotion,
22+
...[
23+
// you can specify what to ignore by using the `ignores` key before any other rule
24+
// this will filter out things we don't want this to run on
25+
{ ignores: ['*.test.*'] },
26+
// you can also override rules by specifying the rule and the new value
27+
{ files: ['*.spec.js'], rules: { 'no-unused-vars': 'off' } },
28+
],
29+
]
30+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"author": "Widen",
3+
"dependencies": {
4+
"eslint-config-prettier": "^9.1.0"
5+
},
6+
"description": "Widen's shared ESLint config for Emotion.",
7+
"exports": {
8+
".": "./lib/index.js"
9+
},
10+
"files": [
11+
"lib"
12+
],
13+
"type": "module",
14+
"homepage": "https://github.com/Widen/eslint-config/tree/master/packages/eslint-config-widen-emotion#readme",
15+
"license": "ISC",
16+
"name": "eslint-config-widen-emotion",
17+
"peerDependencies": {
18+
"eslint": ">= 9"
19+
},
20+
"repository": {
21+
"directory": "packages/eslint-config-widen-emotion",
22+
"type": "git",
23+
"url": "https://github.com/Widen/eslint-config"
24+
},
25+
"version": "1.0.0"
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import noConditionalCssProp from './rules/no-conditional-css-prop.js'
2+
3+
export default {
4+
rules: {
5+
'custom/no-conditional-css-prop': noConditionalCssProp,
6+
},
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { Rule } from 'eslint'
3+
4+
function detectConditionalExpression(
5+
expression: any,
6+
context: Rule.RuleContext,
7+
node: any,
8+
) {
9+
if (expression) {
10+
if (
11+
expression.type === 'ConditionalExpression' ||
12+
(expression.type === 'LogicalExpression' &&
13+
(expression.operator === '&&' || expression.operator === '||'))
14+
) {
15+
context.report({
16+
message:
17+
'Avoid using conditionals within the css prop, move them to style.',
18+
node: node,
19+
})
20+
}
21+
}
22+
}
23+
24+
export default {
25+
create(context) {
26+
return {
27+
JSXAttribute(node: any) {
28+
if (node.name.name === 'css') {
29+
if (node.value && node.value.type === 'JSXExpressionContainer') {
30+
const expression = node.value.expression
31+
32+
// Check if it's an array
33+
if (expression.type === 'ArrayExpression') {
34+
expression.elements.forEach((element: any) => {
35+
detectConditionalExpression(element, context, node)
36+
})
37+
} else {
38+
// Check single expressions
39+
detectConditionalExpression(expression, context, node)
40+
}
41+
}
42+
}
43+
},
44+
}
45+
},
46+
meta: {
47+
docs: {
48+
category: 'Best Practices',
49+
description:
50+
'Disallow conditionals within the css prop, move them to the style prop. This increases the render performance.',
51+
example: `
52+
// Before
53+
<div css={[randomDivStyle, isRed ? {color: "red"} : null]} />
54+
55+
// After
56+
<div css={randomDivStyle} style={isRed ? {color: "red"}} />
57+
`,
58+
recommended: false,
59+
},
60+
schema: [],
61+
type: 'suggestion',
62+
},
63+
} as Rule.RuleModule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import globals from 'globals'
2+
3+
// delete an invalid global that causes error because of the trailing space
4+
const browser = globals.browser
5+
delete browser['AudioWorkletGlobalScope ']
6+
7+
const sharedGlobals = {
8+
...browser,
9+
...globals.es6,
10+
...globals.node,
11+
...globals.jest,
12+
}
13+
14+
export default sharedGlobals
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
declare module 'globals'
2+
declare module 'no-conditional-css-prop'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { RuleTester } from 'eslint'
2+
import rule from '../src/rules/no-conditional-css-prop'
3+
import sharedGlobals from '../src/sharedGlobals'
4+
5+
const ruleTester = new RuleTester({
6+
languageOptions: {
7+
globals: sharedGlobals,
8+
parserOptions: {
9+
ecmaFeatures: {
10+
jsx: true,
11+
},
12+
},
13+
},
14+
})
15+
16+
ruleTester.run('no-conditional-css-prop', rule, {
17+
invalid: [
18+
{
19+
code: '<div css={condition ? { color: "red" } : { color: "blue" }} />',
20+
errors: [
21+
{
22+
message:
23+
'Avoid using conditionals within the css prop, move them to style.',
24+
},
25+
],
26+
},
27+
{
28+
code: '<div css={condition && { color: "red" }} />',
29+
errors: [
30+
{
31+
message:
32+
'Avoid using conditionals within the css prop, move them to style.',
33+
},
34+
],
35+
},
36+
{
37+
code: '<div css={condition || { color: "blue" }} />',
38+
errors: [
39+
{
40+
message:
41+
'Avoid using conditionals within the css prop, move them to style.',
42+
},
43+
],
44+
},
45+
{
46+
code: '<div css={[{color: `red`}, true && {background: `black`}]} />',
47+
errors: [
48+
{
49+
message:
50+
'Avoid using conditionals within the css prop, move them to style.',
51+
},
52+
],
53+
},
54+
],
55+
valid: [
56+
{
57+
code: '<div css={{ color: "red" }} />',
58+
},
59+
{
60+
code: '<div css={someVariable} />',
61+
},
62+
{
63+
code: '<div css={`color: ${someVariable}`} />',
64+
},
65+
{
66+
code: '<div css={[{color: `red`}, {background: `black`}]} />',
67+
},
68+
],
69+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "lib",
5+
"rootDir": "src",
6+
"module": "esnext",
7+
"esModuleInterop": true,
8+
"target": "es2016"
9+
},
10+
"paths": {
11+
"@/*": ["./src/*"]
12+
}
13+
}

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"references": [
44
{ "path": "packages/eslint-config-widen" },
55
{ "path": "packages/eslint-config-widen-base" },
6+
{ "path": "packages/eslint-config-widen-emotion" },
67
{ "path": "packages/eslint-config-widen-jest" },
78
{ "path": "packages/eslint-config-widen-playwright" },
89
{ "path": "packages/eslint-config-widen-react" },

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -3704,6 +3704,16 @@ __metadata:
37043704
languageName: unknown
37053705
linkType: soft
37063706

3707+
"eslint-config-widen-emotion@workspace:packages/eslint-config-widen-emotion":
3708+
version: 0.0.0-use.local
3709+
resolution: "eslint-config-widen-emotion@workspace:packages/eslint-config-widen-emotion"
3710+
dependencies:
3711+
eslint-config-prettier: ^9.1.0
3712+
peerDependencies:
3713+
eslint: ">= 9"
3714+
languageName: unknown
3715+
linkType: soft
3716+
37073717
"eslint-config-widen-jest@workspace:packages/eslint-config-widen-jest":
37083718
version: 0.0.0-use.local
37093719
resolution: "eslint-config-widen-jest@workspace:packages/eslint-config-widen-jest"

0 commit comments

Comments
 (0)