eslint-plugin-smarthr 2.5.0 → 3.1.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.
- package/CHANGELOG.md +23 -0
- package/README.md +0 -1
- package/package.json +3 -3
- package/rules/a11y-input-has-name-attribute/index.js +3 -1
- package/rules/best-practice-for-layouts/README.md +10 -0
- package/rules/best-practice-for-layouts/index.js +42 -32
- package/rules/require-i18n-text/index.js +26 -20
- package/test/best-practice-for-layouts.js +23 -28
- package/test/require-i18n-text.js +16 -0
- package/rules/a11y-required-layout-as-attribute/README.md +0 -54
- package/rules/a11y-required-layout-as-attribute/index.js +0 -65
- package/test/a11y-required-layout-as-attribute.js +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
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
|
+
## [3.1.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.0.0...eslint-plugin-smarthr-v3.1.0) (2025-12-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* best-practice-for-layoutsにradio, checkに対するチェックを追加 ([#937](https://github.com/kufu/tamatebako/issues/937)) ([97a393a](https://github.com/kufu/tamatebako/commit/97a393a9ae2a4cdfbfe8b82aba8cf15f22991e45))
|
|
11
|
+
|
|
12
|
+
## [3.0.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v2.5.0...eslint-plugin-smarthr-v3.0.0) (2025-12-08)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### ⚠ BREAKING CHANGES
|
|
16
|
+
|
|
17
|
+
* a11y-required-layout-as-attributeを削除する ([#919](https://github.com/kufu/tamatebako/issues/919))
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* a11y-required-layout-as-attributeを削除する ([#919](https://github.com/kufu/tamatebako/issues/919)) ([fbf6897](https://github.com/kufu/tamatebako/commit/fbf68978c6b2590cf031cdea6badb9edf7faab96))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* require-i18n-textで特定の文字列の場合、修正対象にしないように調整 ([#924](https://github.com/kufu/tamatebako/issues/924)) ([ddfb24b](https://github.com/kufu/tamatebako/commit/ddfb24b82a784312eb4750e87fbcd7c1374d6fcf))
|
|
27
|
+
|
|
5
28
|
## [2.5.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v2.4.0...eslint-plugin-smarthr-v2.5.0) (2025-12-04)
|
|
6
29
|
|
|
7
30
|
|
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
- [a11y-prohibit-input-placeholder](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-prohibit-input-placeholder)
|
|
15
15
|
- [a11y-prohibit-sectioning-content-in-form](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-prohibit-sectioning-content-in-form)
|
|
16
16
|
- [a11y-prohibit-useless-sectioning-fragment](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-prohibit-useless-sectioning-fragment)
|
|
17
|
-
- [a11y-required-layout-as-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-required-layout-as-attribute)
|
|
18
17
|
- [a11y-trigger-has-button](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-trigger-has-button)
|
|
19
18
|
- [best-practice-for-async-current-target](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-async-current-target)
|
|
20
19
|
- [best-practice-for-button-element](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-button-element)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"json5": "^2.2.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"typescript-eslint": "^8.
|
|
29
|
+
"typescript-eslint": "^8.48.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"eslint": "^9"
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"eslintplugin",
|
|
38
38
|
"smarthr"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "beddf6927401452bb9ac84ab7de9bb3fe0d85461"
|
|
41
41
|
}
|
|
@@ -6,8 +6,10 @@ const OPTION = (() => {
|
|
|
6
6
|
const file = `${process.cwd()}/package.json`
|
|
7
7
|
|
|
8
8
|
if (fs.existsSync(file)) {
|
|
9
|
+
const packageConfig = JSON5.parse(fs.readFileSync(file)).dependencies
|
|
10
|
+
|
|
9
11
|
return {
|
|
10
|
-
react_hook_form: Object.keys(
|
|
12
|
+
react_hook_form: packageConfig ? Object.keys(packageConfig).includes('react-hook-form') : false,
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
<SubText />
|
|
38
38
|
</Stack>
|
|
39
39
|
} />
|
|
40
|
+
|
|
41
|
+
// Checkbox, RadioButtonのchildrenにLayout系コンポーネントを設置する場合、as・forwardedAs属性にspanを指定していないければエラー
|
|
42
|
+
<AnyRadioButton><Cluster><A /><B /></Cluster></AnyRadioButton>
|
|
43
|
+
<RadioButtonPanel><AnyStack><A /><B /></AnyStack></RadioButtonPanel>
|
|
44
|
+
<AnyCheckbox><Sidebar><A /><B /></Sidebar></AnyCheckbox>
|
|
40
45
|
```
|
|
41
46
|
|
|
42
47
|
## ✅ Correct
|
|
@@ -94,4 +99,9 @@
|
|
|
94
99
|
statusLabels={<RequiredLabel />}
|
|
95
100
|
subActionArea={<HelpLink />}
|
|
96
101
|
/>
|
|
102
|
+
|
|
103
|
+
// Checkbox, RadioButtonのchildrenにLayout系コンポーネントを設置する場合、as・forwardedAs属性にspanを指定する
|
|
104
|
+
<AnyRadioButton><Cluster as="span"><A /><B /></Cluster></AnyRadioButton>
|
|
105
|
+
<RadioButtonPanel><AnyStack forwardedAs="span"><A /><B /></AnyStack></RadioButtonPanel>
|
|
106
|
+
<AnyCheckbox><Sidebar as="span"><A /><B /></Sidebar></AnyCheckbox>
|
|
97
107
|
```
|
|
@@ -2,14 +2,17 @@ const MULTI_CHILDREN_REGEX = /(Cluster|Stack)$/
|
|
|
2
2
|
const REGEX_NLSP = /^\s*\n+\s*$/
|
|
3
3
|
const FLEX_END_REGEX = /^(flex-)?end$/
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const LAYOUT_COMPONENT_REGEX_WITHOUT_STACK = /(Center|Cluster|Container|Reel|Sidebar)$/
|
|
6
|
+
const LAYOUT_COMPONENT_REGEX = /(Center|Cluster|Container|Reel|Stack|Sidebar)$/
|
|
7
|
+
const LAYOUT_COMPONENT_ELEMENT_WITHOUT_STACK = `JSXOpeningElement[name.name=${LAYOUT_COMPONENT_REGEX_WITHOUT_STACK}]`
|
|
7
8
|
const HEADING_ELEMENT = 'JSXElement[openingElement.name.name=/Heading$/]'
|
|
8
|
-
const
|
|
9
|
+
const NOT_HAS_AS_SPAN_ATTRIBUTE = ':not(:has(JSXAttribute[name.name=/^(as|forwardedAs)$/][value.value="span"]))'
|
|
10
|
+
const STACK_ELEMENT_NOT_SPAN = `JSXOpeningElement[name.name=/Stack$/]${NOT_HAS_AS_SPAN_ATTRIBUTE}`
|
|
11
|
+
const LAYOUT_ELEMENT_NOT_SPAN = `JSXOpeningElement[name.name=${LAYOUT_COMPONENT_REGEX}]${NOT_HAS_AS_SPAN_ATTRIBUTE}`
|
|
9
12
|
const FORM_CONTROL_LABEL_ATTRIBUTE = 'JSXOpeningElement[name.name=/FormControl$/] JSXAttribute[name.name="label"]'
|
|
10
13
|
const FIELDSET_LEGEND_ATTRIBUTE = 'JSXOpeningElement[name.name=/Fieldset$/] JSXAttribute[name.name="legend"]'
|
|
11
14
|
const ICON_ELEMENT_WITH_TEXT = `JSXOpeningElement[name.name=/Icon$/]:has(JSXAttribute[name.name="text"])`
|
|
12
|
-
const TEXT_ELEMENT_WITH_PREFIX = 'JSXOpeningElement[name.name=/Text$/]:has(JSXAttribute[name.name
|
|
15
|
+
const TEXT_ELEMENT_WITH_PREFIX = 'JSXOpeningElement[name.name=/Text$/]:has(JSXAttribute[name.name=/^(prefixIcon|icon)$/])'
|
|
13
16
|
|
|
14
17
|
const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
|
|
15
18
|
const checkFalsyJSXText = (c) => (
|
|
@@ -51,6 +54,9 @@ const searchChildren = (node) => {
|
|
|
51
54
|
return true
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
const DETAIL_LINK_MESSAGE = `
|
|
58
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`
|
|
59
|
+
|
|
54
60
|
const SCHEMA = []
|
|
55
61
|
|
|
56
62
|
/**
|
|
@@ -91,8 +97,7 @@ module.exports = {
|
|
|
91
97
|
} else if (gapAttr?.value.type === 'JSXExpressionContainer' && gapAttr.value.expression.value === 0) {
|
|
92
98
|
context.report({
|
|
93
99
|
node,
|
|
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
|
|
100
|
+
message: `${nodeName} に "gap={0}" が指定されており、smarthr-ui/${layoutType} の利用方法として誤っている可能性があります。以下の修正方法を検討してください。${DETAIL_LINK_MESSAGE}
|
|
96
101
|
- 方法1: 子要素を一つにまとめられないか検討してください
|
|
97
102
|
- 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
|
|
98
103
|
- 方法2: 子要素のstyleを確認しgap属性を0以外にできないか検討してください
|
|
@@ -116,79 +121,84 @@ module.exports = {
|
|
|
116
121
|
node,
|
|
117
122
|
message:
|
|
118
123
|
(justifyAttr?.value.value === 'center' || alignAttr?.value.value === 'center')
|
|
119
|
-
? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center
|
|
120
|
-
|
|
121
|
-
: `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。
|
|
122
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts
|
|
124
|
+
? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください${DETAIL_LINK_MESSAGE}`
|
|
125
|
+
: `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。${DETAIL_LINK_MESSAGE}
|
|
123
126
|
- styleを確認し、div・spanなど、別要素でマークアップし直すか、${nodeName}を削除してください
|
|
124
127
|
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
|
|
125
128
|
})
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
},
|
|
129
|
-
[`${HEADING_ELEMENT} ${
|
|
130
|
-
const component = node.name.name.match(
|
|
132
|
+
[`${HEADING_ELEMENT} ${LAYOUT_COMPONENT_ELEMENT_WITHOUT_STACK}`]: (node) => {
|
|
133
|
+
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX_WITHOUT_STACK)[1]
|
|
131
134
|
|
|
132
135
|
context.report({
|
|
133
136
|
node,
|
|
134
|
-
message: `Headingの子孫に${component}を置くことはできません。Headingの外で${component}
|
|
135
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`
|
|
137
|
+
message: `Headingの子孫に${component}を置くことはできません。Headingの外で${component}を使用するようにマークアップを修正してください。${DETAIL_LINK_MESSAGE}`
|
|
136
138
|
})
|
|
137
139
|
},
|
|
138
140
|
[`${HEADING_ELEMENT} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
139
141
|
context.report({
|
|
140
142
|
node,
|
|
141
|
-
message: `Headingの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\`
|
|
142
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
|
|
143
|
+
message: `Headingの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
143
144
|
})
|
|
144
145
|
},
|
|
145
146
|
[`${HEADING_ELEMENT} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
|
|
146
147
|
context.report({
|
|
147
148
|
node,
|
|
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`,
|
|
149
|
+
message: `HeadingにIconを設定する場合 <Heading icon={<XxxIcon />}></Heading> のようにicon属性を利用してください${DETAIL_LINK_MESSAGE}`,
|
|
150
150
|
})
|
|
151
151
|
},
|
|
152
|
-
[`${FORM_CONTROL_LABEL_ATTRIBUTE} ${
|
|
152
|
+
[`${FORM_CONTROL_LABEL_ATTRIBUTE} ${LAYOUT_COMPONENT_ELEMENT_WITHOUT_STACK}`]: (node) => {
|
|
153
153
|
context.report({
|
|
154
154
|
node,
|
|
155
|
-
message: `FormControlのlabel属性に${node.name.name.match(
|
|
156
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
|
|
155
|
+
message: `FormControlのlabel属性に${node.name.name.match(LAYOUT_COMPONENT_REGEX_WITHOUT_STACK)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。${DETAIL_LINK_MESSAGE}`,
|
|
157
156
|
})
|
|
158
157
|
},
|
|
159
158
|
[`${FORM_CONTROL_LABEL_ATTRIBUTE} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
160
159
|
context.report({
|
|
161
160
|
node,
|
|
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`,
|
|
161
|
+
message: `FormControlのlabel属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
164
162
|
})
|
|
165
163
|
},
|
|
166
164
|
[`${FORM_CONTROL_LABEL_ATTRIBUTE} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
|
|
167
165
|
context.report({
|
|
168
166
|
node,
|
|
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`,
|
|
167
|
+
message: `FormControlのlabel属性にアイコンを設定する場合 <FormControl label={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlabel.icon属性を利用してください${DETAIL_LINK_MESSAGE}`,
|
|
171
168
|
})
|
|
172
169
|
},
|
|
173
|
-
[`${FIELDSET_LEGEND_ATTRIBUTE} ${
|
|
170
|
+
[`${FIELDSET_LEGEND_ATTRIBUTE} ${LAYOUT_COMPONENT_ELEMENT_WITHOUT_STACK}`]: (node) => {
|
|
174
171
|
context.report({
|
|
175
172
|
node,
|
|
176
|
-
message: `Fieldsetのlegend属性に${node.name.name.match(
|
|
177
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`,
|
|
173
|
+
message: `Fieldsetのlegend属性に${node.name.name.match(LAYOUT_COMPONENT_REGEX_WITHOUT_STACK)[1]}を置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。${DETAIL_LINK_MESSAGE}`,
|
|
178
174
|
})
|
|
179
175
|
},
|
|
180
176
|
[`${FIELDSET_LEGEND_ATTRIBUTE} ${STACK_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
181
177
|
context.report({
|
|
182
178
|
node,
|
|
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`,
|
|
179
|
+
message: `Fieldsetのlegend属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
185
180
|
})
|
|
186
181
|
},
|
|
187
182
|
[`${FIELDSET_LEGEND_ATTRIBUTE} :matches(${ICON_ELEMENT_WITH_TEXT},${TEXT_ELEMENT_WITH_PREFIX})`]: (node) => {
|
|
188
183
|
context.report({
|
|
189
184
|
node,
|
|
190
|
-
message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon
|
|
191
|
-
|
|
185
|
+
message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください${DETAIL_LINK_MESSAGE}`,
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
[`JSXElement:has(>JSXOpeningElement[name.name=/RadioButton(Panel)?$/]) ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
189
|
+
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
190
|
+
|
|
191
|
+
context.report({
|
|
192
|
+
node,
|
|
193
|
+
message: `RadioButton, RadioButtonPanelの子孫に${component}を置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
[`JSXElement:has(>JSXOpeningElement[name.name=/Checkbox?$/]) ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
197
|
+
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
198
|
+
|
|
199
|
+
context.report({
|
|
200
|
+
node,
|
|
201
|
+
message: `Checkboxの子孫に${component}を置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
192
202
|
})
|
|
193
203
|
},
|
|
194
204
|
}
|
|
@@ -35,6 +35,9 @@ const STRING_LITERAL_CONDITION =
|
|
|
35
35
|
const generateAttributeSelector = (attributes) =>
|
|
36
36
|
`JSXAttribute[name.name=/^(${attributes.join('|')})$/]${STRING_LITERAL_CONDITION}`
|
|
37
37
|
|
|
38
|
+
const REGEX_IGNORE_TEXT = /^ *(\.|\+|\-|\*|\/|[0-9]+) *$/
|
|
39
|
+
const checkIgnoreText = (text) => !REGEX_IGNORE_TEXT.test(text)
|
|
40
|
+
|
|
38
41
|
/**
|
|
39
42
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
40
43
|
*/
|
|
@@ -44,49 +47,52 @@ module.exports = {
|
|
|
44
47
|
schema: SCHEMA,
|
|
45
48
|
},
|
|
46
49
|
create(context) {
|
|
47
|
-
const
|
|
48
|
-
const elementsObj = options.elements || {}
|
|
49
|
-
|
|
50
|
+
const elementsObj = (context.options[0] || {}).elements || {}
|
|
50
51
|
// ユーザーが'*'を設定していない場合のみデフォルトを適用
|
|
51
52
|
const wildcardAttributes = elementsObj['*'] || DEFAULT_WILDCARD_ATTRIBUTES
|
|
52
53
|
const specificElements = Object.keys(elementsObj).filter((k) => k !== '*')
|
|
53
54
|
const handlers = {}
|
|
54
55
|
|
|
55
56
|
const reportAttributeError = (node) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
if (checkIgnoreText(node.value.value)) {
|
|
58
|
+
context.report({
|
|
59
|
+
node,
|
|
60
|
+
message: `${node.parent.name.name}の${node.name.name}属性に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください
|
|
59
61
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-i18n-text`,
|
|
60
|
-
|
|
62
|
+
})
|
|
63
|
+
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
// 個別要素の設定
|
|
64
67
|
for (const elementName of specificElements) {
|
|
65
68
|
const attributes = elementsObj[elementName]
|
|
66
|
-
if (attributes.length === 0) continue
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
if (attributes.length !== 0) {
|
|
71
|
+
handlers[`JSXOpeningElement[name.name="${elementName}"] > ${generateAttributeSelector(attributes)}`] = reportAttributeError
|
|
72
|
+
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// ワイルドカード設定
|
|
72
76
|
if (wildcardAttributes && wildcardAttributes.length > 0) {
|
|
73
77
|
const attributeSelector = generateAttributeSelector(wildcardAttributes)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
|
|
79
|
+
handlers[
|
|
80
|
+
specificElements.length > 0
|
|
81
|
+
// 個別設定要素を除外
|
|
82
|
+
? `JSXOpeningElement:not([name.name=/^(${specificElements.join('|')})$/]) > ${attributeSelector}`
|
|
83
|
+
: attributeSelector
|
|
84
|
+
] = reportAttributeError
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// 子要素の文字列リテラルチェック(空白のみのテキストは除外)
|
|
84
88
|
handlers['JSXText[value=/\\S/]'] = (node) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
if (checkIgnoreText(node.value)) {
|
|
90
|
+
context.report({
|
|
91
|
+
node,
|
|
92
|
+
message: `子要素に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください
|
|
88
93
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/require-i18n-text`,
|
|
89
|
-
|
|
94
|
+
})
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
return handlers
|
|
@@ -10,8 +10,10 @@ const ruleTester = new RuleTester({
|
|
|
10
10
|
},
|
|
11
11
|
},
|
|
12
12
|
})
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
const DETAIL_LINK_MESSAGE = `
|
|
15
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts`
|
|
16
|
+
const errorMessage = (type, name) => `${name}には子要素が一つしか無いため、${type}でマークアップする意味がありません。${DETAIL_LINK_MESSAGE}
|
|
15
17
|
- styleを確認し、div・spanなど、別要素でマークアップし直すか、${name}を削除してください
|
|
16
18
|
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
|
|
17
19
|
|
|
@@ -65,6 +67,9 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
65
67
|
{ code: `<AnyFormControl label={{ text: 'hoge', icon: <AnyIcon /> }} />` },
|
|
66
68
|
{ code: `<AnyFieldset legend={{ text: <AnyStack as="span"><span>a</span><span>b</span></AnyStack> }} />` },
|
|
67
69
|
{ code: `<Fieldset legend={{ text: 'hoge', icon: <AnyIcon /> }} />` },
|
|
70
|
+
{ code: `<AnyRadioButton><Cluster as="span"><A /><B /></Cluster></AnyRadioButton>` },
|
|
71
|
+
{ code: `<RadioButtonPanel><AnyStack forwardedAs="span"><A /><B /></AnyStack></RadioButtonPanel>` },
|
|
72
|
+
{ code: `<AnyCheckbox><Sidebar as="span"><A /><B /></Sidebar></AnyCheckbox>` },
|
|
68
73
|
],
|
|
69
74
|
invalid: [
|
|
70
75
|
{ code: `<Stack><Hoge /></Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
|
|
@@ -89,8 +94,7 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
89
94
|
{ code: `<AnyCluster>{a ? a.b.hoge(action) : <Hoge />}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
|
|
90
95
|
{ code: `<AnyCluster>{a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
|
|
91
96
|
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
|
|
92
|
-
{ code: `<HogeStack gap={0}>{a}{b}</HogeStack>`, errors: [ { message: `HogeStack に "gap={0}" が指定されており、smarthr-ui/Stack
|
|
93
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts
|
|
97
|
+
{ code: `<HogeStack gap={0}>{a}{b}</HogeStack>`, errors: [ { message: `HogeStack に "gap={0}" が指定されており、smarthr-ui/Stack の利用方法として誤っている可能性があります。以下の修正方法を検討してください。${DETAIL_LINK_MESSAGE}
|
|
94
98
|
- 方法1: 子要素を一つにまとめられないか検討してください
|
|
95
99
|
- 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
|
|
96
100
|
- 方法2: 子要素のstyleを確認しgap属性を0以外にできないか検討してください
|
|
@@ -98,30 +102,21 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
98
102
|
- 方法3: 別要素でマークアップし直すか、HogeStackを削除してください
|
|
99
103
|
- 親要素に smarthr-ui/Cluster, smarthr-ui/Stack などが存在している場合、div・spanなどで1要素にまとめる必要がある場合があります
|
|
100
104
|
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください` } ] },
|
|
101
|
-
{ code: `<Heading><Cluster><Hoge /><Fuga /></Cluster></Heading>`, errors: [ { message: `Headingの子孫にClusterを置くことはできません。Headingの外でCluster
|
|
102
|
-
|
|
103
|
-
{ code: `<
|
|
104
|
-
|
|
105
|
-
{ code: `<
|
|
106
|
-
|
|
107
|
-
{ code: `<
|
|
108
|
-
|
|
109
|
-
{ code: `<
|
|
110
|
-
|
|
111
|
-
{ code: `<
|
|
112
|
-
|
|
113
|
-
{ code: `<
|
|
114
|
-
|
|
115
|
-
{ code: `<
|
|
116
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts` } ] },
|
|
117
|
-
{ code: `<Fieldset legend={<Cluster><Hoge /><Fuga /></Cluster>} />`, errors: [ { message: `Fieldsetのlegend属性にClusterを置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。
|
|
118
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts` } ] },
|
|
119
|
-
{ code: `<AnyFieldset legend={{ text: <AnyStack><Hoge /><Fuga /></AnyStack> }} />`, errors: [ { message: `Fieldsetのlegend属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください
|
|
120
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts` } ] },
|
|
121
|
-
{ code: `<Fieldset legend={<AnyIcon text="hoge" />} />`, errors: [ { message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください
|
|
122
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts` } ] },
|
|
123
|
-
{ code: `<AnyFieldset legend={{ text: <Text prefixIcon={<SomeIcon />}>hoge</Text>}} />`, errors: [ { message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください
|
|
124
|
-
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts` } ] },
|
|
105
|
+
{ code: `<Heading><Cluster><Hoge /><Fuga /></Cluster></Heading>`, errors: [ { message: `Headingの子孫にClusterを置くことはできません。Headingの外でClusterを使用するようにマークアップを修正してください。${DETAIL_LINK_MESSAGE}` } ] },
|
|
106
|
+
{ code: `<AnyHeading><AnyStack><Hoge /><Fuga /></AnyStack></AnyHeading>`, errors: [ { message: `Headingの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
107
|
+
{ code: `<Heading><AnyIcon text="hoge" /></Heading>`, errors: [ { message: `HeadingにIconを設定する場合 <Heading icon={<XxxIcon />}></Heading> のようにicon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
108
|
+
{ code: `<AnyHeading><Text prefixIcon={<SomeIcon />}>hoge</Text></AnyHeading>`, errors: [ { message: `HeadingにIconを設定する場合 <Heading icon={<XxxIcon />}></Heading> のようにicon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
109
|
+
{ code: `<AnyFormControl label={{ text: <Cluster><Hoge /><Fuga /></Cluster> }} />`, errors: [ { message: `FormControlのlabel属性にClusterを置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。${DETAIL_LINK_MESSAGE}` } ] },
|
|
110
|
+
{ code: `<FormControl label={<AnyStack><Hoge /><Fuga /></AnyStack>} />`, errors: [ { message: `FormControlのlabel属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
111
|
+
{ code: `<AnyFormControl label={{ text: <AnyIcon text="hoge" /> }} />`, errors: [ { message: `FormControlのlabel属性にアイコンを設定する場合 <FormControl label={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlabel.icon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
112
|
+
{ code: `<FormControl label={{ text: <Text prefixIcon={<SomeIcon /> }>hoge</Text>}} />`, errors: [ { message: `FormControlのlabel属性にアイコンを設定する場合 <FormControl label={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlabel.icon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
113
|
+
{ code: `<Fieldset legend={<Cluster><Hoge /><Fuga /></Cluster>} />`, errors: [ { message: `Fieldsetのlegend属性にClusterを置くことはできません。ラベル用テキスト以外をstatusLabels、subActionArea、もしくはlabel属性のObjectとして '{ text: テキスト, icon: <XxxIcon /> }'に置き換えてください。${DETAIL_LINK_MESSAGE}` } ] },
|
|
114
|
+
{ code: `<AnyFieldset legend={{ text: <AnyStack><Hoge /><Fuga /></AnyStack> }} />`, errors: [ { message: `Fieldsetのlegend属性にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
115
|
+
{ code: `<Fieldset legend={<AnyIcon text="hoge" />} />`, errors: [ { message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
116
|
+
{ code: `<AnyFieldset legend={{ text: <Text prefixIcon={<SomeIcon />}>hoge</Text>}} />`, errors: [ { message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
117
|
+
{ code: `<AnyRadioButton><Cluster><A /><B /></Cluster></AnyRadioButton>`, errors: [ { message: `RadioButton, RadioButtonPanelの子孫にClusterを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
118
|
+
{ code: `<RadioButtonPanel><AnyStack><A /><B /></AnyStack></RadioButtonPanel>`, errors: [ { message: `RadioButton, RadioButtonPanelの子孫にStackを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
119
|
+
{ code: `<AnyCheckbox><Sidebar><A /><B /></Sidebar></AnyCheckbox>`, errors: [ { message: `Checkboxの子孫にSidebarを置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}` } ] },
|
|
125
120
|
]
|
|
126
121
|
})
|
|
127
122
|
|
|
@@ -54,6 +54,14 @@ ruleTester.run('require-i18n-text', rule, {
|
|
|
54
54
|
{ code: `<img src="image.png" />` },
|
|
55
55
|
{ code: `<div data-testid="test" />` },
|
|
56
56
|
|
|
57
|
+
// 数値のみ、.と演算記号のみの場合は許容
|
|
58
|
+
{ code: `<Any aria-label="1234" />` },
|
|
59
|
+
{ code: `<div>.</div>` },
|
|
60
|
+
{ code: `<a> +</a>` },
|
|
61
|
+
{ code: `<img alt="-" />` },
|
|
62
|
+
{ code: `<i>*</i>` },
|
|
63
|
+
{ code: `<i>/</i>` },
|
|
64
|
+
|
|
57
65
|
// ワイルドカード - 空配列で除外
|
|
58
66
|
{
|
|
59
67
|
code: `<Icon label="Icon text" />`,
|
|
@@ -98,6 +106,14 @@ ruleTester.run('require-i18n-text', rule, {
|
|
|
98
106
|
errors: [{ message: attributeError('button', 'title') }],
|
|
99
107
|
},
|
|
100
108
|
|
|
109
|
+
// 数値、.と演算記号の場合でも他の文字列が含まれていればエラー
|
|
110
|
+
{ code: `<Any aria-label="1234 あ" />`, errors: [{ message: attributeError('Any', 'aria-label') }] },
|
|
111
|
+
{ code: `<div>a.</div>`, errors: [{ message: childTextError }] },
|
|
112
|
+
{ code: `<a> + b</a>`, errors: [{ message: childTextError }] },
|
|
113
|
+
{ code: `<img alt="-zod" />`, errors: [{ message: attributeError('img', 'alt') }] },
|
|
114
|
+
{ code: `<i>*1</i>`, errors: [{ message: childTextError }] },
|
|
115
|
+
{ code: `<i>a/</i>`, errors: [{ message: childTextError }] },
|
|
116
|
+
|
|
101
117
|
// 属性エラー: カスタムオプション
|
|
102
118
|
{
|
|
103
119
|
code: `<img alt="Profile picture" />`,
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# smarthr/a11y-required-layout-as-attribute
|
|
2
|
-
|
|
3
|
-
- smarthr-ui/Layoutに属するコンポーネントはデフォルトではdiv要素を出力します
|
|
4
|
-
- そのため他の一部コンポーネントとの組み合わせによってはinvalidなマークアップになる場合が起こり得ます
|
|
5
|
-
- 例: FormControlのtitle属性内でClusterを使うと `label > div` の構造になるためinvalid
|
|
6
|
-
- 対象コンポーネントの使用方法をチェックし、適切なマークアップになるよう、as・forwardedAs属性の利用を促します
|
|
7
|
-
|
|
8
|
-
## rules
|
|
9
|
-
|
|
10
|
-
```js
|
|
11
|
-
{
|
|
12
|
-
rules: {
|
|
13
|
-
'smarthr/a11y-required-layout-as-attribute': [
|
|
14
|
-
'error', // 'warn', 'off'
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## ❌ Incorrect
|
|
21
|
-
|
|
22
|
-
```jsx
|
|
23
|
-
<Heading>
|
|
24
|
-
<Cluster>any</Cluster>
|
|
25
|
-
</Heading>
|
|
26
|
-
|
|
27
|
-
<HogeFormControl title={
|
|
28
|
-
<FugaCluster>any</FugaCluster>
|
|
29
|
-
} />
|
|
30
|
-
|
|
31
|
-
<StyledFieldset title={
|
|
32
|
-
<Cluster>any</Cluster>
|
|
33
|
-
}>
|
|
34
|
-
// any
|
|
35
|
-
</StyledFieldset>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## ✅ Correct
|
|
39
|
-
|
|
40
|
-
```jsx
|
|
41
|
-
<Heading>
|
|
42
|
-
<Cluster as="span">any</Cluster>
|
|
43
|
-
</Heading>
|
|
44
|
-
|
|
45
|
-
<HogeFormControl title={
|
|
46
|
-
<FugaCluster forwardedAs="span">any</FugaCluster>
|
|
47
|
-
} />
|
|
48
|
-
|
|
49
|
-
<StyledFieldset title={
|
|
50
|
-
<Cluster as="strong">any</Cluster>
|
|
51
|
-
}>
|
|
52
|
-
// any
|
|
53
|
-
</StyledFieldset>
|
|
54
|
-
```
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const layoutRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack|Base(Column)?)$/
|
|
2
|
-
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
3
|
-
const asRegex = /^(as|forwardedAs)$/
|
|
4
|
-
const formControlRegex = /(FormControl|Fieldset)$/
|
|
5
|
-
|
|
6
|
-
const findAsAttr = (a) => a.name?.name.match(asRegex)
|
|
7
|
-
|
|
8
|
-
const searchBubbleUp = (node) => {
|
|
9
|
-
switch (node.type) {
|
|
10
|
-
case 'Program':
|
|
11
|
-
// rootまで検索した場合は確定でエラーにする
|
|
12
|
-
return null
|
|
13
|
-
case 'JSXElement': {
|
|
14
|
-
const name = node.openingElement.name.name || ''
|
|
15
|
-
|
|
16
|
-
if (headingRegex.test(name)) {
|
|
17
|
-
return name
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
break
|
|
21
|
-
}
|
|
22
|
-
case 'JSXAttribute': {
|
|
23
|
-
const name = node.name.name || ''
|
|
24
|
-
|
|
25
|
-
if (name === 'title' && formControlRegex.test(node.parent.name.name)) {
|
|
26
|
-
return `${node.parent.name.name}のtitle属性`
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return searchBubbleUp(node.parent)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
36
|
-
*/
|
|
37
|
-
module.exports = {
|
|
38
|
-
meta: {
|
|
39
|
-
type: 'problem',
|
|
40
|
-
schema: [],
|
|
41
|
-
},
|
|
42
|
-
create(context) {
|
|
43
|
-
return {
|
|
44
|
-
JSXOpeningElement: (node) => {
|
|
45
|
-
const name = node.name.name || ''
|
|
46
|
-
|
|
47
|
-
if (layoutRegex.test(name) && !node.attributes.some(findAsAttr)) {
|
|
48
|
-
const parentName = searchBubbleUp(node.parent.parent)
|
|
49
|
-
|
|
50
|
-
if (parentName) {
|
|
51
|
-
context.report({
|
|
52
|
-
node,
|
|
53
|
-
message: `${name}は${parentName}内に存在するため、as、もしくはforwardedAs属性を指定し、div以外の要素にする必要があります
|
|
54
|
-
- smarthr-ui/Layoutに属するコンポーネントはデフォルトでdiv要素を出力するため${parentName}内で利用すると、マークアップの仕様に違反します
|
|
55
|
-
- ほぼすべての場合、spanを指定することで適切なマークアップに変更出来ます
|
|
56
|
-
- span以外を指定したい場合、記述コンテンツに属する要素かどうかを確認してください (https://developer.mozilla.org/ja/docs/Web/HTML/Content_categories#%E8%A8%98%E8%BF%B0%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84)`,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports.schema = []
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const rule = require('../rules/a11y-required-layout-as-attribute')
|
|
2
|
-
const RuleTester = require('eslint').RuleTester
|
|
3
|
-
|
|
4
|
-
const ruleTester = new RuleTester({
|
|
5
|
-
languageOptions: {
|
|
6
|
-
parserOptions: {
|
|
7
|
-
ecmaFeatures: {
|
|
8
|
-
jsx: true,
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const generateErrorText = (parentName, name) => `${name}は${parentName}内に存在するため、as、もしくはforwardedAs属性を指定し、div以外の要素にする必要があります
|
|
15
|
-
- smarthr-ui/Layoutに属するコンポーネントはデフォルトでdiv要素を出力するため${parentName}内で利用すると、マークアップの仕様に違反します
|
|
16
|
-
- ほぼすべての場合、spanを指定することで適切なマークアップに変更出来ます
|
|
17
|
-
- span以外を指定したい場合、記述コンテンツに属する要素かどうかを確認してください (https://developer.mozilla.org/ja/docs/Web/HTML/Content_categories#%E8%A8%98%E8%BF%B0%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84)`
|
|
18
|
-
|
|
19
|
-
ruleTester.run('a11y-anchor-has-href-attribute', rule, {
|
|
20
|
-
valid: [
|
|
21
|
-
{ code: `<h1><Cluster as="span">ほげ</Cluster></h1>` },
|
|
22
|
-
{ code: `<Heading><Cluster as="strong" /></Heading>` },
|
|
23
|
-
{ code: `<StyledHeading><AnyCluster forwardedAs="span" /></StyledHeading>` },
|
|
24
|
-
{ code: `<FormControl title={<Cluster as="span" />} />` },
|
|
25
|
-
{ code: `<StyledFieldset title={<AnyCluster forwardedAs="strong" />} />` },
|
|
26
|
-
],
|
|
27
|
-
invalid: [
|
|
28
|
-
{ code: `<h1><Cluster>ほげ</Cluster></h1>`, errors: [{ message: generateErrorText('h1', 'Cluster') }] },
|
|
29
|
-
{ code: `<Heading><Cluster /></Heading>`, errors: [{ message: generateErrorText('Heading', 'Cluster') }] },
|
|
30
|
-
{ code: `<StyledHeading><AnyCluster /></StyledHeading>`, errors: [{ message: generateErrorText('StyledHeading', 'AnyCluster') }] },
|
|
31
|
-
{ code: `<FormControl title={<Cluster />} />`, errors: [{ message: generateErrorText('FormControlのtitle属性', 'Cluster') }] },
|
|
32
|
-
{ code: `<StyledFieldset title={<AnyCluster />} />`, errors: [{ message: generateErrorText('StyledFieldsetのtitle属性', 'AnyCluster') }] },
|
|
33
|
-
]
|
|
34
|
-
})
|