eslint-plugin-smarthr 0.5.1 → 0.5.3

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,22 @@
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.5.3](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.2...v0.5.3) (2024-03-26)
6
+
7
+
8
+ ### Features
9
+
10
+ * a11y-heading-in-sectioning-contentでHeadingを持たないSectioning Contentを検知するように修正 ([#124](https://github.com/kufu/eslint-plugin-smarthr/issues/124)) ([8b96de0](https://github.com/kufu/eslint-plugin-smarthr/commit/8b96de0c9a474b0c8d72a4b9c3b3b351d2cfb4e5))
11
+ * smarthr-ui/Layouts系コンポーネントの利用方法をチェックする best-practice-for-layouts ルールを追加する ([#126](https://github.com/kufu/eslint-plugin-smarthr/issues/126)) ([e0324e4](https://github.com/kufu/eslint-plugin-smarthr/commit/e0324e4ffab0413e61811cf7cf7f129c0602e0f0))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * a11y-input-in-form-control で Clusterを拡張する際のチェックを追加 ([#125](https://github.com/kufu/eslint-plugin-smarthr/issues/125)) ([f723e24](https://github.com/kufu/eslint-plugin-smarthr/commit/f723e24db86c1095178bb1f28636147e7b619bf2))
17
+ * a11y-numbered-text-within-olのチェックで、属性の値でstyleに数値を指定しているような値の場合、無視する ([#123](https://github.com/kufu/eslint-plugin-smarthr/issues/123)) ([77a6278](https://github.com/kufu/eslint-plugin-smarthr/commit/77a6278ff8ec58c99843746442e1f3c0e54574c5))
18
+
19
+ ### [0.5.2](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.1...v0.5.2) (2024-03-17)
20
+
5
21
  ### [0.5.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.0...v0.5.1) (2024-03-17)
6
22
 
7
23
 
package/index.js CHANGED
@@ -23,23 +23,6 @@ function generateRulesMap() {
23
23
  return rulesMap;
24
24
  }
25
25
 
26
- const DISAPPROVE_RULE_NAMES = [
27
- 'a11y-form-control-in-form', // formを使用することの是非について議論中のため
28
-
29
- // ルールが動作するために設定が必要なものはrecommendedに含めない
30
- 'format-import-path',
31
- 'format-translate-component',
32
- 'jsx-start-with-spread-attributes',
33
- 'no-import-other-domain',
34
- 'prohibit-file-name',
35
- 'prohibit-import',
36
- 'prohibit-path-within-template-literal',
37
- 'require-declaration',
38
- 'require-export',
39
- 'require-import',
40
- ]
41
- const DISAPPROVE_RULE_NAMES_REGEX = new RegExp(`^(${DISAPPROVE_RULE_NAMES.join('|')})$`)
42
-
43
26
  function generateRecommendedConfig(rules) {
44
27
  let config = {
45
28
  plugins: ['smarthr'],
@@ -47,9 +30,7 @@ function generateRecommendedConfig(rules) {
47
30
  };
48
31
 
49
32
  for (let ruleName of Object.keys(rules)) {
50
- if (!DISAPPROVE_RULE_NAMES_REGEX.test(ruleName)) {
51
- config.rules[`smarthr/${ruleName}`] = 'off';
52
- }
33
+ config.rules[`smarthr/${ruleName}`] = 'off';
53
34
  }
54
35
 
55
36
  return config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -52,11 +52,11 @@ const bareTagRegex = /^(article|aside|nav|section)$/
52
52
  const modelessDialogRegex = /ModelessDialog$/
53
53
  const layoutComponentRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
54
54
  const asRegex = /^(as|forwardedAs)$/
55
+ const ignoreCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
56
+ const noHeadingTagNamesRegex = /^(span|legend)$/
57
+ const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
55
58
 
56
- const includeSectioningAsAttr = (a) => a.name?.name.match(asRegex) && a.value.value.match(bareTagRegex)
57
-
58
- const noHeadingTagNames = ['span', 'legend']
59
- const ignoreHeadingCheckParentType = ['Program', 'ExportNamedDeclaration']
59
+ const includeSectioningAsAttr = (a) => a.name?.name.match(asRegex) && bareTagRegex.test(a.value.value)
60
60
 
61
61
  const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
62
62
  - smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。
@@ -102,8 +102,8 @@ const searchBubbleUp = (node) => {
102
102
  if (
103
103
  node.type === 'Program' ||
104
104
  node.type === 'JSXElement' && node.openingElement.name.name && (
105
- node.openingElement.name.name.match(sectioningRegex) ||
106
- node.openingElement.name.name.match(layoutComponentRegex) && node.openingElement.attributes.some(includeSectioningAsAttr)
105
+ sectioningRegex.test(node.openingElement.name.name) ||
106
+ layoutComponentRegex.test(node.openingElement.name.name) && node.openingElement.attributes.some(includeSectioningAsAttr)
107
107
  )
108
108
  ) {
109
109
  return node
@@ -111,16 +111,113 @@ const searchBubbleUp = (node) => {
111
111
 
112
112
  if (
113
113
  // Headingコンポーネントの拡張なので対象外
114
- node.type === 'VariableDeclarator' && ignoreHeadingCheckParentType.includes(node.parent.parent?.type) && node.id.name.match(declaratorHeadingRegex) ||
115
- node.type === 'FunctionDeclaration' && ignoreHeadingCheckParentType.includes(node.parent.type) && node.id.name.match(declaratorHeadingRegex) ||
114
+ node.type === 'VariableDeclarator' && node.parent.parent?.type.match(ignoreHeadingCheckParentTypeRegex) && declaratorHeadingRegex.test(node.id.name) ||
115
+ node.type === 'FunctionDeclaration' && ignoreHeadingCheckParentTypeRegex.test(node.parent.type) && declaratorHeadingRegex.test(node.id.name) ||
116
116
  // ModelessDialogのheaderにHeadingを設定している場合も対象外
117
- node.type === 'JSXAttribute' && node.name.name === 'header' && node.parent.name.name.match(modelessDialogRegex)
117
+ node.type === 'JSXAttribute' && node.name.name === 'header' && modelessDialogRegex.test(node.parent.name.name)
118
118
  ) {
119
119
  return null
120
120
  }
121
121
 
122
122
  return searchBubbleUp(node.parent)
123
123
  }
124
+ const searchBubbleUpSections = (node) => {
125
+ switch (node.type) {
126
+ case 'Program':
127
+ // rootまで検索した場合は確定でエラーにする
128
+ return null
129
+ case 'VariableDeclarator':
130
+ // SectioningContent系コンポーネントの拡張の場合は対象外
131
+ if (ignoreCheckParentTypeRegex.test(node.parent.parent?.type) && sectioningRegex.test(node.id.name)) {
132
+ return node
133
+ }
134
+
135
+ break
136
+ case 'FunctionDeclaration':
137
+ case 'ClassDeclaration':
138
+ // SectioningContent系コンポーネントの拡張の場合は対象外
139
+ if (ignoreCheckParentTypeRegex.test(node.parent.type) && sectioningRegex.test(node.id.name)) {
140
+ return node
141
+ }
142
+
143
+ break
144
+ }
145
+
146
+ return searchBubbleUpSections(node.parent)
147
+ }
148
+ const searchChildren = (n) => {
149
+ switch (n.type) {
150
+ case 'BinaryExpression':
151
+ case 'Identifier':
152
+ case 'JSXEmptyExpression':
153
+ case 'JSXText':
154
+ case 'Literal':
155
+ case 'VariableDeclaration':
156
+ // これ以上childrenが存在しないため終了
157
+ return false
158
+ case 'JSXAttribute':
159
+ return n.value ? searchChildren(n.value) : false
160
+ case 'LogicalExpression':
161
+ return searchChildren(n.right)
162
+ case 'ArrowFunctionExpression':
163
+ return searchChildren(n.body)
164
+ case 'MemberExpression':
165
+ return searchChildren(n.property)
166
+ case 'ReturnStatement':
167
+ case 'UnaryExpression':
168
+ return searchChildren(n.argument)
169
+ case 'ChainExpression':
170
+ case 'JSXExpressionContainer':
171
+ return searchChildren(n.expression)
172
+ case 'BlockStatement': {
173
+ return forInSearchChildren(n.body)
174
+ }
175
+ case 'ConditionalExpression': {
176
+ return searchChildren(n.consequent) || searchChildren(n.alternate)
177
+ }
178
+ case 'CallExpression': {
179
+ return forInSearchChildren(n.arguments)
180
+ }
181
+ case 'JSXFragment':
182
+ break
183
+ case 'JSXElement': {
184
+ const name = n.openingElement.name.name || ''
185
+
186
+ if (
187
+ sectioningRegex.test(name) ||
188
+ layoutComponentRegex.test(name) && n.openingElement.attributes.some(includeSectioningAsAttr)
189
+ ) {
190
+ return false
191
+ } else if (
192
+ (
193
+ headingRegex.test(name) &&
194
+ !n.openingElement.attributes.find(findTagAttr)?.value.value.match(noHeadingTagNamesRegex)
195
+ ) ||
196
+ forInSearchChildren(n.openingElement.attributes)
197
+ ) {
198
+ return true
199
+ }
200
+
201
+ break
202
+ }
203
+ }
204
+
205
+ return n.children ? forInSearchChildren(n.children) : false
206
+ }
207
+
208
+ const forInSearchChildren = (ary) => {
209
+ let r = false
210
+
211
+ for (const i in ary) {
212
+ r = searchChildren(ary[i])
213
+
214
+ if (r) {
215
+ break
216
+ }
217
+ }
218
+
219
+ return r
220
+ }
124
221
 
125
222
  const findTagAttr = (a) => a.name?.name == 'tag'
126
223
 
@@ -151,15 +248,15 @@ module.exports = {
151
248
  message,
152
249
  })
153
250
  // Headingに明示的にtag属性が設定されており、それらが span or legend の場合はHeading扱いしない
154
- } else if (elementName.match(headingRegex)) {
251
+ } else if (headingRegex.test(elementName)) {
155
252
  const tagAttr = node.attributes.find(findTagAttr)
156
253
 
157
- if (!noHeadingTagNames.includes(tagAttr?.value.value)) {
254
+ if (!tagAttr?.value.value.match(noHeadingTagNamesRegex)) {
158
255
  const result = searchBubbleUp(node.parent)
159
256
  let hit = false
160
257
 
161
258
  if (result) {
162
- if (elementName.match(pageHeadingRegex)) {
259
+ if (pageHeadingRegex.test(elementName)) {
163
260
  h1s.push(node)
164
261
 
165
262
  if (h1s.length > 1) {
@@ -181,7 +278,7 @@ module.exports = {
181
278
  node,
182
279
  message: rootHeadingMessage,
183
280
  })
184
- } else if (sections.find((s) => s === result)) {
281
+ } else if (sections.includes(result)) {
185
282
  hit = true
186
283
  context.report({
187
284
  node,
@@ -199,6 +296,18 @@ module.exports = {
199
296
  })
200
297
  }
201
298
  }
299
+ } else if (!node.selfClosing) {
300
+ const isSection = sectioningRegex.test(elementName)
301
+ const layoutSectionAsAttr = !isSection && layoutComponentRegex.test(elementName) ? node.attributes.find(includeSectioningAsAttr) : null
302
+
303
+ if ((isSection || layoutSectionAsAttr) && !searchBubbleUpSections(node.parent.parent) && !forInSearchChildren(node.parent.children)) {
304
+ context.report({
305
+ node,
306
+ message: `${isSection ? elementName : `<${elementName} ${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}">`} はHeading要素を含んでいません。
307
+ - SectioningContentはHeadingを含むようにマークアップする必要があります
308
+ - Headingにするべき適切な文字列が存在しない場合、 ${isSection ? `${elementName} は削除するか、SectioningContentではない要素に差し替えてください` : `${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}"を削除、もしくは別の要素に変更してください`}`,
309
+ })
310
+ }
202
311
  }
203
312
  },
204
313
  }
@@ -32,6 +32,7 @@ const EXPECTED_NAMES = {
32
32
  '(A|^a)side$': '(Aside)$',
33
33
  '(N|^n)av$': '(Nav)$',
34
34
  '(S|^s)ection$': '(Section)$',
35
+ 'Cluster$': '(Cluster)$',
35
36
  'Center$': '(Center)$',
36
37
  'Reel$': '(Reel)$',
37
38
  'Sidebar$': '(Sidebar)$',
@@ -9,7 +9,7 @@ const UNEXPECTED_NAMES = EXPECTED_NAMES
9
9
  const NUMBERED_TEXT_REGEX = /^[\s\n]*(([0-9])([^0-9]{2})[^\s\n]*)/
10
10
  const ORDERED_LIST_REGEX = /(Ordered(.*)List|^ol)$/
11
11
  const SELECT_REGEX = /(S|s)elect$/
12
- const IGNORE_ATTRIBUTE_REGEX = /((w|W)idth|(h|H)eight)$/
12
+ const IGNORE_ATTRIBUTE_VALUE_REGEX = /^[0-9]+(px|em|ex|ch|rem|lh|rlh|vw|vh|vmin|vmax|vb|vi|svw|svh|lvw|lvh|dvw|dvh|%)?$/
13
13
  const AS_ATTRIBUTE_REGEX = /^(as|forwardedAs)$/
14
14
 
15
15
  const findAsOlAttr = (a) => a.type === 'JSXAttribute' && AS_ATTRIBUTE_REGEX.test(a.name?.name) && a.value?.value === 'ol'
@@ -128,7 +128,7 @@ module.exports = {
128
128
  return {
129
129
  ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
130
130
  JSXAttribute: (node) => {
131
- if (node.value?.value && !IGNORE_ATTRIBUTE_REGEX.test(node.name?.name)) {
131
+ if (node.value?.value && !IGNORE_ATTRIBUTE_VALUE_REGEX.test(node.value.value)) {
132
132
  checker(node, node.value.value.match(NUMBERED_TEXT_REGEX))
133
133
  }
134
134
  },
@@ -0,0 +1,53 @@
1
+ # smarthr/best-practice-for-layouts
2
+
3
+ - smarthr-ui/Layoutsに属するコンポーネントの利用方法をチェックするルールです
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/best-practice-for-layouts': 'error', // 'warn', 'off',
11
+ },
12
+ }
13
+ ```
14
+
15
+ ## ❌ Incorrect
16
+
17
+ ```jsx
18
+ // 子が複数無いためエラー
19
+ <Cluster>
20
+ <Any />
21
+ </Cluster>
22
+ <StyledStack>
23
+ {flg ? 'a' : 'b'}
24
+ </StyledStack>
25
+ ```
26
+
27
+ ## ✅ Correct
28
+
29
+ ```jsx
30
+ // 子が複数あるのでOK
31
+ <Cluster>
32
+ <Any />
33
+ <Any />
34
+ </Cluster>
35
+
36
+ <StyledStack>
37
+ {flg ? 'a' : (
38
+ <>
39
+ <Any />
40
+ <Any />
41
+ </>
42
+ )}
43
+ </StyledStack>
44
+
45
+ // Cluster、かつ右寄せをしている場合は子一つでもOK
46
+ <Cluster justify="end">
47
+ <Any />
48
+ </Cluster>
49
+ <Cluster justify="flex-end">
50
+ <Any />
51
+ <Any />
52
+ </Cluster>
53
+ ```
@@ -0,0 +1,100 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
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
+ const REGEX_NLSP = /^\s*\n+\s*$/
17
+ const FLEX_END_REGEX = /^(flex-)?end$/
18
+
19
+ const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
20
+ const checkFalsyJSXText = (c) => (
21
+ !(
22
+ c.type === 'JSXText' && c.value.match(REGEX_NLSP) ||
23
+ c.type === 'JSXEmptyExpression'
24
+ )
25
+ )
26
+
27
+ const findJustifyAttr = (a) => a.name?.name === 'justify'
28
+
29
+ const searchChildren = (node) => {
30
+ if (
31
+ node.type === 'JSXFragment' ||
32
+ node.type === 'JSXElement' && node.openingElement.name?.name === 'SectioningFragment'
33
+ ) {
34
+ const children = node.children.filter(checkFalsyJSXText)
35
+
36
+ if (children.length > 1) {
37
+ return false
38
+ }
39
+ }
40
+
41
+ switch(node.type) {
42
+ case 'JSXExpressionContainer':
43
+ return searchChildren(node.expression)
44
+ case 'CallExpression':
45
+ return node.callee.property?.name !== 'map'
46
+ case 'ConditionalExpression':
47
+ return searchChildren(node.consequent) && searchChildren(node.alternate)
48
+ case 'LogicalExpression':
49
+ return searchChildren(node.right)
50
+ }
51
+
52
+ return true
53
+ }
54
+
55
+ const SCHEMA = []
56
+
57
+ module.exports = {
58
+ meta: {
59
+ type: 'problem',
60
+ schema: SCHEMA,
61
+ },
62
+ create(context) {
63
+ return {
64
+ ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
65
+ JSXOpeningElement: (node) => {
66
+ const nodeName = node.name.name;
67
+
68
+ if (nodeName && !node.selfClosing) {
69
+ const matcher = nodeName.match(MULTI_CHILDREN_REGEX)
70
+
71
+ if (matcher) {
72
+ const children = node.parent.children.filter(checkFalsyJSXText)
73
+
74
+ if (children.length === 1) {
75
+ const layoutType = matcher[1]
76
+ const justifyAttr = layoutType === 'Cluster' ? node.attributes.find(findJustifyAttr) : null
77
+
78
+ if (justifyAttr && FLEX_END_REGEX.test(justifyAttr.value.value)) {
79
+ return
80
+ }
81
+
82
+ if (searchChildren(children[0])) {
83
+ context.report({
84
+ node,
85
+ message:
86
+ justifyAttr && justifyAttr.value.value === 'center'
87
+ ? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください`
88
+ : `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。
89
+ - styleを確認し、div・spanなど、別要素でマークアップし直すか、${nodeName}を削除してください
90
+ - as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
91
+ })
92
+ }
93
+ }
94
+ }
95
+ }
96
+ },
97
+ }
98
+ },
99
+ }
100
+ module.exports.schema = SCHEMA
@@ -22,6 +22,9 @@ const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存
22
22
  const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
23
23
  const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Section, SectioningFragmentのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
24
24
  - tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
25
+ const notHaveHeadingMessage = (elementName) => `${elementName} はHeading要素を含んでいません。
26
+ - SectioningContentはHeadingを含むようにマークアップする必要があります
27
+ - Headingにするべき適切な文字列が存在しない場合、 ${elementName} は削除するか、SectioningContentではない要素に差し替えてください`
25
28
 
26
29
  ruleTester.run('a11y-heading-in-sectioning-content', rule, {
27
30
  valid: [
@@ -137,5 +140,8 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
137
140
  { code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
138
141
  { code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
139
142
  { code: '<Section><Heading tag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
143
+ { code: '<Section></Section>', errors: [ { message: notHaveHeadingMessage('Section') } ] },
144
+ { code: '<Aside><HogeSection></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') }, { message: notHaveHeadingMessage('HogeSection') } ] },
145
+ { code: '<Aside><HogeSection><Heading /></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') } ] },
140
146
  ],
141
147
  });
@@ -0,0 +1,84 @@
1
+ const rule = require('../rules/best-practice-for-layouts')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ ecmaVersion: 2018,
7
+ ecmaFeatures: {
8
+ experimentalObjectRestSpread: true,
9
+ jsx: true,
10
+ },
11
+ sourceType: 'module',
12
+ },
13
+ })
14
+
15
+ const errorMessage = (type, name) => `${name}には子要素が一つしか無いため、${type}でマークアップする意味がありません。
16
+ - styleを確認し、div・spanなど、別要素でマークアップし直すか、${name}を削除してください
17
+ - as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
18
+
19
+ ruleTester.run('best-practice-for-button-element', rule, {
20
+ valid: [
21
+ { code: `<Center />` },
22
+ { code: `<Cluster />` },
23
+ { code: `<Stack />` },
24
+ { code: `<HogeCenter />` },
25
+ { code: `<HogeCluster />` },
26
+ { code: `<HogeStack />` },
27
+ { code: `<Center><Hoge /></Center>` },
28
+ { code: `<Center><Hoge /><Hoge /></Center>` },
29
+ { code: `<Stack><Hoge /><Hoge /></Stack>` },
30
+ { code: `<Stack>{a}<Hoge /></Stack>` },
31
+ { code: `<AnyStack>{a.map(action)}</AnyStack>` },
32
+ { code: `<AnyStack>{a && <><Hoge /><Hoge /></>}</AnyStack>` },
33
+ { code: `<AnyStack>{a && a.map(action)}</AnyStack>` },
34
+ { code: `<AnyStack>{a && a.b.map(action)}</AnyStack>` },
35
+ { code: `<AnyStack>{a || <><Hoge /><Hoge /></>}</AnyStack>` },
36
+ { code: `<AnyStack>{a || a.map(action)}</AnyStack>` },
37
+ { code: `<AnyStack>{a || a.b.map(action)}</AnyStack>` },
38
+ { code: `<AnyStack>{a ? a.b.map(action) : <Hoge />}</AnyStack>` },
39
+ { code: `<AnyStack>{a ? <Hoge /> : a.b.map(action)}</AnyStack>` },
40
+ { code: `<AnyStack>{a ? <Hoge /> : a ? <Hoge /> : a.b.map(action)}</AnyStack>` },
41
+ { code: `<Cluster><Hoge /><Hoge /></Cluster>` },
42
+ { code: `<Cluster>{a}<Hoge /></Cluster>` },
43
+ { code: `<AnyCluster>{a.map(action)}</AnyCluster>` },
44
+ { code: `<AnyCluster>{a && <><Hoge /><Hoge /></>}</AnyCluster>` },
45
+ { code: `<AnyCluster>{a && a.map(action)}</AnyCluster>` },
46
+ { code: `<AnyCluster>{a && a.b.map(action)}</AnyCluster>` },
47
+ { code: `<AnyCluster>{a || <><Hoge /><Hoge /></>}</AnyCluster>` },
48
+ { code: `<AnyCluster>{a || a.map(action)}</AnyCluster>` },
49
+ { code: `<AnyCluster>{a || a.b.map(action)}</AnyCluster>` },
50
+ { code: `<AnyCluster>{a ? a.b.map(action) : <Hoge />}</AnyCluster>` },
51
+ { code: `<AnyCluster>{a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
52
+ { code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
53
+ { code: `<Cluster justify="flex-end">{a}</Cluster>` },
54
+ { code: `<HogeCluster justify="end">{a}</HogeCluster>` },
55
+ ],
56
+ invalid: [
57
+ { code: `<Stack><Hoge /></Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
58
+ { code: `<Stack>{a}</Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
59
+ { code: `<AnyStack>{a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
60
+ { code: `<AnyStack>{a && <><Hoge /></>}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
61
+ { code: `<AnyStack>{a && a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
62
+ { code: `<AnyStack>{a && a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
63
+ { code: `<AnyStack>{a || <><Hoge /></>}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
64
+ { code: `<AnyStack>{a || a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
65
+ { code: `<AnyStack>{a || a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
66
+ { code: `<AnyStack>{a ? a.b.hoge(action) : <Hoge />}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
67
+ { code: `<AnyStack>{a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
68
+ { code: `<AnyStack>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
69
+ { code: `<Cluster><Hoge /></Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
70
+ { code: `<Cluster>{a}</Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
71
+ { code: `<AnyCluster>{a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
72
+ { code: `<AnyCluster>{a && <><Hoge /></>}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
73
+ { code: `<AnyCluster>{a && a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
74
+ { code: `<AnyCluster>{a && a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
75
+ { code: `<AnyCluster>{a || <><Hoge /></>}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
76
+ { code: `<AnyCluster>{a || a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
77
+ { code: `<AnyCluster>{a || a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
78
+ { code: `<AnyCluster>{a ? a.b.hoge(action) : <Hoge />}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
79
+ { code: `<AnyCluster>{a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
80
+ { code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
81
+ { code: `<HogeCluster justify="center">{a}</HogeCluster>`, errors: [ { message: 'HogeCluster は smarthr-ui/Cluster ではなく smarthr-ui/Center でマークアップしてください' } ] },
82
+ ]
83
+ })
84
+