eslint-plugin-smarthr 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/libs/common_domain.js +5 -8
  3. package/libs/util.js +4 -0
  4. package/package.json +4 -4
  5. package/rules/a11y-anchor-has-href-attribute/index.js +31 -20
  6. package/rules/a11y-clickable-element-has-text/index.js +4 -25
  7. package/rules/a11y-delegate-element-has-role-presentation/index.js +34 -55
  8. package/rules/a11y-form-control-in-form/index.js +5 -31
  9. package/rules/a11y-heading-in-sectioning-content/index.js +7 -63
  10. package/rules/a11y-image-has-alt-attribute/index.js +0 -16
  11. package/rules/a11y-input-has-name-attribute/index.js +15 -33
  12. package/rules/a11y-input-in-form-control/index.js +28 -74
  13. package/rules/a11y-numbered-text-within-ol/index.js +2 -11
  14. package/rules/a11y-prohibit-input-maxlength-attribute/index.js +8 -20
  15. package/rules/a11y-prohibit-input-placeholder/index.js +2 -14
  16. package/rules/a11y-prohibit-sectioning-content-in-form/index.js +5 -37
  17. package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +5 -23
  18. package/rules/a11y-required-layout-as-attribute/index.js +0 -25
  19. package/rules/a11y-trigger-has-button/index.js +10 -18
  20. package/rules/best-practice-for-data-test-attribute/index.js +3 -6
  21. package/rules/best-practice-for-layouts/index.js +2 -16
  22. package/rules/best-practice-for-remote-trigger-dialog/index.js +3 -11
  23. package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +38 -30
  24. package/rules/best-practice-for-tailwind-variants/index.js +10 -18
  25. package/rules/component-name/README.md +44 -0
  26. package/rules/component-name/index.js +139 -0
  27. package/rules/design-system-guideline-prohibit-double-icons/index.js +1 -11
  28. package/rules/format-import-path/index.js +14 -6
  29. package/rules/format-translate-component/index.js +3 -1
  30. package/rules/no-import-other-domain/index.js +8 -8
  31. package/rules/prohibit-file-name/index.js +1 -1
  32. package/rules/prohibit-import/index.js +21 -23
  33. package/rules/prohibit-path-within-template-literal/index.js +1 -1
  34. package/rules/require-barrel-import/index.js +8 -11
  35. package/rules/require-declaration/index.js +5 -3
  36. package/rules/require-export/index.js +34 -30
  37. package/rules/require-import/index.js +10 -10
  38. package/rules/trim-props/index.js +9 -8
  39. package/test/a11y-anchor-has-href-attribute.js +0 -29
  40. package/test/a11y-clickable-element-has-text.js +0 -66
  41. package/test/{a11y-delegate-element-has-role-presantation.js → a11y-delegate-element-has-role-presentation.js} +3 -2
  42. package/test/a11y-form-control-in-form.js +1 -1
  43. package/test/a11y-heading-in-sectioning-content.js +0 -82
  44. package/test/a11y-image-has-alt-attribute.js +0 -45
  45. package/test/a11y-input-has-name-attribute.js +0 -44
  46. package/test/a11y-input-in-form-control.js +9 -33
  47. package/test/a11y-prohhibit-input-placeholder.js +0 -45
  48. package/test/a11y-trigger-has-button.js +0 -42
  49. package/test/best-practice-for-remote-trigger-dialog.js +0 -6
  50. package/test/component-name.js +247 -0
  51. package/test/prohibit-import.js +13 -13
@@ -1,9 +1,6 @@
1
1
  const SCHEMA = []
2
2
 
3
- const prohibitAttributies = [
4
- "data-spec",
5
- "data-testid"
6
- ]
3
+ const PROHIBIT_ATTR_REGEX = /^(data-(spec|testid))$/
7
4
 
8
5
  /**
9
6
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -16,12 +13,12 @@ module.exports = {
16
13
  create(context) {
17
14
  return {
18
15
  JSXAttribute: (node) => {
19
- const hit = prohibitAttributies.find((attr) => attr === node.name.name)
16
+ const hit = node.name.name.match(PROHIBIT_ATTR_REGEX)
20
17
 
21
18
  if (hit) {
22
19
  context.report({
23
20
  node,
24
- message: `テストのために要素を指定するために、${hit} 属性を利用するのではなく、他の方法で要素を指定することを検討してください。
21
+ message: `テストのために要素を指定するために、${hit[1]} 属性を利用するのではなく、他の方法で要素を指定することを検討してください。
25
22
  - 方法1: click_link, click_button等を利用したりすることで、利用しているテスト環境に準じた方法で要素を指定することを検討してください。
26
23
  - 参考(Testing Library): https://testing-library.com/docs/queries/about
27
24
  - 参考(Capybara): https://rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders
@@ -1,25 +1,12 @@
1
- const { generateTagFormatter } = require('../../libs/format_styled_components')
1
+ const MULTI_CHILDREN_REGEX = /(Cluster|Stack)$/
2
2
 
3
- const MULTI_CHILDREN_EXPECTED_NAMES = {
4
- 'Cluster$': '(Cluster)$',
5
- 'Stack$': '(Stack)$',
6
- }
7
- const EXPECTED_NAMES = {
8
- ...MULTI_CHILDREN_EXPECTED_NAMES,
9
- 'Center$': '(Center)$',
10
-
11
- }
12
-
13
- const UNEXPECTED_NAMES = EXPECTED_NAMES
14
-
15
- const MULTI_CHILDREN_REGEX = new RegExp(`(${Object.keys(MULTI_CHILDREN_EXPECTED_NAMES).join('|')})`)
16
3
  const REGEX_NLSP = /^\s*\n+\s*$/
17
4
  const FLEX_END_REGEX = /^(flex-)?end$/
18
5
 
19
6
  const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
20
7
  const checkFalsyJSXText = (c) => (
21
8
  !(
22
- c.type === 'JSXText' && c.value.match(REGEX_NLSP) ||
9
+ c.type === 'JSXText' && REGEX_NLSP.test(c.value) ||
23
10
  c.type === 'JSXEmptyExpression'
24
11
  )
25
12
  )
@@ -63,7 +50,6 @@ module.exports = {
63
50
  },
64
51
  create(context) {
65
52
  return {
66
- ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
67
53
  JSXOpeningElement: (node) => {
68
54
  const nodeName = node.name.name;
69
55
 
@@ -1,11 +1,4 @@
1
- const { generateTagFormatter } = require('../../libs/format_styled_components');
2
-
3
- const EXPECTED_NAMES = {
4
- 'RemoteDialogTrigger$': 'RemoteDialogTrigger$',
5
- 'RemoteTrigger(.+)Dialog$': 'RemoteTrigger(.+)Dialog$',
6
- }
7
-
8
- const REGEX_REMOTE_TRIGGER_DIALOG = /RemoteTrigger(Action|Message|Modeless)Dialog$/
1
+ const REGEX_REMOTE_TRIGGER_DIALOG = /RemoteTrigger(Action|Form|Message|Modeless)Dialog$/
9
2
  const REGEX_REMOTE_DIALOG_TRIGGER = /RemoteDialogTrigger$/
10
3
 
11
4
  /**
@@ -18,13 +11,12 @@ module.exports = {
18
11
  },
19
12
  create(context) {
20
13
  return {
21
- ...generateTagFormatter({ context, EXPECTED_NAMES }),
22
14
  JSXOpeningElement: (node) => {
23
15
  const nodeName = node.name.name || '';
24
16
 
25
- const regexRemoteTriggerDialog = nodeName.match(REGEX_REMOTE_TRIGGER_DIALOG)
17
+ const regexRemoteTriggerDialog = REGEX_REMOTE_TRIGGER_DIALOG.test(nodeName)
26
18
 
27
- if (regexRemoteTriggerDialog || nodeName.match(REGEX_REMOTE_DIALOG_TRIGGER)) {
19
+ if (regexRemoteTriggerDialog || REGEX_REMOTE_DIALOG_TRIGGER.test(nodeName)) {
28
20
  const attrName = regexRemoteTriggerDialog ? 'id' : 'targetId'
29
21
  const id = node.attributes.find((a) => a.name?.name === attrName)
30
22
 
@@ -3,6 +3,8 @@ const { AST_NODE_TYPES } = require('@typescript-eslint/utils')
3
3
  const SCHEMA = []
4
4
  const MARGIN_CLASS_PATTERNS = /shr-m[trbl]?-/ // mt-, mr-, mb-, ml-, m-
5
5
 
6
+ const findClassNameAttr = (attr) => attr.type === AST_NODE_TYPES.JSXAttribute && attr.name.name === 'className'
7
+
6
8
  /**
7
9
  * コンポーネントのルート要素を渡し、該当の余白クラスが存在すればそれを、なければNULLを返す
8
10
  * @param {import('@typescript-eslint/utils').TSESTree.Node} node
@@ -12,17 +14,18 @@ const findSpacingClassInRootElement = (node) => {
12
14
  // JSX でなければ対象外
13
15
  if (node.type !== AST_NODE_TYPES.JSXElement) return null
14
16
 
15
- // className属性がなければ対象外
16
- const classNameAttr = node.openingElement.attributes.find(
17
- (attr) => attr.type === AST_NODE_TYPES.JSXAttribute && attr.name.name === 'className',
18
- )
19
- if (!classNameAttr) return null
17
+ const classNameAttr = node.openingElement.attributes.find(findClassNameAttr)
20
18
 
21
- // className属性の値がリテラルでなければ対象外
22
- if (classNameAttr?.value?.type !== AST_NODE_TYPES.Literal || typeof classNameAttr.value.value !== 'string') return null
19
+ if (
20
+ classNameAttr &&
21
+ // className属性の値がリテラル、かつ余白クラスの場合
22
+ classNameAttr.value?.type === AST_NODE_TYPES.Literal && typeof classNameAttr.value.value === 'string' &&
23
+ MARGIN_CLASS_PATTERNS.test(classNameAttr.value.value)
24
+ ) {
25
+ return classNameAttr.value
26
+ }
23
27
 
24
- // className属性の値に余白クラスが含まれていればそれを返す
25
- return MARGIN_CLASS_PATTERNS.test(classNameAttr.value.value) ? classNameAttr.value : null
28
+ return null
26
29
  }
27
30
 
28
31
  /**
@@ -56,30 +59,37 @@ module.exports = {
56
59
  * 関数本体をチェックし、ルート要素で余白クラスが設定されたJSXを返している場合、エラーを報告する
57
60
  * @param {import('@typescript-eslint/utils').TSESTree.Node} body
58
61
  */
59
- const checkFunctionBody = (body) => {
60
- // 関数がブロックを持たずに直接JSXを返すパターン
61
- if (body.type === AST_NODE_TYPES.JSXElement) {
62
- const spacingClass = findSpacingClassInRootElement(body)
63
- if (spacingClass) {
64
- context.report({
65
- node: spacingClass,
66
- messageId: 'noRootSpacing',
67
- })
68
- }
69
- return
70
- }
62
+ const checkFunctionBody = (n) => {
63
+ const body = n.body
64
+
65
+ switch (body.type) {
66
+ // 関数がブロックを持たずに直接JSXを返すパターン
67
+ case AST_NODE_TYPES.JSXElement: {
68
+ const spacingClass = findSpacingClassInRootElement(body)
71
69
 
72
- // 関数がブロック内で JSX を return するパターン
73
- if (body.type === AST_NODE_TYPES.BlockStatement) {
74
- const returnStatement = findJSXReturnStatement(body)
75
- if (returnStatement?.argument) {
76
- const spacingClass = findSpacingClassInRootElement(returnStatement.argument)
77
70
  if (spacingClass) {
78
71
  context.report({
79
72
  node: spacingClass,
80
73
  messageId: 'noRootSpacing',
81
74
  })
82
75
  }
76
+
77
+ break
78
+ }
79
+ // 関数がブロック内で JSX を return するパターン
80
+ case AST_NODE_TYPES.BlockStatement: {
81
+ const returnStatement = findJSXReturnStatement(body)
82
+ if (returnStatement?.argument) {
83
+ const spacingClass = findSpacingClassInRootElement(returnStatement.argument)
84
+ if (spacingClass) {
85
+ context.report({
86
+ node: spacingClass,
87
+ messageId: 'noRootSpacing',
88
+ })
89
+ }
90
+ }
91
+
92
+ break
83
93
  }
84
94
  }
85
95
  }
@@ -88,14 +98,12 @@ module.exports = {
88
98
  // アロー関数式のコンポーネントをチェック
89
99
  ArrowFunctionExpression: (node) => {
90
100
  if (node.parent?.type === AST_NODE_TYPES.VariableDeclarator) {
91
- checkFunctionBody(node.body)
101
+ checkFunctionBody(node)
92
102
  }
93
103
  },
94
104
 
95
105
  // function宣言のコンポーネントをチェック
96
- FunctionDeclaration: (node) => {
97
- checkFunctionBody(node.body)
98
- },
106
+ FunctionDeclaration: checkFunctionBody,
99
107
  }
100
108
  },
101
109
  }
@@ -6,8 +6,6 @@ const TV_RESULT_CONST_NAME_REGEX = /(C|c)lassNameGenerator$/
6
6
 
7
7
  const findValidImportNameNode = (s) => s.type === 'ImportSpecifier' && s.imported.name === TV_COMPONENTS_METHOD && s.local.name !== TV_COMPONENTS_METHOD
8
8
 
9
- const checkImportTailwindVariants = (node, context) => {
10
- }
11
9
  const findNodeHasId = (node) => {
12
10
  if (node.id) {
13
11
  return node
@@ -42,13 +40,11 @@ module.exports = {
42
40
  create(context) {
43
41
  return {
44
42
  ImportDeclaration: (node) => {
45
- if (node.source.value === TV_COMPONENTS) {
46
- if (node.specifiers.some(findValidImportNameNode)) {
47
- context.report({
48
- node,
49
- message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"`,
50
- });
51
- }
43
+ if (node.source.value === TV_COMPONENTS && node.specifiers.some(findValidImportNameNode)) {
44
+ context.report({
45
+ node,
46
+ message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"`,
47
+ });
52
48
  }
53
49
  },
54
50
  CallExpression: (node) => {
@@ -61,15 +57,11 @@ module.exports = {
61
57
  message: `${TV_COMPONENTS_METHOD}の実行結果を格納する変数名は "${idNode.id.name}" ではなく "${TV_RESULT_CONST_NAME_REGEX}"にmatchする名称に統一してください。`,
62
58
  });
63
59
  }
64
- } else if (TV_RESULT_CONST_NAME_REGEX.test(node.callee.name)) {
65
- const useMemoNode = findNodeUseMemo(node.parent)
66
-
67
- if (!useMemoNode) {
68
- context.report({
69
- node,
70
- message: `"${node.callee.name}" を実行する際、useMemoでラップし、メモ化してください`,
71
- });
72
- }
60
+ } else if (TV_RESULT_CONST_NAME_REGEX.test(node.callee.name) && !findNodeUseMemo(node.parent)) {
61
+ context.report({
62
+ node,
63
+ message: `"${node.callee.name}" を実行する際、useMemoでラップし、メモ化してください`,
64
+ });
73
65
  }
74
66
  },
75
67
  }
@@ -0,0 +1,44 @@
1
+ # smarthr/component-name
2
+
3
+ - styled-componentなどでコンポーネントを作成する際の命名規則を設定するルールです
4
+ - 特定のタグ、smarthr-uiが提供するコンポーネントなどを拡張する際、元の要素がなんであるか?がわかる名称になるようにチェックします
5
+ - a11y系ルールなどの前提になるチェックのため、基本的にoffにすることは推奨されません
6
+
7
+ ## rules
8
+
9
+ ```js
10
+ {
11
+ rules: {
12
+ 'smarthr/component-name': 'error', // 'warn', 'off',
13
+ },
14
+ }
15
+ ```
16
+
17
+ ## ❌ Incorrect
18
+
19
+ ```jsx
20
+ // import 時のasをチェック
21
+ import { HogeSelect as Fuga } from 'any'
22
+
23
+ // selectと勘違いしてしまうような名称はNG
24
+ const HogeSelect = styled.div
25
+ const HogeSelect = styled(Hoge)
26
+
27
+ // selectとわからないような名称はNG
28
+ const Hoge = styled.select
29
+ const Hoge = styled(FugaSelect)
30
+ ```
31
+
32
+ ## ✅ Correct
33
+
34
+ ```jsx
35
+ // import 時のasをチェック
36
+ import { HogeSelect as FugaSelect } from 'any'
37
+ // typeを設定すれば命名チェックから除外される
38
+ import { type HogeSelect as Fuga } from 'any'
39
+ import type { HogeSelect as Fuga } from 'any'
40
+
41
+ // 継承元がselectであることがわかるためOK
42
+ const HogeSelect = styled.select
43
+ const HogeSelect = styled(FugaSelect)
44
+ ```
@@ -0,0 +1,139 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
+
3
+ const EXPECTED_NAMES = {
4
+ '(A|^a)rticle$': 'Article$',
5
+ '(A|^a)side$': 'Aside$',
6
+ '(B|^b)utton$': 'Button$',
7
+ '(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
8
+ '(F|^f)ieldset$': 'Fieldset$',
9
+ '(F|^f)orm$': 'Form$',
10
+ '(Heading|^h(2|3|4|5|6))$': 'Heading$',
11
+ '(I|^i)nput$': 'Input$',
12
+ '(L|^l)abel$': 'Label$',
13
+ '(N|^n)av$': 'Nav$',
14
+ '(Ordered(.*)List|^ol)$': 'Ordered(.*)List$',
15
+ '(PageHeading|^h1)$': 'PageHeading$',
16
+ '(S|^s)ection$': 'Section$',
17
+ '(S|^s)elect$': 'Select$',
18
+ '(T|^t)extarea$': 'Textarea$',
19
+ 'AccordionPanel$': 'AccordionPanel$',
20
+ 'ActionDialogWithTrigger$': 'ActionDialogWithTrigger$',
21
+ 'Anchor$': 'Anchor$',
22
+ 'AnchorButton$': 'AnchorButton$',
23
+ 'Base$': 'Base$',
24
+ 'BaseColumn$': 'BaseColumn$',
25
+ 'Center$': 'Center$',
26
+ 'Check(B|b)ox$': 'Checkbox$',
27
+ 'Check(B|b)ox(e)?s$': 'Checkboxes$',
28
+ 'Cluster$': 'Cluster$',
29
+ 'Combo(B|b)ox$': 'Combobox$',
30
+ 'DialogTrigger$': 'DialogTrigger$',
31
+ 'DropZone$': 'DropZone$',
32
+ 'DropdownTrigger$': 'DropdownTrigger$',
33
+ 'FieldSet$': 'FieldSet$',
34
+ 'Fieldsets$': 'Fieldsets$',
35
+ 'FilterDropdown$': 'FilterDropdown$',
36
+ 'FormControl$': 'FormControl$',
37
+ 'FormControls$': 'FormControls$',
38
+ 'FormDialog$': 'FormDialog$',
39
+ 'FormGroup$': 'FormGroup$',
40
+ 'Icon$': 'Icon$',
41
+ 'Image$': 'Image$',
42
+ 'Img$': 'Img$',
43
+ 'IndexNav$': 'IndexNav$',
44
+ 'InputFile$': 'InputFile$',
45
+ 'Link$': 'Link$',
46
+ 'Message$': 'Message$',
47
+ 'ModelessDialog$': 'ModelessDialog$',
48
+ 'Pagination$': 'Pagination$',
49
+ 'RadioButton$': 'RadioButton$',
50
+ 'RadioButtonPanel$': 'RadioButtonPanel$',
51
+ 'RadioButtonPanels$': 'RadioButtonPanels$',
52
+ 'RadioButtons$': 'RadioButtons$',
53
+ 'Reel$': 'Reel$',
54
+ 'RemoteDialogTrigger$': 'RemoteDialogTrigger$',
55
+ 'RemoteTrigger(.*)FormDialog$': 'RemoteTrigger(.*)FormDialog$',
56
+ 'RemoteTrigger(.+)Dialog$': 'RemoteTrigger(.+)Dialog$',
57
+ 'RightFixedNote$': 'RightFixedNote$',
58
+ 'SearchInput$': 'SearchInput$',
59
+ 'SegmentedControl$': 'SegmentedControl$',
60
+ 'SideNav$': 'SideNav$',
61
+ 'Sidebar$': 'Sidebar$',
62
+ 'SmartHRLogo$': 'SmartHRLogo$',
63
+ 'Stack$': 'Stack$',
64
+ 'Switch$': 'Switch$',
65
+ 'TabItem$': 'TabItem$',
66
+ 'Text$': 'Text$',
67
+ 'TimePicker$': 'TimePicker$',
68
+ '^(img|svg)$': '(Img|Image|Icon)$',
69
+ '^a$': '(Anchor|Link)$',
70
+ }
71
+
72
+ const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています
73
+ - childrenにHeadingを含まない場合、コンポーネントの名称から"{{expected}}"を取り除いてください
74
+ - childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/{{expected}}をexendしてください
75
+ - "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"{{expected}}"を設定し、そのコンポーネント内でsmarthr-ui/{{expected}}を利用してください`
76
+ const UNEXPECTED_NAMES = {
77
+ '(Anchor|^a)$': '(Anchor)$',
78
+ '(A|^a)rticle$': ['(Article)$', unexpectedMessageTemplate ],
79
+ '(A|^a)side$': ['(Aside)$', unexpectedMessageTemplate ],
80
+ '(B|^b)utton$': '(Button)$',
81
+ '(Date|Wareki)Picker$': '((Date|Wareki)Picker)$',
82
+ '(F|^f)ieldset$': '(Fieldset)$',
83
+ '(F|^f)orm$': '(Form)$',
84
+ '(Heading|^h(1|2|3|4|5|6))$': '(Heading)$',
85
+ '(Icon|^(img|svg))$': '(Icon)$',
86
+ '(Image|^(img|svg))$': '(Image)$',
87
+ '(Img|^(img|svg))$': '(Img)$',
88
+ '(I|^i)nput$': '(Input)$',
89
+ '(Link|^a)$': '(Link)$',
90
+ '(L|^l)abel$': '(Label)$',
91
+ '(N|^n)av$': ['(Nav)$', unexpectedMessageTemplate ],
92
+ '(Ordered(.*)List|^ol)$': '(Ordered(.*)List)$',
93
+ '(S|^s)ection$': ['(Section)$', unexpectedMessageTemplate ],
94
+ '(S|^s)elect$': '(Select)$',
95
+ '(T|^t)extarea$': '(Textarea)$',
96
+ 'Base$': '(Base)$',
97
+ 'BaseColumn$': '(BaseColumn)$',
98
+ 'Center$': '(Center)$',
99
+ 'Check(B|b)ox$': '(Checkbox)$',
100
+ 'Check(B|b)ox(e)?s$': '(Checkboxes)$',
101
+ 'Cluster$': '(Cluster)$',
102
+ 'Combo(B|b)ox$': '(Combobox)$',
103
+ 'Fieldsets$': '(Fieldsets)$',
104
+ 'FilterDropdown$': '(FilterDropdown)$',
105
+ 'FormControl$': '(FormControl)$',
106
+ 'FormControls$': '(FormControls)$',
107
+ 'FormDialog$': '(FormDialog)$',
108
+ 'FormGroup$': '(FormGroup)$',
109
+ 'IndexNav$': '(IndexNav)$',
110
+ 'InputFile$': '(InputFile)$',
111
+ 'RadioButton$': '(RadioButton)$',
112
+ 'RadioButtonPanel$': '(RadioButtonPanel)$',
113
+ 'RadioButtonPanels$': '(RadioButtonPanels)$',
114
+ 'RadioButtons$': '(RadioButtons)$',
115
+ 'Reel$': '(Reel)$',
116
+ 'RemoteTrigger(.*)FormDialog$': '(RemoteTrigger(.*)FormDialog)$',
117
+ 'SearchInput$': '(SearchInput)$',
118
+ 'SideNav$': '(SideNav)$',
119
+ 'Sidebar$': '(Sidebar)$',
120
+ 'Stack$': '(Stack)$',
121
+ 'TimePicker$': '(TimePicker)$',
122
+ }
123
+
124
+
125
+ const SCHEMA = []
126
+
127
+ /**
128
+ * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
129
+ */
130
+ module.exports = {
131
+ meta: {
132
+ type: 'problem',
133
+ schema: SCHEMA,
134
+ },
135
+ create(context) {
136
+ return generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES })
137
+ },
138
+ }
139
+ module.exports.schema = SCHEMA
@@ -1,15 +1,6 @@
1
- const { generateTagFormatter } = require('../../libs/format_styled_components')
2
-
3
- const EXPECTED_NAMES = {
4
- '(Button)$': '(Button)$',
5
- '(Link)$': '(Link)$',
6
- }
7
-
8
- const UNEXPECTED_NAMES = EXPECTED_NAMES
9
-
10
1
  const SCHEMA = []
11
2
 
12
- const REGEX_PATTERN = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
3
+ const REGEX_PATTERN = /(Button|Link)$/
13
4
 
14
5
  /**
15
6
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -21,7 +12,6 @@ module.exports = {
21
12
  },
22
13
  create(context) {
23
14
  return {
24
- ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
25
15
  JSXOpeningElement: (node) => {
26
16
  const nodeName = node.name.name
27
17
 
@@ -21,6 +21,14 @@ const SCHEMA = [
21
21
  }
22
22
  ]
23
23
 
24
+ const CWD = process.cwd()
25
+ const REPLACE_PATH_ENTRIES = Object.entries(replacePaths)
26
+ const REPLACE_PATH_KEY_REGEX = new RegExp(`^(${Object.keys(replacePaths).join('|')})`)
27
+ const DIR_SEPARATE_REGEX = /\//g
28
+ const MULTIPLE_DIR_SEPARATE_REGEX =/(\/)+/g
29
+
30
+ const dirCount = (dir) => dir.match(DIR_SEPARATE_REGEX).length
31
+
24
32
  const convertType = (calcContext, calcDomainNode) => {
25
33
  const { option: { format: { all, outside, globalModule, module, domain, lower } } } = calcContext
26
34
  const { isGlobalModuleImport, isModuleImport, isDomainImport, isLowerImport } = calcDomainNode
@@ -49,14 +57,14 @@ const calculateAbsoluteImportPath = ({ importPath, resolvedImportPath }) => {
49
57
  return importPath
50
58
  }
51
59
 
52
- return Object.entries(replacePaths).reduce((prev, [key, values]) => {
60
+ return REPLACE_PATH_ENTRIES.reduce((prev, [key, values]) => {
53
61
  if (resolvedImportPath === prev) {
54
62
  return values.reduce((p, v) => {
55
63
  if (prev === p) {
56
- const regexp = new RegExp(`^${path.resolve(`${process.cwd()}/${v}`)}(.+)$`)
64
+ const regexp = new RegExp(`^${path.resolve(`${CWD}/${v}`)}(.+)$`)
57
65
 
58
- if (prev.match(regexp)) {
59
- return p.replace(regexp, `${key}/$1`).replace(/(\/)+/g, '/')
66
+ if (regexp.test(prev)) {
67
+ return p.replace(regexp, `${key}/$1`).replace(MULTIPLE_DIR_SEPARATE_REGEX, '/')
60
68
  }
61
69
  }
62
70
 
@@ -71,7 +79,7 @@ const calculateRelativeImportPath = ({ importPath, filteredDirs, filteredPaths }
71
79
  // HINT: 相対パスの場合でも、余計にさかのぼっていたりする場合もあるので修正対象とする
72
80
  if (
73
81
  importPath[0] !== '.' &&
74
- !importPath.match(new RegExp(`^(${Object.keys(replacePaths).join('|')})`))
82
+ !REPLACE_PATH_KEY_REGEX.test(importPath)
75
83
  ) {
76
84
  return importPath
77
85
  }
@@ -113,7 +121,7 @@ module.exports = {
113
121
 
114
122
  // HINT: 記述するdirの数でより近い方を選択する。
115
123
  // 相対・絶対記法が同一の場合、おそらくimport元から距離はあるため、たどりやすくするために絶対記法を選択する
116
- return (absoluted.split('/').length <= relatived.split('/').length) ? absoluted : relatived
124
+ return (dirCount(absoluted) <= dirCount(relatived)) ? absoluted : relatived
117
125
  }
118
126
 
119
127
  return importPath
@@ -13,6 +13,8 @@ const SCHEMA = [
13
13
  }
14
14
  ]
15
15
 
16
+ const NOOP = () => {}
17
+
16
18
  /**
17
19
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
18
20
  */
@@ -23,7 +25,7 @@ module.exports = {
23
25
  },
24
26
  create(context) {
25
27
  const { componentPath, componentName, prohibitAttributies } = context.options[0]
26
- let JSXAttribute = () => {}
28
+ let JSXAttribute = NOOP
27
29
 
28
30
  if (prohibitAttributies) {
29
31
  JSXAttribute = (node) => {
@@ -46,7 +46,7 @@ module.exports = {
46
46
  const calcContext = calculateDomainContext(context)
47
47
 
48
48
  // 対象外ファイル
49
- if (!calcContext.isTarget || calcContext.option.ignores && calcContext.option.ignores.some((i) => !!calcContext.filename.match(new RegExp(i)))) {
49
+ if (!calcContext.isTarget || calcContext.option.ignores && calcContext.option.ignores.some((i) => (new RegExp(i)).test(calcContext.filename))) {
50
50
  return {}
51
51
  }
52
52
 
@@ -55,8 +55,8 @@ module.exports = {
55
55
  humanizeParentDir,
56
56
  } = calcContext
57
57
 
58
- const targetPathRegexs = Object.keys(option?.allowedImports || {})
59
- const targetAllowedImports = targetPathRegexs.filter((regex) => !!calcContext.filename.match(new RegExp(regex)))
58
+ const targetPathRegexs = option?.allowedImports ? Object.keys(option.allowedImports) : []
59
+ const targetAllowedImports = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(calcContext.filename))
60
60
 
61
61
  return {
62
62
  ImportDeclaration: (node) => {
@@ -90,11 +90,11 @@ module.exports = {
90
90
  })
91
91
  })
92
92
 
93
- if (isDenyPath && deniedModules[0] === true) {
94
- return
95
- }
96
-
97
- if (!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0) {
93
+ if (isDenyPath) {
94
+ if (deniedModules[0] === true) {
95
+ return
96
+ }
97
+ } else if (deniedModules.length === 1 && deniedModules[0].length === 0) {
98
98
  return
99
99
  }
100
100
 
@@ -19,7 +19,7 @@ module.exports = {
19
19
  },
20
20
  create(context) {
21
21
  const options = context.options[0]
22
- const targetPaths = Object.keys(options).filter((regex) => !!context.filename.match(new RegExp(regex)))
22
+ const targetPaths = Object.keys(options).filter((regex) => (new RegExp(regex)).test(context.filename))
23
23
 
24
24
 
25
25
  if (targetPaths.length === 0) {
@@ -1,4 +1,5 @@
1
1
  const path = require('path')
2
+ const { getParentDir } = require('../../libs/util')
2
3
 
3
4
  const SCHEMA = [{
4
5
  type: 'object',
@@ -30,6 +31,8 @@ const SCHEMA = [{
30
31
  additionalProperties: true,
31
32
  }]
32
33
 
34
+ const CWD = process.cwd()
35
+
33
36
  const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください`
34
37
 
35
38
  /**
@@ -42,14 +45,9 @@ module.exports = {
42
45
  },
43
46
  create(context) {
44
47
  const options = context.options[0]
45
- const parentDir = (() => {
46
- const dir = context.filename.match(/^(.+?)\..+?$/)[1].split('/')
47
- dir.pop()
48
-
49
- return dir.join('/')
50
- })()
48
+ const parentDir = getParentDir(context.filename)
51
49
  const targetPathRegexs = Object.keys(options)
52
- const targetProhibits = targetPathRegexs.filter((regex) => !!context.filename.match(new RegExp(regex)))
50
+ const targetProhibits = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
53
51
 
54
52
  if (targetProhibits.length === 0) {
55
53
  return {}
@@ -63,32 +61,32 @@ module.exports = {
63
61
 
64
62
  targetModules.forEach((targetModule) => {
65
63
  const { imported, reportMessage } = Object.assign({imported: true}, option[targetModule])
66
- const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`)
64
+ const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${CWD}/${targetModule}`)
67
65
  let sourceValue = node.source.value
68
66
 
69
67
  if (actualTarget[0] === '/') {
70
68
  sourceValue = path.resolve(`${parentDir}/${sourceValue}`)
71
69
  }
72
70
 
73
- if (actualTarget !== sourceValue) {
74
- return
75
- }
71
+ if (actualTarget === sourceValue) {
72
+ let useImported = false
76
73
 
77
- const useImported = (() => {
78
74
  if (!Array.isArray(imported)) {
79
- return !!imported
80
- }
75
+ useImported = !!imported
76
+ } else {
77
+ const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
81
78
 
82
- const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
83
-
84
- return specifier ? specifier.imported.name : false
85
- })()
79
+ if (specifier) {
80
+ useImported = specifier.imported.name
81
+ }
82
+ }
86
83
 
87
- if (useImported) {
88
- context.report({
89
- node,
90
- message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
91
- });
84
+ if (useImported) {
85
+ context.report({
86
+ node,
87
+ message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
88
+ });
89
+ }
92
90
  }
93
91
  })
94
92
  })
@@ -13,7 +13,7 @@ const recursiveFetchName = (obj, chained = '') => {
13
13
  const recursiveFetchRootNameIsPath = (obj, regex) => {
14
14
  const [name, chained] = recursiveFetchName(obj, '')
15
15
 
16
- return name.match(regex) ? chained : null
16
+ return regex.test(name) ? chained : null
17
17
  }
18
18
 
19
19
  const SCHEMA = [