-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): add top-level-styles eslint rule (#595)
* Add top-level-styles eslint rule * Change files * code review feedback
- Loading branch information
Showing
7 changed files
with
292 additions
and
45 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@griffel-eslint-plugin-dd20b1e1-c272-409a-95f9-19ca824187ce.json
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,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "feat: add top-level-styles eslint rule", | ||
"packageName": "@griffel/eslint-plugin", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
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,62 @@ | ||
# Ensure `makeStyles`, `makeResetStyles` and `makeStaticStyles` are only called at the top-level of a file | ||
|
||
This rule discourages developers from misusing the `makeStyles`, `makeResetStyles` and `makeStaticStyles` methods. These methods don't allow passing runtime values for constructing the styles, as described here under [limitations](https://griffel.js.org/react/guides/). To encourage proper usage, this rule only permits calling those methods in the _top-level scope_ of a file, where it's far less likely developers will pass runtime values. | ||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
// filename: component.tsx | ||
import { makeStyles } from '@griffel/react'; | ||
|
||
export const getStyles = () => { | ||
const useStyles = makeStyles({ | ||
root: { | ||
backgroundColor: 'red', | ||
}, | ||
}); | ||
|
||
return useStyles; | ||
} | ||
``` | ||
|
||
```js | ||
// filename: component.tsx | ||
import { makeStyles } from '@fluentui/react-components'; | ||
|
||
export class MyClass { | ||
getStyles () { | ||
const styles = makeStyles({ | ||
root: { | ||
backgroundColor: 'red', | ||
}, | ||
}); | ||
|
||
return styles; | ||
} | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
import { makeStyles } from '@griffel/react'; | ||
|
||
export const useStyles = makeStyles({ | ||
root: { | ||
backgroundColor: 'red', | ||
}, | ||
}); | ||
``` | ||
|
||
```js | ||
import { makeStyles } from '@fluentui/react-components'; | ||
import { generateStyles } from './custom-css'; | ||
|
||
export const useStyles = generateStyles(makeStyles({ | ||
root: { | ||
backgroundColor: 'red', | ||
}, | ||
})); | ||
``` |
116 changes: 116 additions & 0 deletions
116
packages/eslint-plugin/src/rules/top-level-styles.test.ts
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,116 @@ | ||
import { TSESLint } from '@typescript-eslint/utils'; | ||
import { stylesFileRule, RULE_NAME } from './top-level-styles'; | ||
import * as path from 'path'; | ||
|
||
const componentFileName = 'packages/components/components-foo/foo.tsx'; | ||
|
||
const ruleTester = new TSESLint.RuleTester({ | ||
parser: path.resolve('./node_modules/@typescript-eslint/parser/dist'), | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
ruleTester.run(RULE_NAME, stylesFileRule, { | ||
valid: [ | ||
{ | ||
code: ` | ||
import { makeStyles } from '@fluentui/react-components'; | ||
export const useStyles = makeStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
`, | ||
filename: componentFileName, | ||
}, | ||
{ | ||
code: ` | ||
import { makeStyles } from 'unknown-package'; | ||
export function getStyles() { | ||
const useStyles = makeStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
return useStyles; | ||
} | ||
`, | ||
filename: componentFileName, | ||
}, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: ` | ||
import { makeStyles } from '@fluentui/react-components'; | ||
function getStyles() { | ||
const useStyles = makeStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
return useStyles; | ||
} | ||
`, | ||
errors: [{ messageId: 'foundInvalidUsage', data: { methodName: 'makeStyles' } }], | ||
filename: componentFileName, | ||
}, | ||
{ | ||
code: ` | ||
import { makeStaticStyles } from '@fluentui/react-components'; | ||
function getStyles() { | ||
const useStyles = makeStaticStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
return useStyles; | ||
} | ||
`, | ||
errors: [{ messageId: 'foundInvalidUsage', data: { methodName: 'makeStaticStyles' } }], | ||
filename: componentFileName, | ||
}, | ||
{ | ||
code: ` | ||
import { makeResetStyles } from '@fluentui/react-components'; | ||
function getStyles() { | ||
const useStyles = makeResetStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
return useStyles; | ||
} | ||
`, | ||
errors: [{ messageId: 'foundInvalidUsage', data: { methodName: 'makeResetStyles' } }], | ||
filename: componentFileName, | ||
}, | ||
{ | ||
code: ` | ||
import { makeStyles } from '@griffel/react'; | ||
const getStyles = () => { | ||
const useStyles = makeStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
return useStyles; | ||
}; | ||
`, | ||
errors: [{ messageId: 'foundInvalidUsage', data: { methodName: 'makeStyles' } }], | ||
filename: componentFileName, | ||
}, | ||
{ | ||
code: ` | ||
import { makeStyles } from '@fluentui/react-components'; | ||
class MyClass { | ||
constructor() { | ||
const useStyles = makeStyles({ | ||
root: { color: 'blue' }, | ||
}); | ||
} | ||
} | ||
`, | ||
errors: [{ messageId: 'foundInvalidUsage', data: { methodName: 'makeStyles' } }], | ||
filename: componentFileName, | ||
}, | ||
], | ||
}); |
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,51 @@ | ||
import { ESLintUtils } from '@typescript-eslint/utils'; | ||
|
||
import { getMakeStylesCallExpression, isMakeStylesImport } from '../utils/helpers'; | ||
import { getDocsUrl } from '../utils/getDocsUrl'; | ||
|
||
export const RULE_NAME = 'top-level-styles'; | ||
|
||
export const stylesFileRule = ESLintUtils.RuleCreator(getDocsUrl)({ | ||
name: RULE_NAME, | ||
meta: { | ||
docs: { | ||
description: | ||
'Ensure makeStyles(), makeResetStyles() and makeStaticStyles() calls are placed in the top level of the file', | ||
recommended: 'recommended', | ||
}, | ||
messages: { | ||
foundInvalidUsage: '`{{ methodName }}` should be only be called from the top-level of the file', | ||
}, | ||
type: 'problem', | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
let isMakeStylesImported = false; | ||
|
||
return { | ||
ImportDeclaration(node) { | ||
if (!isMakeStylesImported) { | ||
isMakeStylesImported = isMakeStylesImport(node); | ||
} | ||
}, | ||
|
||
'FunctionDeclaration CallExpression, ArrowFunctionExpression CallExpression, FunctionExpression CallExpression': | ||
function (node) { | ||
if (!isMakeStylesImported) { | ||
return; | ||
} | ||
const methodName = getMakeStylesCallExpression(node, 'makeStyles', 'makeStaticStyles', 'makeResetStyles'); | ||
if (methodName) { | ||
context.report({ | ||
messageId: 'foundInvalidUsage', | ||
data: { | ||
methodName, | ||
}, | ||
node, | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |
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