eslint-plugin-smarthr 0.3.19 → 0.3.21

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.3.21](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.20...v0.3.21) (2024-01-16)
6
+
7
+
8
+ ### Features
9
+
10
+ * a11y-heading-in-sectioning-content の Layout[as={sectioningContentTag}] への対応 ([#91](https://github.com/kufu/eslint-plugin-smarthr/issues/91)) ([62bb051](https://github.com/kufu/eslint-plugin-smarthr/commit/62bb0510d0e91db063a9bd09dab4fbeff6a2844e))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * a11y-delegate-element-has-role-presentationでインタラクティブな要素のチェック処理を厳密に行う ([#100](https://github.com/kufu/eslint-plugin-smarthr/issues/100)) ([e715bff](https://github.com/kufu/eslint-plugin-smarthr/commit/e715bff6a95ab2cbf3652b44f999ad4c98ccb621))
16
+ * import時のrenameの際、typeの場合はチェックを無視する設定を追加 ([#99](https://github.com/kufu/eslint-plugin-smarthr/issues/99)) ([cae899f](https://github.com/kufu/eslint-plugin-smarthr/commit/cae899ff2b012e93b2651c971034553cefeef10e))
17
+
18
+ ### [0.3.20](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.19...v0.3.20) (2024-01-02)
19
+
5
20
  ### [0.3.19](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.18...v0.3.19) (2024-01-01)
6
21
 
7
22
 
@@ -27,12 +27,13 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) =>
27
27
  }) : []
28
28
 
29
29
 
30
- const checkImportedNameToLocalName = (node, base, extended) => {
30
+ const checkImportedNameToLocalName = (node, base, extended, mode) => {
31
31
  entriesesTagNames.forEach(([b, e]) => {
32
32
  if (base.match(b) && !extended.match(e)) {
33
33
  context.report({
34
34
  node,
35
- message: `${extended}を正規表現 "${e.toString()}" がmatchする名称に変更してください`,
35
+ message: `${extended}を正規表現 "${e.toString()}" がmatchする名称に変更してください。
36
+ - ${base}が型の場合、'${mode} type { ${base} as ${extended} }' もしくは '${mode} { type ${base} as ${extended} }' のように明示的に型であることを宣言してください。名称変更が不要になります`,
36
37
  });
37
38
  }
38
39
  })
@@ -42,11 +43,13 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) =>
42
43
  ImportDeclaration: (node) => {
43
44
  checkImportStyledComponents(node, context)
44
45
 
45
- node.specifiers.forEach((s) => {
46
- if (s.imported && s.imported.name !== s.local.name) {
47
- checkImportedNameToLocalName(node, s.imported.name, s.local.name)
48
- }
49
- })
46
+ if (node.importKind !== 'type') {
47
+ node.specifiers.forEach((s) => {
48
+ if (s.importKind !== 'type' && s.imported && s.imported.name !== s.local.name) {
49
+ checkImportedNameToLocalName(node, s.imported.name, s.local.name, 'import')
50
+ }
51
+ })
52
+ }
50
53
  },
51
54
  VariableDeclarator: (node) => {
52
55
  if (!node.init) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -14,6 +14,9 @@ const EXPECTED_NAMES = {
14
14
  'SegmentedControl$': 'SegmentedControl$',
15
15
  'RightFixedNote$': 'RightFixedNote$',
16
16
  'FieldSet$': 'FieldSet$',
17
+ 'Fieldset$': 'Fieldset$',
18
+ 'FormControl$': 'FormControl$',
19
+ 'FormGroup$': 'FormGroup$',
17
20
  '(b|B)utton$': 'Button$',
18
21
  'Anchor$': 'Anchor$',
19
22
  'Link$': 'Link$',
@@ -36,11 +39,10 @@ const UNEXPECTED_NAMES = {
36
39
  '(Link|^a)$': '(Link)$',
37
40
  }
38
41
 
39
- const INTERACTIVE_COMPONENT_NAMES = Object.keys(EXPECTED_NAMES)
42
+ const INTERACTIVE_COMPONENT_NAMES = Object.keys(EXPECTED_NAMES).join('|')
40
43
  const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/
41
44
  const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/
42
-
43
- const INTERACTIVE_NODE_TYPE = ['JSXElement', 'JSXExpressionContainer', 'ConditionalExpression']
45
+ const INTERACTIVE_NODE_TYPE_REGEX = /^(JSXElement|JSXExpressionContainer|ConditionalExpression)$/
44
46
 
45
47
  const messageNonInteractiveEventHandler = (nodeName, interactiveComponentRegex, onAttrs) => {
46
48
  const onAttrsText = onAttrs.join(', ')
@@ -48,13 +50,16 @@ const messageNonInteractiveEventHandler = (nodeName, interactiveComponentRegex,
48
50
  return `${nodeName} に${onAttrsText}を設定するとブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
49
51
  - 方法1: ${nodeName}がinput、buttonやaなどのインタラクティブな要素の場合、コンポーネント名の末尾をインタラクティブなコンポーネントであることがわかる名称に変更してください
50
52
  - "${interactiveComponentRegex}" の正規表現にmatchするコンポーネントに差し替える、もしくは名称を変更してください
51
- - 方法2: インタラクティブな親要素、もしくは子要素が存在する場合、直接${onAttrsText}を設定することを検討してください
52
- - 方法3: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください
53
- - 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください
53
+ - 方法2: ${onAttrsText} がコンポーネント内の特定のインタラクティブな要素に設定される場合、名称を具体的なものに変更してください
54
+ - 属性名を"${INTERACTIVE_ON_REGEX}"に一致しないものに変更してください
55
+ - 例: 対象コンポーネント内に '追加ボタン' が存在する場合、'onClick' という属性名を 'onClickAddButton' に変更する
56
+ - 方法3: インタラクティブな親要素、もしくは子要素が存在する場合、直接${onAttrsText}を設定することを検討してください
57
+ - 方法4: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください
58
+ - 方法5: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください
54
59
  - 'role="presentation"' を設定した要素はマークアップとしての意味がなくなるため、div・span などマークアップとしての意味を持たない要素に設定してください
55
60
  - 'role="presentation"' を設定する適切な要素が存在しない場合、div、またはspanでイベントが発生する要素を囲んだ上でrole属性を設定してください`
56
61
  }
57
- const messageRolePresentationNotHasInteractive = (nodeName, interactiveComponentRegex, onAttrs) => `${nodeName}に 'role="presentation"' が設定されているにも関わらず、子要素にinput、buttonやaなどのインタラクティブな要素が見つからないため、ブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
62
+ const messageRolePresentationNotHasInteractive = (nodeName, interactiveComponentRegex, onAttrs, roleMean) => `${nodeName}に 'role="${roleMean}"' が設定されているにも関わらず、子要素にinput、buttonやaなどのインタラクティブな要素が見つからないため、ブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
58
63
  - 方法1: 子要素にインタラクティブな要素が存在するにも関わらずこのエラーが表示されている場合、子要素の名称を変更してください
59
64
  - "${interactiveComponentRegex}" の正規表現にmatchするよう、インタラクティブな子要素全てを差し替える、もしくは名称を変更してください
60
65
  - 方法2: ${nodeName}自体がインタラクティブな要素の場合、'role="presentation"'を削除した上で名称を変更してください
@@ -81,27 +86,28 @@ module.exports = {
81
86
  },
82
87
  create(context) {
83
88
  const options = context.options[0]
84
- const interactiveComponentRegex = new RegExp(`(${INTERACTIVE_COMPONENT_NAMES.join('|')}${options?.additionalInteractiveComponentRegex ? `|${options.additionalInteractiveComponentRegex.join('|')}` : ''})`)
89
+ const interactiveComponentRegex = new RegExp(`(${INTERACTIVE_COMPONENT_NAMES}${options?.additionalInteractiveComponentRegex ? `|${options.additionalInteractiveComponentRegex.join('|')}` : ''})`)
90
+ const findInteractiveNode = (ec) => ec && ec.type.match(INTERACTIVE_NODE_TYPE_REGEX) && isHasInteractive(ec)
85
91
  const isHasInteractive = (c) => {
86
92
  switch (c.type) {
87
93
  case 'JSXElement': {
88
- if ((c.openingElement.name.name || '').match(interactiveComponentRegex)) {
89
- return true
90
- }
94
+ const name = c.openingElement.name.name
91
95
 
92
- if (c.children.length > 0) {
96
+ if (name && name.match(interactiveComponentRegex)) {
97
+ return true
98
+ } else if (c.children.length > 0) {
93
99
  return !!c.children.find(isHasInteractive)
94
100
  }
95
101
  }
96
102
  case 'JSXExpressionContainer':
97
103
  case 'ConditionalExpression': {
98
- let expression = c
104
+ let e = c
99
105
 
100
106
  if (c.expression) {
101
- expression = c.expression
107
+ e = c.expression
102
108
  }
103
109
 
104
- return !![expression.right, expression.consequent, expression.alternate].find((ec) => INTERACTIVE_NODE_TYPE.includes(ec?.type) && isHasInteractive(ec))
110
+ return !![e.right, e.consequent, e.alternate].find(findInteractiveNode)
105
111
  }
106
112
  }
107
113
 
@@ -114,7 +120,7 @@ module.exports = {
114
120
  const nodeName = node.name.name || '';
115
121
 
116
122
  let onAttrs = []
117
- let isMeanedRole = false
123
+ let roleMean = undefined
118
124
  let isRolePresentation = false
119
125
 
120
126
  node.attributes.forEach((a) => {
@@ -126,37 +132,100 @@ module.exports = {
126
132
  const v = a.value?.value || ''
127
133
 
128
134
  if (v === 'presentation') {
129
- isMeanedRole = isRolePresentation = true
130
- } else if (v.match(MEANED_ROLE_REGEX)) {
131
- isMeanedRole = true
135
+ isRolePresentation = true
136
+ roleMean = v
137
+ } else if (v.match(MEANED_ROLE_REGEX)) {
138
+ roleMean = v
132
139
  }
133
140
  }
134
141
  })
135
142
 
136
- if (!nodeName.match(interactiveComponentRegex)) {
137
- if (onAttrs.length > 0) {
138
- if (!isRolePresentation) {
139
- // HINT: role="presentation"以外で意味があるroleが設定されている場合はエラーにしない
140
- // 基本的にsmarthr-uiでroleの設定などは巻き取る && そもそもroleを設定するよりタグを適切にマークアップすることが優先されるため
141
- // エラーなどには表示しない
142
- if (!isMeanedRole) {
143
- context.report({
144
- node,
145
- message: messageNonInteractiveEventHandler(nodeName, interactiveComponentRegex, onAttrs),
146
- });
143
+ if (nodeName.match(interactiveComponentRegex)) {
144
+ if (isRolePresentation) {
145
+ context.report({
146
+ node,
147
+ message: messageInteractiveHasRolePresentation(nodeName, interactiveComponentRegex)
148
+ })
149
+ }
150
+ } else if (onAttrs.length > 0) {
151
+ // HINT: role="presentation"以外で意味があるroleが設定されている場合はエラーにしない
152
+ // 基本的にsmarthr-uiでroleの設定などは巻き取る && そもそもroleを設定するよりタグを適切にマークアップすることが優先されるため
153
+ // エラーなどには表示しない
154
+ if (!roleMean) {
155
+ context.report({
156
+ node,
157
+ message: messageNonInteractiveEventHandler(nodeName, interactiveComponentRegex, onAttrs),
158
+ });
159
+ // HINT: role='slider' はインタラクティブな要素扱いとするため除外する
160
+ } else if (roleMean !== 'slider') {
161
+ const searchChildren = (n) => {
162
+ switch (n.type) {
163
+ case 'BinaryExpression':
164
+ case 'Identifier':
165
+ case 'JSXEmptyExpression':
166
+ case 'JSXText':
167
+ case 'Literal':
168
+ case 'VariableDeclaration':
169
+ // これ以上childrenが存在しないため終了
170
+ return false
171
+ case 'JSXAttribute':
172
+ return n.value ? searchChildren(n.value) : false
173
+ case 'LogicalExpression':
174
+ return searchChildren(n.right)
175
+ case 'ArrowFunctionExpression':
176
+ return searchChildren(n.body)
177
+ case 'MemberExpression':
178
+ return searchChildren(n.property)
179
+ case 'ReturnStatement':
180
+ case 'UnaryExpression':
181
+ return searchChildren(n.argument)
182
+ case 'ChainExpression':
183
+ case 'JSXExpressionContainer':
184
+ return searchChildren(n.expression)
185
+ case 'BlockStatement':
186
+ return forInSearchChildren(n.body)
187
+ case 'ConditionalExpression':
188
+ return searchChildren(n.consequent) || searchChildren(n.alternate)
189
+ case 'CallExpression': {
190
+ return forInSearchChildren(n.arguments)
191
+ }
192
+ case 'JSXFragment':
193
+ break
194
+ case 'JSXElement': {
195
+ const name = n.openingElement.name.name || ''
196
+
197
+ if (name.match(interactiveComponentRegex)) {
198
+ return true
199
+ }
200
+
201
+ if (forInSearchChildren(n.openingElement.attributes)) {
202
+ return true
203
+ }
204
+
205
+ break
206
+ }
207
+ }
208
+
209
+ return n.children ? forInSearchChildren(n.children) : false
210
+ }
211
+
212
+ const forInSearchChildren = (ary) => {
213
+ for (const i in ary) {
214
+ if (searchChildren(ary[i])) {
215
+ return true
216
+ }
147
217
  }
148
- } else if (!node.parent.children.find(isHasInteractive)) {
218
+
219
+ return false
220
+ }
221
+
222
+ if (!forInSearchChildren(node.parent.children)) {
149
223
  context.report({
150
224
  node,
151
- message: messageRolePresentationNotHasInteractive(nodeName, interactiveComponentRegex, onAttrs)
225
+ message: messageRolePresentationNotHasInteractive(nodeName, interactiveComponentRegex, onAttrs, roleMean)
152
226
  })
153
227
  }
154
228
  }
155
- } else if (isRolePresentation) {
156
- context.report({
157
- node,
158
- message: messageInteractiveHasRolePresentation(nodeName, interactiveComponentRegex)
159
- })
160
229
  }
161
230
  },
162
231
  };
@@ -10,6 +10,10 @@ const EXPECTED_NAMES = {
10
10
  'Nav$': 'Nav$',
11
11
  'Section$': 'Section$',
12
12
  'ModelessDialog$': 'ModelessDialog$',
13
+ 'Center$': 'Center$',
14
+ 'Reel$': 'Reel$',
15
+ 'Sidebar$': 'Sidebar$',
16
+ 'Stack$': 'Stack$',
13
17
  }
14
18
 
15
19
  const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています
@@ -34,6 +38,10 @@ const UNEXPECTED_NAMES = {
34
38
  '(Section)$',
35
39
  unexpectedMessageTemplate,
36
40
  ],
41
+ 'Center$': '(Center)$',
42
+ 'Reel$': '(Reel)$',
43
+ 'Sidebar$': '(Sidebar)$',
44
+ 'Stack$': '(Stack)$',
37
45
  }
38
46
 
39
47
  const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
@@ -42,6 +50,9 @@ const declaratorHeadingRegex = /Heading$/
42
50
  const sectioningRegex = /((A(rticle|side))|Nav|Section|^SectioningFragment)$/
43
51
  const bareTagRegex = /^(article|aside|nav|section)$/
44
52
  const modelessDialogRegex = /ModelessDialog$/
53
+ const layoutComponentRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
54
+
55
+ const includeSectioningAsAttr = ({name: { name }, value: { value }}) => name === 'as' && value.match(bareTagRegex)
45
56
 
46
57
  const noHeadingTagNames = ['span', 'legend']
47
58
  const ignoreHeadingCheckParentType = ['Program', 'ExportNamedDeclaration']
@@ -89,7 +100,10 @@ const reportMessageBareToSHR = (tagName, visibleExample) => {
89
100
  const searchBubbleUp = (node) => {
90
101
  if (
91
102
  node.type === 'Program' ||
92
- node.type === 'JSXElement' && node.openingElement.name.name?.match(sectioningRegex)
103
+ node.type === 'JSXElement' && node.openingElement.name.name && (
104
+ node.openingElement.name.name.match(sectioningRegex) ||
105
+ node.openingElement.name.name.match(layoutComponentRegex) && node.openingElement.attributes.some(includeSectioningAsAttr)
106
+ )
93
107
  ) {
94
108
  return node
95
109
  }
@@ -12,16 +12,19 @@ const ruleTester = new RuleTester({
12
12
  },
13
13
  });
14
14
 
15
- const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$)/'
15
+ const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$)/'
16
16
  const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
17
17
  const onAttrsText = onAttrs.join(', ')
18
18
 
19
19
  return `${nodeName} に${onAttrsText}を設定するとブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。
20
20
  - 方法1: ${nodeName}がinput、buttonやaなどのインタラクティブな要素の場合、コンポーネント名の末尾をインタラクティブなコンポーネントであることがわかる名称に変更してください
21
21
  - "${interactiveComponentRegex}" の正規表現にmatchするコンポーネントに差し替える、もしくは名称を変更してください
22
- - 方法2: インタラクティブな親要素、もしくは子要素が存在する場合、直接${onAttrsText}を設定することを検討してください
23
- - 方法3: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください
24
- - 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください
22
+ - 方法2: ${onAttrsText} がコンポーネント内の特定のインタラクティブな要素に設定される場合、名称を具体的なものに変更してください
23
+ - 属性名を"/^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/"に一致しないものに変更してください
24
+ - 例: 対象コンポーネント内に '追加ボタン' が存在する場合、'onClick' という属性名を 'onClickAddButton' に変更する
25
+ - 方法3: インタラクティブな親要素、もしくは子要素が存在する場合、直接${onAttrsText}を設定することを検討してください
26
+ - 方法4: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください
27
+ - 方法5: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください
25
28
  - 'role="presentation"' を設定した要素はマークアップとしての意味がなくなるため、div・span などマークアップとしての意味を持たない要素に設定してください
26
29
  - 'role="presentation"' を設定する適切な要素が存在しない場合、div、またはspanでイベントが発生する要素を囲んだ上でrole属性を設定してください`
27
30
  }
@@ -52,6 +55,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
52
55
  { code: '<Wrapper onClick={any} role="presentation">{any ? <AnyButton /> : null}</Wrapper>' },
53
56
  { code: '<Wrapper onClick={any} role="presentation">{any1 ? (any2 ? <HogeLink /> : null) : null}</Wrapper>' },
54
57
  { code: '<Wrapper onClick={any} role="presentation">{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>' },
58
+ { code: '<Wrapper onClick={any} role="slider">Hoge</Wrapper>' },
55
59
  ],
56
60
  invalid: [
57
61
  { code: '<Input role="presentation" />', errors: [ { message: messageInteractiveHasRolePresentation('Input') } ] },
@@ -61,7 +65,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
61
65
  { code: '<div onClick={any} onSubmit={any2} role="presentation"><Hoge /></div>', errors: [ { message: messageRolePresentationNotHasInteractive('div', ['onClick', 'onSubmit']) } ] },
62
66
  { code: '<div onClick={any}><Link /></div>', errors: [ { message: messageNonInteractiveEventHandler('div', ['onClick']) } ] },
63
67
  { code: '<Wrapper onClick={any}><Link /></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
64
- { code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|^Hoge$)/') } ] },
68
+ { code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|^Hoge$)/') } ] },
65
69
  { code: '<Wrapper onClick={any} onChange={anyany}><any><Link /></any></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick', 'onChange']) } ] },
66
70
  { code: '<Wrapper onClick={any}>{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
67
71
  ],
@@ -46,11 +46,20 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
46
46
  { code: 'const FugaNav = styled(HogeNav)``' },
47
47
  { code: 'const FugaSection = styled(HogeSection)``' },
48
48
  { code: "const FugaHeading = styled(Heading).attrs(() => ({ type: 'blockTitle' }))``" },
49
+ { code: 'const FugaCenter = styled(HogeCenter)``' },
50
+ { code: 'const FugaReel = styled(HogeReel)``' },
51
+ { code: 'const FugaSidebar = styled(HogeSidebar)``' },
52
+ { code: 'const FugaStack = styled(HogeStack)``' },
49
53
  { code: '<PageHeading>hoge</PageHeading>' },
50
54
  { code: '<Section><Heading>hoge</Heading></Section>' },
51
55
  { code: '<><Section><Heading>hoge</Heading></Section><Section><Heading>fuga</Heading></Section></>' },
52
56
  { code: 'const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>' },
53
57
  { code: 'export const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>' },
58
+ { code: '<Center as="section"><div><Heading>hoge</Heading></div></Center>' },
59
+ { code: '<Cluster as="section"><div><Heading>hoge</Heading></div></Cluster>' },
60
+ { code: '<Reel as="aside"><div><Heading>hoge</Heading></div></Reel>' },
61
+ { code: '<Sidebar as="nav"><div><Heading>hoge</Heading></div></Sidebar>' },
62
+ { code: '<Stack as="section"><div><Heading>hoge</Heading></div></Stack>' },
54
63
  ],
55
64
  invalid: [
56
65
  { code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
@@ -74,6 +83,10 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
74
83
  { code: 'const Fuga = styled(HogeAside)``', errors: [ { message: `Fugaを正規表現 "/Aside$/" がmatchする名称に変更してください` } ] },
75
84
  { code: 'const Fuga = styled(HogeNav)``', errors: [ { message: `Fugaを正規表現 "/Nav$/" がmatchする名称に変更してください` } ] },
76
85
  { code: 'const Fuga = styled(HogeSection)``', errors: [ { message: `Fugaを正規表現 "/Section$/" がmatchする名称に変更してください` } ] },
86
+ { code: 'const Fuga = styled(HogeCenter)``', errors: [ { message: `Fugaを正規表現 "/Center$/" がmatchする名称に変更してください` } ] },
87
+ { code: 'const Fuga = styled(HogeReel)``', errors: [ { message: `Fugaを正規表現 "/Reel$/" がmatchする名称に変更してください` } ] },
88
+ { code: 'const Fuga = styled(HogeSidebar)``', errors: [ { message: `Fugaを正規表現 "/Sidebar$/" がmatchする名称に変更してください` } ] },
89
+ { code: 'const Fuga = styled(HogeStack)``', errors: [ { message: `Fugaを正規表現 "/Stack$/" がmatchする名称に変更してください` } ] },
77
90
  { code: 'const StyledArticle = styled.article``', errors: [ { message: `"article"を利用せず、smarthr-ui/Articleを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.article" -> "styled(Article)")` } ] },
78
91
  { code: 'const StyledAside = styled.aside``', errors: [ { message: `"aside"を利用せず、smarthr-ui/Asideを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.aside" -> "styled(Aside)")` } ] },
79
92
  { code: 'const StyledNav = styled.nav``', errors: [ { message: `"nav"を利用せず、smarthr-ui/Navを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.nav" -> "styled(Nav)")` } ] },