Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tailwind-variantsの使い方をチェックするルールを追加 #480

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eslint-plugin-smarthr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [best-practice-for-date](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-date)
- [best-practice-for-layouts](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts)
- [best-practice-for-remote-trigger-dialog](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-remote-trigger-dialog)
- [best-practice-for-tailwind-variants](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants)
- [design-system-guideline-prohibit-double-icons](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-double-icons)
- [format-import-path](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-import-path)
- [format-translate-component](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-translate-component)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# smarthr/best-practice-for-tailwind-variants

- tailwind-variantsの記法をチェックするルールです
- 現状は以下のチェックを行います
- tailwind-variants(tv) のimport時の名称をtvに固定しているか (asなどでの名称変更の禁止)
- tv の実行結果を格納する変数名を統一 (styleGenerator、もしくはxxxStyleGenerator)
- tvで生成した関数の実行をuseMemo hook でメモ化しているか


## rules

```js
{
rules: {
'smarthr/best-practice-for-tailwind-variants': 'error', // 'warn', 'off'
},
}
```

## ❌ Incorrect

```jsx
import { tv as hoge } from 'tailwind-variants'

const xxx = tv({
...
})

...

const style = xxx()
```

## ✅ Correct

```jsx
import { tv } from 'tailwind-variants'

const styleGenerator = tv({
...
})

...

const style = useMemo(() => styleGenerator(), [])
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const SCHEMA = []

const TV_COMPONENTS_METHOD = 'tv'
const TV_COMPONENTS = 'tailwind-variants'
const TV_RESULT_CONST_NAME_REGEX = /(S|s)tyleGenerator$/

const findValidImportNameNode = (s) => s.type === 'ImportSpecifier' && s.local.name === TV_COMPONENTS_METHOD

const checkImportTailwindVariants = (node, context) => {
}
const findNodeHasId = (node) => {
if (node.id) {
return node
}

if (node.parent) {
return findNodeHasId(node.parent)
}

return null
}
const findNodeUseMemo = (node) => {
if (node.type === 'CallExpression' && node.callee.name === 'useMemo') {
return node
}

if (node.parent) {
return findNodeUseMemo(node.parent)
}

return null
}

/**
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
*/
module.exports = {
meta: {
type: 'problem',
schema: SCHEMA,
},
create(context) {
return {
ImportDeclaration: (node) => {
if (node.source.value === TV_COMPONENTS) {
if (!node.specifiers.some(findValidImportNameNode)) {
context.report({
node,
message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"`,
});
}
}
},
CallExpression: (node) => {
if (node.callee.name === TV_COMPONENTS_METHOD) {
const idNode = findNodeHasId(node.parent)

if (idNode && !TV_RESULT_CONST_NAME_REGEX.test(idNode.id.name)) {
context.report({
node: idNode,
message: `${TV_COMPONENTS_METHOD}の実行結果を格納する変数名は "${idNode.id.name}" ではなく "${TV_RESULT_CONST_NAME_REGEX}"にmatchする名称に統一してください。`,
});
}
} else if (TV_RESULT_CONST_NAME_REGEX.test(node.callee.name)) {
const useMemoNode = findNodeUseMemo(node.parent)

if (!useMemoNode) {
context.report({
node,
message: `"${node.callee.name}" を実行する際、useMemoでラップし、メモ化してください`,
});
}
}
},
}
},
}
module.exports.schema = SCHEMA
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const rule = require('../rules/best-practice-for-tailwind-variants')
const RuleTester = require('eslint').RuleTester

const ruleTester = new RuleTester({
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
})

ruleTester.run('best-practice-for-button-element', rule, {
valid: [
{ code: `import { tv } from 'tailwind-variants'` },
{ code: `const styleGenerator = tv()` },
{ code: `const xxxStyleGenerator = tv()` },
{ code: `const hoge = useMemo(() => styleGenerator(), [])` },
{ code: `const xxx = useMemo(() => hogeStyleGenerator(), [])` },
],
invalid: [
{ code: `import { tv as hoge } from 'tailwind-variants'`, errors: [ { message: `tailwind-variants をimportする際は、名称が"tv" となるようにしてください。例: "import { tv } from 'tailwind-variants'"` } ] },
{ code: `const hoge = tv()`, errors: [ { message: `tvの実行結果を格納する変数名は "hoge" ではなく "/(S|s)tyleGenerator$/"にmatchする名称に統一してください。` } ] },
{ code: `const hoge = styleGenerator()`, errors: [ { message: `"styleGenerator" を実行する際、useMemoでラップし、メモ化してください` } ] },
{ code: `const hoge = hogeStyleGenerator()`, errors: [ { message: `"hogeStyleGenerator" を実行する際、useMemoでラップし、メモ化してください` } ] },
]
})
Loading