Skip to content

Commit

Permalink
Create ESLint rule for CSS map (#1500)
Browse files Browse the repository at this point in the history
Also update eslint and @types/eslint
  • Loading branch information
dddlr committed Sep 7, 2023
1 parent ae6cea5 commit 685093a
Show file tree
Hide file tree
Showing 15 changed files with 1,276 additions and 132 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-pens-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/eslint-plugin': minor
---

Add ESLint rule `@compiled/no-invalid-css-map` for linting cssMap usages
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
}
},
"resolutions": {
"@types/eslint-scope": "*",
"css-what": ">=6.1.0",
"nth-check": ">=2.1.1",
"semver": "^7.5.4",
Expand All @@ -76,6 +77,7 @@
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^6.5.16",
"@types/eslint": "^8.44.0",
"@types/jest": "^27.5.2",
"@types/node": "^18.17.14",
"@types/react": "^17.0.62",
Expand All @@ -84,7 +86,7 @@
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"babel-loader": "^9.1.2",
"eslint": "^8.41.0",
"eslint": "^8.44.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-json-files": "^2.2.0",
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const recommended = {
'@compiled/no-keyframes-tagged-template-expression': 'error',
'@compiled/no-styled-tagged-template-expression': 'error',
'@compiled/no-css-prop-without-css-function': 'error',
'@compiled/no-invalid-css-map': 'error',
},
};
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { noCssTaggedTemplateExpressionRule } from './rules/no-css-tagged-templat
import { noEmotionCssRule } from './rules/no-emotion-css';
import { noExportedCssRule } from './rules/no-exported-css';
import { noExportedKeyframesRule } from './rules/no-exported-keyframes';
import { noInvalidCssMapRule } from './rules/no-invalid-css-map';
import { noKeyframesTaggedTemplateExpressionRule } from './rules/no-keyframes-tagged-template-expression';
import { noStyledTaggedTemplateExpressionRule } from './rules/no-styled-tagged-template-expression';

Expand All @@ -17,6 +18,7 @@ export const rules = {
'no-keyframes-tagged-template-expression': noKeyframesTaggedTemplateExpressionRule,
'no-styled-tagged-template-expression': noStyledTaggedTemplateExpressionRule,
'no-css-prop-without-css-function': noCssPropWithoutCssFunctionRule,
'no-invalid-css-map': noInvalidCssMapRule,
};

export const configs = {
Expand Down
197 changes: 197 additions & 0 deletions packages/eslint-plugin/src/rules/no-invalid-css-map/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# `no-invalid-css-map`

Ensure that all usages of the `cssMap` API are valid, and enforces the format of the object that is passed to `cssMap`.

Please refer to [our documentation](https://compiledcssinjs.com/docs/api-cssmap) for more details and some examples.

This is intended to be used in conjunction with type checking (either through TypeScript or Flow).

## Rule Details

Examples of **incorrect** code for this rule:

```js
import React from 'react';
import { cssMap } from '@compiled/react';

// cssMap needs to be declared in the top-most scope.
// (not within a function, class, etc.)

const Foo = () => {
const bar = cssMap({
danger: {
color: 'red',
},
});
};
```

```js
import React from 'react';
import { cssMap } from '@compiled/react';
import { importedVariable, importedFunction } from 'another-package';

// Cannot use imported functions as values in cssMap.

const myVariable = importedFunction();

const styles = cssMap({
danger: {
// Both invalid because they rely on an imported function.
color: myVariable,
padding: importedFunction(),
},
});
```

```js
import React from 'react';
import { cssMap } from '@compiled/react';

// Cannot export usages of cssMap.
// Any usages of cssMap must be in the same file.

export const foo = cssMap({
danger: {
color: 'red',
},
});
```

```js
import React from 'react';
import { cssMap } from '@compiled/react';
import { token } from '@atlaskit/token';

// Functions and object methods are not allowed as
// values in cssMap.

const styles = cssMap({
// Object method
get danger() {
return { color: '#123456' };
},
});

const styles2 = cssMap({
// Arrow function
danger: () => {
color: '#123456';
},
});

function customFunction(...args) {
return arguments.join('');
}

const styles3 = cssMap({
danger: {
// Locally defined function
color: customFunction('red', 'blue'),
backgroundColor: 'red',
},
});
```

```js
import React from 'react';
import { cssMap } from '@compiled/react';

// Spread elements ("...") cannot be used in cssMap.

const base = {
success: {
color: 'green',
},
};

const bar = cssMap({
...base,
danger: {
color: 'red',
},
});
```

Examples of **correct** code for this rule:

```js
import React from 'react';
import { cssMap } from '@compiled/react';

// Literals (strings, numbers, etc.) are used as values
// in cssMap.

const styles = cssMap({
danger: {
color: 'red',
backgroundColor: 'red',
},
success: {
color: 'green',
backgroundColor: 'green',
},
});
```

```js
import React from 'react';
import { cssMap } from '@compiled/react';

// A statically evaluable variable (known at build time)
// is used here.

const bap = 'blue';

const styles = cssMap({
danger: {
color: bap,
},
});
```

### Options

#### `allowedFunctionCalls`: [string, string][]

Normally, this ESLint rule forbids all function calls from being used inside the `cssMap(...)` function call. For example, this would be invalid using default settings:

```js
import React from 'react';
import { cssMap } from '@compiled/react';
import { token } from '@atlaskit/token';

const styles = cssMap({
danger: {
color: token('my-color'),
backgroundColor: 'red',
},
success: {
color: 'green',
},
});
```

If you would like to whitelist certain functions (e.g. `token` from `@atlaskit/token`), you can include the names of the functions as part of the `allowedFunctionCalls` argument. Each function should be represented as a two-element array, with the first element being the package the function is from, and the second element being the name of the function.

For example, with the below configuration, the above code example would be okay.

```js
// .eslintrc.js

// ...
rules: {
'@compiled/no-invalid-css-map': [
'error',
{
allowFunctionCalls: [
['@atlaskit/token', 'token'],
]
},
],
// ...
},
// ...
```

Please note that this ESLint rule only supports whitelisting imports in the form `import { myFunctionOrVariable } from 'my-package'`; we do not currently support whitelisting default imports, so `import myFunctionOrVariable from 'my-package'` would always be invalid when used in `cssMap`. You are welcome to [file an issue](https://github.com/atlassian-labs/compiled/issues) if you need to whitelist default imports.

0 comments on commit 685093a

Please sign in to comment.