eslint-plugin-smarthr 2.3.0 → 2.5.0

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 (79) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +2 -2
  3. package/rules/a11y-anchor-has-href-attribute/index.js +1 -0
  4. package/rules/a11y-clickable-element-has-text/index.js +1 -0
  5. package/rules/a11y-delegate-element-has-role-presentation/index.js +2 -0
  6. package/rules/a11y-form-control-in-form/index.js +1 -0
  7. package/rules/a11y-heading-in-sectioning-content/README.md +16 -1
  8. package/rules/a11y-heading-in-sectioning-content/index.js +7 -2
  9. package/rules/a11y-help-link-with-support-href/index.js +2 -1
  10. package/rules/a11y-image-has-alt-attribute/index.js +2 -0
  11. package/rules/a11y-input-has-name-attribute/index.js +6 -3
  12. package/rules/a11y-input-in-form-control/index.js +11 -4
  13. package/rules/a11y-numbered-text-within-ol/index.js +5 -0
  14. package/rules/a11y-prohibit-checkbox-or-radio-in-table-cell/index.js +5 -23
  15. package/rules/a11y-prohibit-input-maxlength-attribute/index.js +5 -4
  16. package/rules/a11y-prohibit-input-placeholder/index.js +7 -3
  17. package/rules/a11y-prohibit-sectioning-content-in-form/index.js +2 -0
  18. package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +2 -1
  19. package/rules/a11y-trigger-has-button/index.js +4 -2
  20. package/rules/best-practice-for-async-current-target/index.js +2 -2
  21. package/rules/best-practice-for-button-element/index.js +1 -0
  22. package/rules/best-practice-for-data-test-attribute/index.js +1 -0
  23. package/rules/best-practice-for-date/index.js +2 -0
  24. package/rules/best-practice-for-layouts/index.js +22 -10
  25. package/rules/best-practice-for-nested-attributes-array-index/index.js +1 -0
  26. package/rules/best-practice-for-remote-trigger-dialog/index.js +2 -1
  27. package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +2 -1
  28. package/rules/best-practice-for-tailwind-variants/index.js +6 -3
  29. package/rules/component-name/README.md +11 -3
  30. package/rules/component-name/index.js +135 -2
  31. package/rules/design-system-guideline-prohibit-double-icons/index.js +1 -0
  32. package/rules/format-import-path/index.js +2 -1
  33. package/rules/jsx-start-with-spread-attributes/index.js +2 -1
  34. package/rules/no-import-other-domain/index.js +2 -1
  35. package/rules/prohibit-export-array-type/index.js +1 -0
  36. package/rules/prohibit-file-name/index.js +2 -1
  37. package/rules/prohibit-import/index.js +4 -2
  38. package/rules/prohibit-path-within-template-literal/index.js +2 -1
  39. package/rules/require-barrel-import/index.js +2 -1
  40. package/rules/require-declaration/index.js +4 -2
  41. package/rules/require-export/index.js +2 -1
  42. package/rules/require-i18n-text/index.js +4 -2
  43. package/rules/require-import/index.js +2 -1
  44. package/rules/trim-props/index.js +24 -12
  45. package/test/a11y-anchor-has-href-attribute.js +1 -0
  46. package/test/a11y-clickable-element-has-text.js +1 -0
  47. package/test/a11y-delegate-element-has-role-presentation.js +2 -0
  48. package/test/a11y-form-control-in-form.js +1 -0
  49. package/test/a11y-heading-in-sectioning-content.js +7 -2
  50. package/test/a11y-help-link-with-support-href.js +2 -1
  51. package/test/a11y-image-has-alt-attribute.js +2 -0
  52. package/test/a11y-input-has-name-attribute.js +30 -15
  53. package/test/a11y-input-in-form-control.js +15 -1
  54. package/test/a11y-numbered-text-within-ol.js +29 -0
  55. package/test/a11y-prohhibit-input-placeholder.js +19 -9
  56. package/test/a11y-prohibit-checkbox-or-radio-in-table-cell.js +35 -15
  57. package/test/a11y-prohibit-input-maxlength-attribute.js +5 -4
  58. package/test/a11y-prohibit-useless-sectioning-fragment.js +2 -1
  59. package/test/a11y-trigger-has-button.js +12 -6
  60. package/test/best-practice-for-async-current-target.js +2 -2
  61. package/test/best-practice-for-button-element.js +2 -0
  62. package/test/best-practice-for-data-test-attribute.js +1 -0
  63. package/test/best-practice-for-date.js +2 -0
  64. package/test/best-practice-for-layouts.js +26 -12
  65. package/test/best-practice-for-nested-attributes-array-index.js +1 -0
  66. package/test/best-practice-for-remote-trigger-dialog.js +6 -3
  67. package/test/best-practice-for-tailwind-prohibit-root-margin.js +2 -2
  68. package/test/best-practice-for-tailwind-variants.js +8 -4
  69. package/test/component-name.js +11 -2
  70. package/test/design-system-guideline-prohibit-double-icons.js +1 -0
  71. package/test/prohibit-file-name.js +4 -2
  72. package/test/prohibit-import.js +14 -7
  73. package/test/prohibit-path-within-template-literal.js +4 -2
  74. package/test/require-declaration.js +11 -5
  75. package/test/require-export.js +6 -3
  76. package/test/require-i18n-text.js +4 -3
  77. package/test/require-import.js +13 -7
  78. package/test/trim-props.js +23 -14
  79. package/libs/format_styled_components.js +0 -126
@@ -92,6 +92,7 @@ module.exports = {
92
92
  context.report({
93
93
  node,
94
94
  message: `${nodeName} に "gap={0}" が指定されており、smarthr-ui/${layoutType} の利用方法として誤っている可能性があります。以下の修正方法を検討してください。
95
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts
95
96
  - 方法1: 子要素を一つにまとめられないか検討してください
96
97
  - 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
97
98
  - 方法2: 子要素のstyleを確認しgap属性を0以外にできないか検討してください
@@ -115,8 +116,10 @@ module.exports = {
115
116
  node,
116
117
  message:
117
118
  (justifyAttr?.value.value === 'center' || alignAttr?.value.value === 'center')
118
- ? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください`
119
+ ? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください
120
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`
119
121
  : `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。
122
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts
120
123
  - styleを確認し、div・spanなど、別要素でマークアップし直すか、${nodeName}を削除してください
121
124
  - as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
122
125
  })
@@ -128,55 +131,64 @@ module.exports = {
128
131
 
129
132
  context.report({
130
133
  node,
131
- message: `Headingの子孫に${component}を置くことはできません。Headingの外で${component}を使用するようにマークアップを修正してください。`
134
+ message: `Headingの子孫に${component}を置くことはできません。Headingの外で${component}を使用するようにマークアップを修正してください。
135
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`
132
136
  })
133
137
  },
134
138
  [`${HEADING_ELEMENT} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
135
139
  context.report({
136
140
  node,
137
- message: 'Headingの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に `span` を指定してください',
141
+ message: `Headingの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください
142
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
138
143
  })
139
144
  },
140
145
  [`${HEADING_ELEMENT} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
141
146
  context.report({
142
147
  node,
143
- message: 'HeadingにIconを設定する場合 <Heading icon={<XxxIcon />}></Heading> のようにicon属性を利用してください',
148
+ message: `HeadingにIconを設定する場合 <Heading icon={<XxxIcon />}></Heading> のようにicon属性を利用してください
149
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
144
150
  })
145
151
  },
146
152
  [`${FORM_CONTROL_LABEL_ATTRIBUTE} ${INVALID_ELEMENT}`]: (node) => {
147
153
  context.report({
148
154
  node,
149
- message: `FormControlのlabel属性に${node.name.name.match(TARGET_INVALID_COMPONENT_REGEX)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。`,
155
+ message: `FormControlのlabel属性に${node.name.name.match(TARGET_INVALID_COMPONENT_REGEX)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。
156
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
150
157
  })
151
158
  },
152
159
  [`${FORM_CONTROL_LABEL_ATTRIBUTE} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
153
160
  context.report({
154
161
  node,
155
- message: 'FormControlのlabel属性にStackを置く場合、as属性、もしくはforwardedAs属性に `span` を指定してください',
162
+ message: `FormControlのlabel属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください
163
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
156
164
  })
157
165
  },
158
166
  [`${FORM_CONTROL_LABEL_ATTRIBUTE} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
159
167
  context.report({
160
168
  node,
161
- message: "FormControlのlabel属性にアイコンを設定する場合 <FormControl label={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlabel.icon属性を利用してください",
169
+ message: `FormControlのlabel属性にアイコンを設定する場合 <FormControl label={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlabel.icon属性を利用してください
170
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
162
171
  })
163
172
  },
164
173
  [`${FIELDSET_LEGEND_ATTRIBUTE} ${INVALID_ELEMENT}`]: (node) => {
165
174
  context.report({
166
175
  node,
167
- message: `Fieldsetのlegend属性に${node.name.name.match(TARGET_INVALID_COMPONENT_REGEX)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。`,
176
+ message: `Fieldsetのlegend属性に${node.name.name.match(TARGET_INVALID_COMPONENT_REGEX)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。
177
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
168
178
  })
169
179
  },
170
180
  [`${FIELDSET_LEGEND_ATTRIBUTE} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
171
181
  context.report({
172
182
  node,
173
- message: 'Fieldsetのlegend属性にStackを置く場合、as属性、もしくはforwardedAs属性に `span` を指定してください',
183
+ message: `Fieldsetのlegend属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください
184
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
174
185
  })
175
186
  },
176
187
  [`${FIELDSET_LEGEND_ATTRIBUTE} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
177
188
  context.report({
178
189
  node,
179
- message: "Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください",
190
+ message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください
191
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
180
192
  })
181
193
  },
182
194
  }
@@ -13,6 +13,7 @@ module.exports = {
13
13
  context.report({
14
14
  node,
15
15
  message: `入力要素のname属性に対して、配列に当たる部分の連番を指定しない場合(例: a[xxx][][yyy] )、配列内アイテムの属性が意図せず入れ替わってしまう場合がありえるため、常にindexを設定してください。
16
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index
16
17
  - 例のyyyに当たる値が配列内の別アイテムに紐づいてしまう場合があります。
17
18
  - 詳しくは https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index を参照してください`,
18
19
  })
@@ -11,7 +11,8 @@ module.exports = {
11
11
  context.report({
12
12
  node,
13
13
  message: `${node.parent.name.name}の${node.name.name}属性には直接文字列を指定してください。
14
- - 変数などは利用できません(これは関連するTriggerとDialogを検索しやすくするためです)`,
14
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-remote-trigger-dialog
15
+ - 変数などは利用できません(これは関連するTriggerとDialogを検索しやすくするためです)`,
15
16
  })
16
17
  }
17
18
 
@@ -13,7 +13,8 @@ module.exports = {
13
13
  ':matches(ArrowFunctionExpression,FunctionDeclaration,ReturnStatement)>JSXElement>JSXOpeningElement JSXAttribute[name.name="className"][value.value=/( |^)shr-m[trbl]?-/]': (node) => {
14
14
  context.report({
15
15
  node,
16
- message: 'コンポーネントのルート要素に外側への余白(margin)を設定しないでください。外側の余白は使用する側で制御するべきです。',
16
+ message: `コンポーネントのルート要素に外側への余白(margin)を設定しないでください。外側の余白は使用する側で制御するべきです。
17
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-prohibit-root-margin`,
17
18
  })
18
19
  },
19
20
  }
@@ -43,7 +43,8 @@ module.exports = {
43
43
  if (node.source.value === TV_COMPONENTS && node.specifiers.some(findValidImportNameNode)) {
44
44
  context.report({
45
45
  node,
46
- message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"`,
46
+ message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"
47
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants`,
47
48
  });
48
49
  }
49
50
  },
@@ -54,13 +55,15 @@ module.exports = {
54
55
  if (idNode && !TV_RESULT_CONST_NAME_REGEX.test(idNode.id.name)) {
55
56
  context.report({
56
57
  node: idNode,
57
- message: `${TV_COMPONENTS_METHOD}の実行結果を格納する変数名は "${idNode.id.name}" ではなく "${TV_RESULT_CONST_NAME_REGEX}"にmatchする名称に統一してください。`,
58
+ message: `${TV_COMPONENTS_METHOD}の実行結果を格納する変数名は "${idNode.id.name}" ではなく "${TV_RESULT_CONST_NAME_REGEX}"にmatchする名称に統一してください。
59
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants`,
58
60
  });
59
61
  }
60
62
  } else if (TV_RESULT_CONST_NAME_REGEX.test(node.callee.name) && !findNodeUseMemo(node.parent)) {
61
63
  context.report({
62
64
  node,
63
- message: `"${node.callee.name}" を実行する際、useMemoでラップし、メモ化してください`,
65
+ message: `"${node.callee.name}" を実行する際、useMemoでラップし、メモ化してください
66
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants`,
64
67
  });
65
68
  }
66
69
  },
@@ -20,13 +20,18 @@
20
20
  // import 時のasをチェック
21
21
  import { HogeSelect as Fuga } from 'any'
22
22
 
23
- // selectと勘違いしてしまうような名称はNG
23
+ // 特定の要素と勘違いしてしまうような名称はNG
24
+ // 例: select要素ではないにも関わらずselectを予想してしまう名称を設定している
24
25
  const HogeSelect = styled.div
25
26
  const HogeSelect = styled(Hoge)
26
27
 
27
- // selectとわからないような名称はNG
28
+ // 拡張前の要素がわからないような名称はNG
29
+ // 例: select要素であることがHogeから想像できない
28
30
  const Hoge = styled.select
29
31
  const Hoge = styled(FugaSelect)
32
+
33
+ // Modalでは意味が通らないパターンがあるためDialogに統一する方針のためNG
34
+ const HogeModal = styled(Any)
30
35
  ```
31
36
 
32
37
  ## ✅ Correct
@@ -38,7 +43,10 @@ import { HogeSelect as FugaSelect } from 'any'
38
43
  import { type HogeSelect as Fuga } from 'any'
39
44
  import type { HogeSelect as Fuga } from 'any'
40
45
 
41
- // 継承元がselectであることがわかるためOK
46
+ // あたらしい名称が継承元の要素を予想できるためOK
42
47
  const HogeSelect = styled.select
43
48
  const HogeSelect = styled(FugaSelect)
49
+
50
+ // ModalではなくDialogが利用されているためOK
51
+ const HogeDialog = styled(Any)
44
52
  ```
@@ -1,4 +1,61 @@
1
- const { generateTagFormatter } = require('../../libs/format_styled_components')
1
+ const STYLED_COMPONENTS_METHOD = 'styled'
2
+ const STYLED_COMPONENTS = `${STYLED_COMPONENTS_METHOD}-components`
3
+
4
+ const findInvalidImportNameNode = (s) => s.type === 'ImportDefaultSpecifier' && s.local.name !== STYLED_COMPONENTS_METHOD
5
+
6
+ const checkImportStyledComponents = (node, context) => {
7
+ if (node.source.value !== STYLED_COMPONENTS) {
8
+ return
9
+ }
10
+
11
+ const invalidNameNode = node.specifiers.find(findInvalidImportNameNode)
12
+
13
+ if (invalidNameNode) {
14
+ context.report({
15
+ node: invalidNameNode,
16
+ message: `${STYLED_COMPONENTS} をimportする際は、名称が"${STYLED_COMPONENTS_METHOD}" となるようにしてください。例: "import ${STYLED_COMPONENTS_METHOD} from '${STYLED_COMPONENTS}'"
17
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name`,
18
+ });
19
+ }
20
+ }
21
+
22
+ const getStyledComponentBaseName = (node) => {
23
+ let base = null
24
+
25
+ if (!node.init) {
26
+ return base
27
+ }
28
+
29
+ const tag = node.init.tag || node.init
30
+
31
+ if (tag.object?.name === STYLED_COMPONENTS_METHOD) {
32
+ base = tag.property.name
33
+ } else if (tag.callee) {
34
+ const callee = tag.callee
35
+
36
+ switch (STYLED_COMPONENTS_METHOD) {
37
+ case callee.name: {
38
+ const arg = tag.arguments[0]
39
+ base = arg.name || arg.value
40
+ break
41
+ }
42
+ case callee.callee?.name: {
43
+ const arg = callee.arguments[0]
44
+ base = arg.name || arg.value
45
+ break
46
+ }
47
+ case callee.object?.name:
48
+ base = callee.property.name
49
+ break
50
+ case callee.object?.callee?.name:
51
+ const arg = callee.object.arguments[0]
52
+ base = arg.name || arg.value
53
+ break
54
+ }
55
+ }
56
+
57
+ return base
58
+ }
2
59
 
3
60
  const EXPECTED_NAMES = {
4
61
  '(A|^a)rticle$': 'Article$',
@@ -71,6 +128,7 @@ const EXPECTED_NAMES = {
71
128
  }
72
129
 
73
130
  const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています
131
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name
74
132
  - childrenにHeadingを含まない場合、コンポーネントの名称から"{{expected}}"を取り除いてください
75
133
  - childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/{{expected}}をexendしてください
76
134
  - "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"{{expected}}"を設定し、そのコンポーネント内でsmarthr-ui/{{expected}}を利用してください`
@@ -134,7 +192,82 @@ module.exports = {
134
192
  schema: SCHEMA,
135
193
  },
136
194
  create(context) {
137
- return generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES })
195
+ const entriesesTagNames = Object.entries(EXPECTED_NAMES).map(([b, e]) => [ new RegExp(b), new RegExp(e) ])
196
+ const entriesesUnTagNames = UNEXPECTED_NAMES ? Object.entries(UNEXPECTED_NAMES).map(([b, e]) => {
197
+ const [ auctualE, messageTemplate ] = Array.isArray(e) ? e : [e, '']
198
+
199
+ return [ new RegExp(b), new RegExp(auctualE), messageTemplate ]
200
+ }) : []
201
+
202
+
203
+ const checkImportedNameToLocalName = (node, base, extended, isImport) => {
204
+ entriesesTagNames.forEach(([b, e]) => {
205
+ if (base.match(b) && !extended.match(e)) {
206
+ context.report({
207
+ node,
208
+ message: `${extended}を正規表現 "${e.toString()}" がmatchする名称に変更してください。
209
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name${isImport ? `
210
+ - ${base}が型の場合、'import type { ${base} as ${extended} }' もしくは 'import { type ${base} as ${extended} }' のように明示的に型であることを宣言してください。名称変更が不要になります` : ''}`,
211
+ });
212
+ }
213
+ })
214
+ }
215
+
216
+ return {
217
+ ImportDeclaration: (node) => {
218
+ checkImportStyledComponents(node, context)
219
+
220
+ if (node.importKind !== 'type') {
221
+ node.specifiers.forEach((s) => {
222
+ if (s.importKind !== 'type' && s.imported && s.imported.name !== s.local.name) {
223
+ checkImportedNameToLocalName(node, s.imported.name, s.local.name, true)
224
+ }
225
+ })
226
+ }
227
+ },
228
+ VariableDeclarator: (node) => {
229
+ const base = getStyledComponentBaseName(node)
230
+
231
+ if (base) {
232
+ const extended = node.id.name
233
+
234
+ checkImportedNameToLocalName(node, base, extended)
235
+
236
+ entriesesUnTagNames.forEach(([b, e, m]) => {
237
+ const matcher = extended.match(e)
238
+
239
+ if (matcher && !base.match(b)) {
240
+ const expected = matcher[1]
241
+ const isBareTag = base === base.toLowerCase()
242
+ const sampleFixBase = `styled${isBareTag ? `.${base}` : `(${base})`}`
243
+
244
+ context.report({
245
+ node,
246
+ message: m ? m
247
+ .replaceAll('{{extended}}', extended)
248
+ .replaceAll('{{expected}}', expected) : `${extended} は ${b.toString()} にmatchする名前のコンポーネントを拡張することを期待している名称になっています
249
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name
250
+ - ${extended} の名称の末尾が"${expected}" という文字列ではない状態にしつつ、"${base}"を継承していることをわかる名称に変更してください
251
+ - もしくは"${base}"を"${extended}"の継承元であることがわかるような${isBareTag ? '適切なタグや別コンポーネントに差し替えてください' : '名称に変更するか、適切な別コンポーネントに差し替えてください'}
252
+ - 修正例1: const ${extended.replace(expected, '')}Xxxx = ${sampleFixBase}
253
+ - 修正例2: const ${extended}Xxxx = ${sampleFixBase}
254
+ - 修正例3: const ${extended} = styled(Xxxx${expected})`
255
+ })
256
+ }
257
+ })
258
+ }
259
+ },
260
+ 'VariableDeclarator[id.name=/Modal/]': (node) => {
261
+ context.report({
262
+ node,
263
+ message: `コンポーネント名や変数名に"Modal"という名称は使わず、"Dialog"に統一してください
264
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/component-name
265
+ - Modalとは形容詞であり、かつ"現在の操作から切り離して専用の操作を行わせる" という意味合いを持ちます
266
+ - そのためDialogでなければ正しくない場合がありえます(smarthr-ui/ModelessDialogのように元々の操作も行えるDialogなどが該当)
267
+ - DialogはModalなダイアログ、Modelessなダイアログすべてを含有した名称のため、統一することを推奨しています`
268
+ })
269
+ },
270
+ }
138
271
  },
139
272
  }
140
273
  module.exports.schema = SCHEMA
@@ -14,6 +14,7 @@ module.exports = {
14
14
  context.report({
15
15
  node,
16
16
  message: `${node.name.name} には prefix と suffix は同時に設定できません。
17
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-double-icons
17
18
  - どちらにもアイコンをつけられそうな場合は、prefixを優先してください。`,
18
19
  })
19
20
  }
@@ -130,7 +130,8 @@ module.exports = {
130
130
  if (importPath !== fixedImportPath) {
131
131
  context.report({
132
132
  node,
133
- message: `${fixedImportPath} に修正してください`,
133
+ message: `${fixedImportPath} に修正してください
134
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-import-path`,
134
135
  fix: (fixer) => fixer.replaceText(
135
136
  node,
136
137
  context.sourceCode.getText(node).replace(new RegExp(`from '${importPath}'$`), `from '${fixedImportPath}'`)
@@ -39,7 +39,8 @@ module.exports = {
39
39
 
40
40
  context.report({
41
41
  node,
42
- message: `"${attributeCode}" は意図しない上書きを防ぐため、spread attributesでない属性より先に記述してください`,
42
+ message: `"${attributeCode}" は意図しない上書きを防ぐため、spread attributesでない属性より先に記述してください
43
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/jsx-start-with-spread-attributes`,
43
44
  fix: option?.fix ? (fixer) => {
44
45
  const elementNode = node.parent
45
46
  const sortedAttributes = [...elementNode.attributes].reduce((p, a, i) => {
@@ -122,7 +122,8 @@ module.exports = {
122
122
 
123
123
  context.report({
124
124
  node,
125
- message: `別ドメインから ${importPath}${deniedModules.length ? ` の ${deniedModules.join(', ')}` : ''} がimportされています。`,
125
+ message: `別ドメインから ${importPath}${deniedModules.length ? ` の ${deniedModules.join(', ')}` : ''} がimportされています。
126
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/no-import-other-domain`,
126
127
  })
127
128
  }
128
129
  },
@@ -12,6 +12,7 @@ module.exports = {
12
12
  context.report({
13
13
  node,
14
14
  message: `型をexportする際、配列ではなくアイテムの型をexportしてください。
15
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/prohibit-export-array-type
15
16
  - 型を配列でexportすると、その型が配列かどうかを判定するための情報は名称のみになります
16
17
  - 名称から配列かどうかを判定しにくい場合があるため、利用するファイル内で配列として型を設定してください`,
17
18
  })
@@ -34,7 +34,8 @@ module.exports = {
34
34
  matcher = context.filename.match(new RegExp(path))
35
35
 
36
36
  if (matcher) {
37
- messages.push([...matcher].reduce(((prev, k, index) => prev.replaceAll(`\$${index}`, k)), message))
37
+ messages.push([...matcher].reduce(((prev, k, index) => prev.replaceAll(`\$${index}`, k)), `${message}
38
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/prohibit-file-name`))
38
39
  }
39
40
  })
40
41
 
@@ -33,7 +33,8 @@ const SCHEMA = [{
33
33
 
34
34
  const CWD = process.cwd()
35
35
 
36
- const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください`
36
+ const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください
37
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/prohibit-import`
37
38
 
38
39
  /**
39
40
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -84,7 +85,8 @@ module.exports = {
84
85
  if (useImported) {
85
86
  context.report({
86
87
  node,
87
- message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
88
+ message: reportMessage ? `${reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported)}
89
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/prohibit-import` : defaultReportMessage(node.source.value, useImported)
88
90
  });
89
91
  }
90
92
  }
@@ -46,7 +46,8 @@ module.exports = {
46
46
  if (name) {
47
47
  context.report({
48
48
  node: exp,
49
- message: `${name}は \`\` で囲まないでください。queryStringを結合するなどのURL生成は ${name} 内で行います。 (例: ${name}({ query: { hoge: 'abc' } })`,
49
+ message: `${name}は \`\` で囲まないでください。queryStringを結合するなどのURL生成は ${name} 内で行います。 (例: ${name}({ query: { hoge: 'abc' } })
50
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/prohibit-path-within-template-literal`,
50
51
  });
51
52
  }
52
53
  })
@@ -185,7 +185,8 @@ module.exports = {
185
185
 
186
186
  context.report({
187
187
  node,
188
- message: deniedModules.length ? `${deniedModules.join(', ')} は ${noExt} からimportしてください` : `${noExt} からimportするか、${barrel} のbarrelファイルを削除して直接import可能にしてください`,
188
+ message: `${deniedModules.length ? `${deniedModules.join(', ')} は ${noExt} からimportしてください` : `${noExt} からimportするか、${barrel} のbarrelファイルを削除して直接import可能にしてください`}
189
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-barrel-import`,
189
190
  });
190
191
  }
191
192
  },
@@ -103,7 +103,8 @@ module.exports = {
103
103
  if (!hit) {
104
104
  context.report({
105
105
  node,
106
- message: localOption.reportMessage || `${localOption.type} ${requireDeclaration}が宣言されていません`,
106
+ message: `${localOption.reportMessage || `${localOption.type} ${requireDeclaration}が宣言されていません`}
107
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-declaration`,
107
108
  })
108
109
  } else if (localOption.use) {
109
110
  const code = context.sourceCode.getText(hit)
@@ -113,7 +114,8 @@ module.exports = {
113
114
  if (!useRegex(u).test(code) && (!localOption.reportMessage || !reported)) {
114
115
  context.report({
115
116
  node: hit,
116
- message: localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`,
117
+ message: `${localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`}
118
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-declaration`,
117
119
  })
118
120
  reported = true
119
121
  }
@@ -79,7 +79,8 @@ module.exports = {
79
79
  if (notExistsExports.length) {
80
80
  context.report({
81
81
  node,
82
- message: `${notExistsExports.join(', ')} をexportしてください`,
82
+ message: `${notExistsExports.join(', ')} をexportしてください
83
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-export`,
83
84
  })
84
85
  }
85
86
  })
@@ -55,7 +55,8 @@ module.exports = {
55
55
  const reportAttributeError = (node) => {
56
56
  context.report({
57
57
  node,
58
- message: `${node.parent.name.name}の${node.name.name}属性に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください`,
58
+ message: `${node.parent.name.name}の${node.name.name}属性に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください
59
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-i18n-text`,
59
60
  })
60
61
  }
61
62
 
@@ -83,7 +84,8 @@ module.exports = {
83
84
  handlers['JSXText[value=/\\S/]'] = (node) => {
84
85
  context.report({
85
86
  node,
86
- message: '子要素に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください',
87
+ message: `子要素に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください
88
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-i18n-text`,
87
89
  })
88
90
  }
89
91
 
@@ -80,7 +80,8 @@ module.exports = {
80
80
  const reporter = (item) => {
81
81
  context.report({
82
82
  node,
83
- message: reportMessage ? reportMessage.replaceAll('{{module}}', actualTarget).replaceAll('{{export}}', item) : defaultReportMessage(actualTarget, item)
83
+ message: `${reportMessage ? reportMessage.replaceAll('{{module}}', actualTarget).replaceAll('{{export}}', item) : defaultReportMessage(actualTarget, item)}
84
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-import`
84
85
  })
85
86
  }
86
87
 
@@ -1,5 +1,17 @@
1
1
  const SCHEMA = []
2
2
 
3
+ const searchBubbleUp = (node) => {
4
+ switch (node.type) {
5
+ case 'Program':
6
+ case 'JSXAttribute':
7
+ return null
8
+ case 'TemplateLiteral':
9
+ return node
10
+ }
11
+
12
+ return searchBubbleUp(node.parent)
13
+ }
14
+
3
15
  /**
4
16
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
5
17
  */
@@ -10,21 +22,21 @@ module.exports = {
10
22
  fixable: 'whitespace',
11
23
  },
12
24
  create(context) {
13
- return {
14
- 'JSXAttribute Literal[value=/(^ | $)/]': (node) => {
15
- return context.report({
16
- node,
17
- message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください',
18
- fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(/^('|")\s+/, '$1').replace(/\s+('|")$/, '$1')),
19
- })
20
- },
21
- 'JSXAttribute TemplateLiteral:has(TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/]))': (node) => {
25
+ const checker = (node) => {
26
+ // HINT: TemplateLiteralがネストしている場合、親側だけチェックする
27
+ if (!searchBubbleUp(node.parent)) {
22
28
  return context.report({
23
29
  node,
24
- message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください',
25
- fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(/(^`\s+|\s+`$)/g, '`')),
30
+ message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
31
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/trim-props`,
32
+ fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(/^('|"|`)\s+/, '$1').replace(/\s+('|"|`)$/, '$1')),
26
33
  })
27
- },
34
+ }
35
+ }
36
+
37
+ return {
38
+ 'JSXAttribute Literal[value=/(^ | $)/]': checker,
39
+ 'JSXAttribute TemplateLiteral:has(>TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/]))': checker,
28
40
  }
29
41
  },
30
42
  }
@@ -11,6 +11,7 @@ const ruleTester = new RuleTester({
11
11
  },
12
12
  })
13
13
  const generateErrorText = (name) => `${name} に href 属性を正しく設定してください
14
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-anchor-has-href-attribute
14
15
  - onClickなどでページ遷移する場合でもhref属性に遷移先のURIを設定してください
15
16
  - Cmd + clickなどのキーボードショートカットに対応出来ます
16
17
  - onClickなどの動作がURLの変更を行わない場合、button要素でマークアップすることを検討してください
@@ -11,6 +11,7 @@ const ruleTester = new RuleTester({
11
11
  },
12
12
  })
13
13
  const defaultErrorMessage = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください
14
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-clickable-element-has-text
14
15
  - 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
15
16
  - SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください
16
17
  - クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`
@@ -17,6 +17,7 @@ const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveCompone
17
17
  const onAttrsText = onAttrs.join(', ')
18
18
 
19
19
  return `${nodeName} に${onAttrsText}を設定するとブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
20
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-delegate-element-has-role-presentation
20
21
  - 方法1: ${nodeName}がinput、buttonやaなどのインタラクティブな要素の場合、コンポーネント名の末尾をインタラクティブなコンポーネントであることがわかる名称に変更してください
21
22
  - "${interactiveComponentRegex}" の正規表現にmatchするコンポーネントに差し替える、もしくは名称を変更してください
22
23
  - 方法2: ${onAttrsText} がコンポーネント内の特定のインタラクティブな要素に設定される場合、名称を具体的なものに変更してください
@@ -29,6 +30,7 @@ const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveCompone
29
30
  - 'role="presentation"' を設定する適切な要素が存在しない場合、div、またはspanでイベントが発生する要素を囲んだ上でrole属性を設定してください`
30
31
  }
31
32
  const messageRolePresentationNotHasInteractive = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => `${nodeName}に 'role="presentation"' が設定されているにも関わらず、子要素にinput、buttonやaなどのインタラクティブな要素が見つからないため、ブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
33
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-delegate-element-has-role-presentation
32
34
  - 方法1: 子要素にインタラクティブな要素が存在するにも関わらずこのエラーが表示されている場合、子要素の名称を変更してください
33
35
  - "${interactiveComponentRegex}" の正規表現にmatchするよう、インタラクティブな子要素全てを差し替える、もしくは名称を変更してください
34
36
  - 方法2: ${nodeName}自体がインタラクティブな要素の場合、'role="presentation"'を削除した上で名称を変更してください
@@ -11,6 +11,7 @@ const ruleTester = new RuleTester({
11
11
  },
12
12
  })
13
13
  const generateErrorText = (elementName) => `${elementName}をform要素で囲むようにマークアップしてください。
14
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-form-control-in-form
14
15
  - form要素で囲むことでスクリーンリーダーに入力フォームであることが正しく伝わる、入力要素にfocusした状態でEnterを押せばsubmitできる、inputのpattern属性を利用できるなどのメリットがあります
15
16
  - 以下のいずれかの方法で修正をおこなってください
16
17
  - 方法1: form要素で ${elementName} を囲んでください。smarthr-ui/ActionDialog、もしくはsmarthr-ui/RemoteTriggerActionDialogを利用している場合、smarthr-ui/FormDialog、smarthr-ui/RemoteTriggerFormDialogに置き換えてください