eslint-plugin-smarthr 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/libs/common_domain.js +5 -8
- package/libs/util.js +4 -0
- package/package.json +4 -4
- package/rules/a11y-anchor-has-href-attribute/index.js +31 -20
- package/rules/a11y-clickable-element-has-text/index.js +4 -25
- package/rules/a11y-delegate-element-has-role-presentation/index.js +34 -55
- package/rules/a11y-form-control-in-form/index.js +5 -31
- package/rules/a11y-heading-in-sectioning-content/index.js +7 -63
- package/rules/a11y-image-has-alt-attribute/index.js +0 -16
- package/rules/a11y-input-has-name-attribute/index.js +15 -33
- package/rules/a11y-input-in-form-control/index.js +28 -74
- package/rules/a11y-numbered-text-within-ol/index.js +2 -11
- package/rules/a11y-prohibit-input-maxlength-attribute/index.js +8 -20
- package/rules/a11y-prohibit-input-placeholder/index.js +2 -14
- package/rules/a11y-prohibit-sectioning-content-in-form/index.js +5 -37
- package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +5 -23
- package/rules/a11y-required-layout-as-attribute/index.js +0 -25
- package/rules/a11y-trigger-has-button/index.js +10 -18
- package/rules/best-practice-for-data-test-attribute/index.js +3 -6
- package/rules/best-practice-for-layouts/index.js +2 -16
- package/rules/best-practice-for-remote-trigger-dialog/index.js +3 -11
- package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +38 -30
- package/rules/best-practice-for-tailwind-variants/index.js +10 -18
- package/rules/component-name/README.md +44 -0
- package/rules/component-name/index.js +139 -0
- package/rules/design-system-guideline-prohibit-double-icons/index.js +1 -11
- package/rules/format-import-path/index.js +14 -6
- package/rules/format-translate-component/index.js +3 -1
- package/rules/no-import-other-domain/index.js +8 -8
- package/rules/prohibit-file-name/index.js +1 -1
- package/rules/prohibit-import/index.js +21 -23
- package/rules/prohibit-path-within-template-literal/index.js +1 -1
- package/rules/require-barrel-import/index.js +8 -11
- package/rules/require-declaration/index.js +5 -3
- package/rules/require-export/index.js +34 -30
- package/rules/require-import/index.js +10 -10
- package/rules/trim-props/index.js +9 -8
- package/test/a11y-anchor-has-href-attribute.js +0 -29
- package/test/a11y-clickable-element-has-text.js +0 -66
- package/test/{a11y-delegate-element-has-role-presantation.js → a11y-delegate-element-has-role-presentation.js} +3 -2
- package/test/a11y-form-control-in-form.js +1 -1
- package/test/a11y-heading-in-sectioning-content.js +0 -82
- package/test/a11y-image-has-alt-attribute.js +0 -45
- package/test/a11y-input-has-name-attribute.js +0 -44
- package/test/a11y-input-in-form-control.js +9 -33
- package/test/a11y-prohhibit-input-placeholder.js +0 -45
- package/test/a11y-trigger-has-button.js +0 -42
- package/test/best-practice-for-remote-trigger-dialog.js +0 -6
- package/test/component-name.js +247 -0
- package/test/prohibit-import.js +13 -13
|
@@ -2,8 +2,6 @@ const fs = require('fs')
|
|
|
2
2
|
|
|
3
3
|
const JSON5 = require('json5')
|
|
4
4
|
|
|
5
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
6
|
-
|
|
7
5
|
const OPTION = (() => {
|
|
8
6
|
const file = `${process.cwd()}/package.json`
|
|
9
7
|
|
|
@@ -19,20 +17,7 @@ const OPTION = (() => {
|
|
|
19
17
|
}
|
|
20
18
|
})()
|
|
21
19
|
|
|
22
|
-
const
|
|
23
|
-
'(i|I)nput$': 'Input$',
|
|
24
|
-
'(t|T)extarea$': 'Textarea$',
|
|
25
|
-
'(s|S)elect$': 'Select$',
|
|
26
|
-
'InputFile$': 'InputFile$',
|
|
27
|
-
'RadioButton$': 'RadioButton$',
|
|
28
|
-
'RadioButtonPanel$': 'RadioButtonPanel$',
|
|
29
|
-
'Check(b|B)ox$': 'Checkbox$',
|
|
30
|
-
'Combo(b|B)ox$': 'Combobox$',
|
|
31
|
-
'(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
|
|
32
|
-
TimePicker$: 'TimePicker$',
|
|
33
|
-
DropZone$: 'DropZone$',
|
|
34
|
-
}
|
|
35
|
-
const TARGET_TAG_NAME_REGEX = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
|
|
20
|
+
const TARGET_TAG_NAME_REGEX = /((I|^i)nput|(T|^t)extarea|(S|^s)elect|InputFile|RadioButton(Panel)?|(Check|Combo)(B|b)ox|(Date|Wareki|Time)Picker|DropZone)$/
|
|
36
21
|
const INPUT_NAME_REGEX = /^[a-zA-Z0-9_\[\]]+$/
|
|
37
22
|
const INPUT_TAG_REGEX = /(i|I)nput$/
|
|
38
23
|
const RADIO_BUTTON_REGEX = /RadioButton(Panel)?$/
|
|
@@ -69,12 +54,11 @@ module.exports = {
|
|
|
69
54
|
const checkType = option.checkType || 'always'
|
|
70
55
|
|
|
71
56
|
return {
|
|
72
|
-
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
73
57
|
JSXOpeningElement: (node) => {
|
|
74
58
|
const { name, attributes } = node
|
|
75
59
|
const nodeName = name.name || ''
|
|
76
60
|
|
|
77
|
-
if (
|
|
61
|
+
if (TARGET_TAG_NAME_REGEX.test(nodeName)) {
|
|
78
62
|
let nameAttr = null
|
|
79
63
|
let hasSpreadAttr = false
|
|
80
64
|
let hasReactHookFormRegisterSpreadAttr = false
|
|
@@ -103,27 +87,25 @@ module.exports = {
|
|
|
103
87
|
}
|
|
104
88
|
})
|
|
105
89
|
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
!(OPTION.react_hook_form && hasReactHookFormRegisterSpreadAttr) &&
|
|
109
|
-
(attributes.length === 0 || checkType !== 'allow-spread-attributes' || !hasSpreadAttr)
|
|
110
|
-
) {
|
|
111
|
-
const isRadio = nodeName.match(RADIO_BUTTON_REGEX) || (nodeName.match(INPUT_TAG_REGEX) && hasRadioInput)
|
|
112
|
-
|
|
113
|
-
context.report({
|
|
114
|
-
node,
|
|
115
|
-
message: `${nodeName} ${isRadio ? MESSAGE_UNDEFINED_FOR_RADIO : MESSAGE_UNDEFINED_FOR_NOT_RADIO}`,
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
const nameValue = nameAttr.value?.value || ''
|
|
90
|
+
if (nameAttr) {
|
|
91
|
+
const nameValue = nameAttr.value?.value
|
|
120
92
|
|
|
121
|
-
if (nameValue && !
|
|
93
|
+
if (nameValue && !INPUT_NAME_REGEX.test(nameValue)) {
|
|
122
94
|
context.report({
|
|
123
95
|
node,
|
|
124
96
|
message: `${nodeName} のname属性の値(${nameValue})${MESSAGE_NAME_FORMAT_SUFFIX}`,
|
|
125
97
|
})
|
|
126
98
|
}
|
|
99
|
+
} else if (
|
|
100
|
+
!(OPTION.react_hook_form && hasReactHookFormRegisterSpreadAttr) &&
|
|
101
|
+
(attributes.length === 0 || checkType !== 'allow-spread-attributes' || !hasSpreadAttr)
|
|
102
|
+
) {
|
|
103
|
+
const isRadio = RADIO_BUTTON_REGEX.test(nodeName) || INPUT_TAG_REGEX.test(nodeName) && hasRadioInput
|
|
104
|
+
|
|
105
|
+
context.report({
|
|
106
|
+
node,
|
|
107
|
+
message: `${nodeName} ${isRadio ? MESSAGE_UNDEFINED_FOR_RADIO : MESSAGE_UNDEFINED_FOR_NOT_RADIO}`,
|
|
108
|
+
})
|
|
127
109
|
}
|
|
128
110
|
}
|
|
129
111
|
},
|
|
@@ -1,57 +1,12 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
'RadioButton$': '(RadioButton)$',
|
|
5
|
-
'RadioButtons$': '(RadioButtons)$',
|
|
6
|
-
'RadioButtonPanel$': '(RadioButtonPanel)$',
|
|
7
|
-
'RadioButtonPanels$': '(RadioButtonPanels)$',
|
|
8
|
-
'Check(b|B)ox$': '(Checkbox)$',
|
|
9
|
-
'Check(b|B)ox(e)?s$': '(Checkboxes)$',
|
|
10
|
-
}
|
|
11
|
-
const EXPECTED_INPUT_NAMES = {
|
|
12
|
-
'(I|^i)nput$': '(Input)$',
|
|
13
|
-
'SearchInput$': '(SearchInput)$',
|
|
14
|
-
'(T|^t)extarea$': '(Textarea)$',
|
|
15
|
-
'(S|^s)elect$': '(Select)$',
|
|
16
|
-
'InputFile$': '(InputFile)$',
|
|
17
|
-
'Combo(b|B)ox$': '(Combobox)$',
|
|
18
|
-
'(Date|Wareki)Picker$': '((Date|Wareki)Picker)$',
|
|
19
|
-
'TimePicker$': '(TimePicker)$',
|
|
20
|
-
...EXPECTED_LABELED_INPUT_NAMES,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const EXPECTED_FORM_CONTROL_NAMES = {
|
|
24
|
-
'(FormGroup)$': '(FormGroup)$',
|
|
25
|
-
'(FormControl)$': '(FormControl)$',
|
|
26
|
-
'((F|^f)ieldset)$': '(Fieldset)$',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const EXPECTED_NAMES = {
|
|
30
|
-
...EXPECTED_INPUT_NAMES,
|
|
31
|
-
...EXPECTED_FORM_CONTROL_NAMES,
|
|
32
|
-
'(A|^a)rticle$': '(Article)$',
|
|
33
|
-
'(A|^a)side$': '(Aside)$',
|
|
34
|
-
'(N|^n)av$': '(Nav)$',
|
|
35
|
-
'(S|^s)ection$': '(Section)$',
|
|
36
|
-
'Cluster$': '(Cluster)$',
|
|
37
|
-
'Center$': '(Center)$',
|
|
38
|
-
'Reel$': '(Reel)$',
|
|
39
|
-
'Sidebar$': '(Sidebar)$',
|
|
40
|
-
'Stack$': '(Stack)$',
|
|
41
|
-
'(L|^l)abel$': '(Label)$',
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
46
|
-
|
|
47
|
-
const FORM_CONTROL_INPUTS_REGEX = new RegExp(`(${Object.keys(EXPECTED_INPUT_NAMES).join('|')})`)
|
|
48
|
-
const LABELED_INPUTS_REGEX = new RegExp(`(${Object.keys(EXPECTED_LABELED_INPUT_NAMES).join('|')})`)
|
|
1
|
+
const LABELED_INPUTS_REGEX_STR = 'RadioButton(Panel)?(s)?|Check(B|b)ox(es|s)?'
|
|
2
|
+
const LABELED_INPUTS_REGEX = new RegExp(`(${LABELED_INPUTS_REGEX_STR})$`)
|
|
3
|
+
const FORM_CONTROL_INPUTS_REGEX = new RegExp(`(${LABELED_INPUTS_REGEX_STR}|(Search)?(I|^i)nput(File)?|(T|^t)extarea|(S|^s)elect|Combo(B|b)ox|(Date|Wareki|Time)Picker)$`)
|
|
49
4
|
const SEARCH_INPUT_REGEX = /SearchInput$/
|
|
50
5
|
const INPUT_REGEX = /(i|I)nput$/
|
|
51
6
|
const RADIO_BUTTONS_REGEX = /RadioButton(Panel)?(s)?$/
|
|
52
7
|
const CHECKBOX_REGEX = /Check(B|b)ox(s|es)?$/
|
|
53
8
|
const SELECT_REGEX = /(S|s)elect(s)?$/
|
|
54
|
-
const FROM_CONTROLS_REGEX =
|
|
9
|
+
const FROM_CONTROLS_REGEX = /(Form(Control|Group)|(F|^f)ieldset)$/
|
|
55
10
|
const FORM_CONTROL_REGEX = /(Form(Control|Group))$/
|
|
56
11
|
const FIELDSET_REGEX = /Fieldset$/
|
|
57
12
|
const DIALOG_REGEX = /Dialog(WithTrigger)?$/
|
|
@@ -60,11 +15,12 @@ const BARE_SECTIONING_TAG_REGEX = /^(article|aside|nav|section)$/
|
|
|
60
15
|
const LAYOUT_COMPONENT_REGEX = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
|
|
61
16
|
const AS_REGEX = /^(as|forwardedAs)$/
|
|
62
17
|
const SUFFIX_S_REGEX = /s$/
|
|
18
|
+
const az_REGEX = /[a-z]/
|
|
63
19
|
|
|
64
20
|
const IGNORE_INPUT_CHECK_PARENT_TYPE = /^(Program|ExportNamedDeclaration)$/
|
|
65
21
|
|
|
66
22
|
const findRoleGroup = (a) => a.name?.name === 'role' && a.value.value === 'group'
|
|
67
|
-
const findAsSectioning = (a) => a.name?.name
|
|
23
|
+
const findAsSectioning = (a) => AS_REGEX.test(a.name?.name) && BARE_SECTIONING_TAG_REGEX.test(a.value.value)
|
|
68
24
|
const findTitle = (i) => i.key.name === 'title'
|
|
69
25
|
|
|
70
26
|
const SCHEMA = [
|
|
@@ -91,18 +47,17 @@ module.exports = {
|
|
|
91
47
|
const additionalInputComponents = option.additionalInputComponents?.length > 0 ? new RegExp(`(${option.additionalInputComponents.join('|')})`) : null
|
|
92
48
|
const additionalMultiInputComponents = option.additionalMultiInputComponents?.length > 0 ? new RegExp(`(${option.additionalMultiInputComponents.join('|')})`) : null
|
|
93
49
|
|
|
94
|
-
const checkAdditionalInputComponents = (name) => additionalInputComponents &&
|
|
95
|
-
const checkAdditionalMultiInputComponents = (name) => additionalMultiInputComponents &&
|
|
50
|
+
const checkAdditionalInputComponents = (name) => additionalInputComponents && additionalInputComponents.test(name)
|
|
51
|
+
const checkAdditionalMultiInputComponents = (name) => additionalMultiInputComponents && additionalMultiInputComponents.test(name)
|
|
96
52
|
|
|
97
53
|
let formControls = []
|
|
98
54
|
let conditionalformControls = []
|
|
99
55
|
let checkboxFormControls = []
|
|
100
56
|
|
|
101
57
|
return {
|
|
102
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
103
58
|
JSXOpeningElement: (node) => {
|
|
104
59
|
const nodeName = node.name.name || '';
|
|
105
|
-
const isFormControlInput =
|
|
60
|
+
const isFormControlInput = FORM_CONTROL_INPUTS_REGEX.test(nodeName)
|
|
106
61
|
const isAdditionalMultiInput = checkAdditionalMultiInputComponents(nodeName)
|
|
107
62
|
let conditionalExpressions = []
|
|
108
63
|
|
|
@@ -110,11 +65,11 @@ module.exports = {
|
|
|
110
65
|
let isInMap = false
|
|
111
66
|
|
|
112
67
|
// HINT: 検索ボックスの場合、UIの関係上labelを設定出来ないことが多い & smarthr-ui/SearchInputはa11y対策してあるため無視
|
|
113
|
-
if (
|
|
68
|
+
if (SEARCH_INPUT_REGEX.test(nodeName)) {
|
|
114
69
|
return
|
|
115
70
|
}
|
|
116
71
|
|
|
117
|
-
const isPureInput =
|
|
72
|
+
const isPureInput = INPUT_REGEX.test(nodeName)
|
|
118
73
|
let isPseudoLabel = false
|
|
119
74
|
let isTypeRadio = false
|
|
120
75
|
let isTypeCheck = false
|
|
@@ -154,9 +109,9 @@ module.exports = {
|
|
|
154
109
|
}
|
|
155
110
|
}
|
|
156
111
|
|
|
157
|
-
const isPreMultiple = isAdditionalMultiInput || isFormControlInput &&
|
|
158
|
-
const isRadio = (isPureInput && isTypeRadio) ||
|
|
159
|
-
const isCheckbox = !isRadio && (isPureInput && isTypeCheck ||
|
|
112
|
+
const isPreMultiple = isAdditionalMultiInput || isFormControlInput && SUFFIX_S_REGEX.test(nodeName)
|
|
113
|
+
const isRadio = (isPureInput && isTypeRadio) || RADIO_BUTTONS_REGEX.test(nodeName)
|
|
114
|
+
const isCheckbox = !isRadio && (isPureInput && isTypeCheck || CHECKBOX_REGEX.test(nodeName))
|
|
160
115
|
|
|
161
116
|
const wrapComponentName = isRadio ? 'Fieldset' : 'FormControl'
|
|
162
117
|
const search = (n) => {
|
|
@@ -166,7 +121,7 @@ module.exports = {
|
|
|
166
121
|
const name = openingElement.name.name
|
|
167
122
|
|
|
168
123
|
if (name) {
|
|
169
|
-
if (
|
|
124
|
+
if (FROM_CONTROLS_REGEX.test(name)) {
|
|
170
125
|
const hit = formControls.includes(n)
|
|
171
126
|
let conditionalHit = false
|
|
172
127
|
|
|
@@ -240,7 +195,7 @@ module.exports = {
|
|
|
240
195
|
// HINT: 擬似的にラベルが設定されている場合、無視する
|
|
241
196
|
if (!isCheckbox && !isPseudoLabel) {
|
|
242
197
|
const actualName = isSection ? name : `<${name} ${layoutSectionAttribute.name.name}="${layoutSectionAttribute.value.value}">`
|
|
243
|
-
const isSelect = !isRadio &&
|
|
198
|
+
const isSelect = !isRadio && SELECT_REGEX.test(nodeName)
|
|
244
199
|
|
|
245
200
|
context.report({
|
|
246
201
|
node,
|
|
@@ -266,11 +221,11 @@ module.exports = {
|
|
|
266
221
|
break
|
|
267
222
|
}
|
|
268
223
|
case 'VariableDeclarator': {
|
|
269
|
-
if (n.parent.parent?.type && n.parent.parent.type
|
|
224
|
+
if (n.parent.parent?.type && IGNORE_INPUT_CHECK_PARENT_TYPE.test(n.parent.parent.type)) {
|
|
270
225
|
const name = n.id.name
|
|
271
226
|
|
|
272
227
|
// 入力要素系コンポーネントの拡張なので対象外
|
|
273
|
-
if (
|
|
228
|
+
if (FORM_CONTROL_INPUTS_REGEX.test(name) || checkAdditionalMultiInputComponents(name) || checkAdditionalInputComponents(name)) {
|
|
274
229
|
return
|
|
275
230
|
}
|
|
276
231
|
}
|
|
@@ -278,11 +233,11 @@ module.exports = {
|
|
|
278
233
|
break
|
|
279
234
|
}
|
|
280
235
|
case 'FunctionDeclaration': {
|
|
281
|
-
if (n.parent.type
|
|
236
|
+
if (IGNORE_INPUT_CHECK_PARENT_TYPE.test(n.parent.type)) {
|
|
282
237
|
const name = n.id.name
|
|
283
238
|
|
|
284
239
|
// 入力要素系コンポーネントの拡張なので対象外
|
|
285
|
-
if (
|
|
240
|
+
if (FORM_CONTROL_INPUTS_REGEX.test(name) || checkAdditionalMultiInputComponents(name) || checkAdditionalInputComponents(name)) {
|
|
286
241
|
return
|
|
287
242
|
}
|
|
288
243
|
}
|
|
@@ -291,7 +246,7 @@ module.exports = {
|
|
|
291
246
|
// HINT: smarthr-ui/Checkboxはlabelを単独で持つため、FormControl系でラップをする必要はない
|
|
292
247
|
// HINT: 擬似的にラベルが設定されている場合、無視する
|
|
293
248
|
if (!isCheckbox && !isPseudoLabel) {
|
|
294
|
-
const isSelect = !isRadio &&
|
|
249
|
+
const isSelect = !isRadio && SELECT_REGEX.test(nodeName)
|
|
295
250
|
|
|
296
251
|
context.report({
|
|
297
252
|
node,
|
|
@@ -328,7 +283,7 @@ module.exports = {
|
|
|
328
283
|
|
|
329
284
|
if (!nodeName.match(FORM_CONTROL_REGEX) && isRoleGrouop) {
|
|
330
285
|
const component = formControlMatcher[1]
|
|
331
|
-
const actualComponent = component[0]
|
|
286
|
+
const actualComponent = az_REGEX.test(component[0]) ? component : `smarthr-ui/${component}`
|
|
332
287
|
|
|
333
288
|
const message = `${nodeName}に 'role="group" が設定されています。${actualComponent} をつかってマークアップする場合、'role="group"' は不要です
|
|
334
289
|
- ${nodeName} が ${actualComponent}、もしくはそれを拡張しているコンポーネントではない場合、名称を ${FROM_CONTROLS_REGEX} にマッチしないものに変更してください`
|
|
@@ -346,7 +301,7 @@ module.exports = {
|
|
|
346
301
|
const name = n.openingElement.name.name || ''
|
|
347
302
|
|
|
348
303
|
// Fieldset > Dialog > Fieldset のようにDialogを挟んだFormControl系のネストは許容する(Portalで実際にはネストしていないため)
|
|
349
|
-
if (
|
|
304
|
+
if (DIALOG_REGEX.test(name)) {
|
|
350
305
|
return
|
|
351
306
|
}
|
|
352
307
|
|
|
@@ -354,7 +309,7 @@ module.exports = {
|
|
|
354
309
|
if (matcher) {
|
|
355
310
|
// FormControl > FormControl や FormControl > Fieldset のように複数のFormControl系コンポーネントがネストしてしまっているためエラーにする
|
|
356
311
|
// Fieldset > Fieldset や Fieldset > FormControl のようにFieldsetが親の場合は許容する
|
|
357
|
-
if (
|
|
312
|
+
if (FORM_CONTROL_REGEX.test(name)) {
|
|
358
313
|
context.report({
|
|
359
314
|
node: n,
|
|
360
315
|
message: `${name} が、${nodeName} を子要素として持っており、マークアップとして正しくない状態になっています。以下のいずれかの方法で修正を試みてください。
|
|
@@ -379,7 +334,7 @@ module.exports = {
|
|
|
379
334
|
|
|
380
335
|
searchParent(node.parent.parent)
|
|
381
336
|
|
|
382
|
-
if (!node.selfClosing &&
|
|
337
|
+
if (!node.selfClosing && isRoleGrouop && FORM_CONTROL_REGEX.test(nodeName)) {
|
|
383
338
|
const searchChildren = (n, count = 0) => {
|
|
384
339
|
switch (n.type) {
|
|
385
340
|
case 'BinaryExpression':
|
|
@@ -432,12 +387,11 @@ module.exports = {
|
|
|
432
387
|
case 'JSXElement': {
|
|
433
388
|
const name = n.openingElement.name.name || ''
|
|
434
389
|
|
|
435
|
-
if (
|
|
390
|
+
if (FIELDSET_REGEX.test(name) || checkAdditionalMultiInputComponents(name)) {
|
|
436
391
|
// 複数inputが存在する可能性のあるコンポーネントなので無限カウントとする
|
|
437
392
|
return Infinity
|
|
438
393
|
}
|
|
439
394
|
|
|
440
|
-
|
|
441
395
|
let nextCount = forInSearchChildren(n.openingElement.attributes, count)
|
|
442
396
|
|
|
443
397
|
if (nextCount > 1) {
|
|
@@ -445,8 +399,8 @@ module.exports = {
|
|
|
445
399
|
}
|
|
446
400
|
|
|
447
401
|
if (
|
|
448
|
-
|
|
449
|
-
|
|
402
|
+
FORM_CONTROL_INPUTS_REGEX.test(name) ||
|
|
403
|
+
FORM_CONTROL_REGEX.test(name) ||
|
|
450
404
|
checkAdditionalInputComponents(name)
|
|
451
405
|
) {
|
|
452
406
|
nextCount = nextCount + 1
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'(Ordered(.*)List|^ol)$': '(Ordered(.*)List)$',
|
|
5
|
-
'(S|s)elect$': '(Select)$',
|
|
6
|
-
}
|
|
7
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
8
|
-
|
|
9
1
|
const NUMBERED_TEXT_REGEX = /^[\s\n]*(([0-9])([^0-9]{2})[^\s\n]*)/
|
|
10
2
|
const ORDERED_LIST_REGEX = /(Ordered(.*)List|^ol)$/
|
|
11
3
|
const SELECT_REGEX = /(S|s)elect$/
|
|
@@ -18,12 +10,12 @@ const searchOrderedList = (node) => {
|
|
|
18
10
|
if (node.type === 'JSXElement' && node.openingElement.name?.name) {
|
|
19
11
|
const name = node.openingElement.name.name
|
|
20
12
|
|
|
21
|
-
if (
|
|
13
|
+
if (SELECT_REGEX.test(name)) {
|
|
22
14
|
// HINT: select要素の場合、optionのラベルに連番がついている場合がありえるのでignoreする
|
|
23
15
|
// 通常と処理を分けるためnullではなく0を返す
|
|
24
16
|
return 0
|
|
25
17
|
} else if (
|
|
26
|
-
|
|
18
|
+
ORDERED_LIST_REGEX.test(name) ||
|
|
27
19
|
node.openingElement.attributes.find(findAsOlAttr)
|
|
28
20
|
) {
|
|
29
21
|
return node.openingElement
|
|
@@ -129,7 +121,6 @@ module.exports = {
|
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
return {
|
|
132
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
133
124
|
JSXAttribute: (node) => {
|
|
134
125
|
if (node.value?.value && !IGNORE_ATTRIBUTE_VALUE_REGEX.test(node.value.value)) {
|
|
135
126
|
checker(node, node.value.value.match(NUMBERED_TEXT_REGEX))
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'(Input|^input)$': '(Input)$',
|
|
5
|
-
'(Textarea|^textarea)$': '(Textarea)$',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
9
|
-
|
|
10
|
-
const INPUT_COMPONENT_NAMES = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
|
|
1
|
+
const INPUT_COMPONENT_NAMES = /((I|^i)nput|(T|^t)extarea)$/
|
|
11
2
|
|
|
12
3
|
const SCHEMA = []
|
|
13
4
|
|
|
5
|
+
const checkHasMaxLength = (attr) => attr.name?.name === 'maxLength'
|
|
6
|
+
|
|
14
7
|
/**
|
|
15
8
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
16
9
|
*/
|
|
@@ -21,21 +14,16 @@ module.exports = {
|
|
|
21
14
|
},
|
|
22
15
|
create(context) {
|
|
23
16
|
return {
|
|
24
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
25
17
|
JSXOpeningElement: (node) => {
|
|
26
|
-
if (node.name.type === 'JSXIdentifier' && INPUT_COMPONENT_NAMES.test(node.name.name)) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
context.report({
|
|
31
|
-
node,
|
|
32
|
-
message: `${node.name.name}にmaxLength属性を設定しないでください。
|
|
18
|
+
if (node.name.type === 'JSXIdentifier' && INPUT_COMPONENT_NAMES.test(node.name.name) && node.attributes.find(checkHasMaxLength)) {
|
|
19
|
+
context.report({
|
|
20
|
+
node,
|
|
21
|
+
message: `${node.name.name}にmaxLength属性を設定しないでください。
|
|
33
22
|
- maxLength属性がついた要素に、テキストをペーストすると、maxLength属性の値を超えた範囲が意図せず切り捨てられてしまう場合があります
|
|
34
23
|
- 以下のいずれかの方法で修正をおこなってください
|
|
35
24
|
- 方法1: pattern属性とtitle属性を組み合わせ、form要素でラップする
|
|
36
25
|
- 方法2: JavaScriptを用いたバリデーションを実装する`,
|
|
37
|
-
|
|
38
|
-
}
|
|
26
|
+
})
|
|
39
27
|
}
|
|
40
28
|
},
|
|
41
29
|
}
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'(i|I)nput$': 'Input$',
|
|
5
|
-
'SearchInput$': 'SearchInput$',
|
|
6
|
-
'(t|T)extarea$': 'Textarea$',
|
|
7
|
-
'FieldSet$': 'FieldSet$',
|
|
8
|
-
'Combo(b|B)ox$': 'Combobox$',
|
|
9
|
-
'(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
|
|
10
|
-
'TimePicker$': 'TimePicker$',
|
|
11
|
-
}
|
|
12
|
-
const INPUT_TAG_REGEX = /((i|I)nput|(t|T)extarea|FieldSet|Combo(b|B)ox|(Date|Wareki|Time)Picker)$/
|
|
1
|
+
const INPUT_TAG_REGEX = /((I|^i)nput|(T|^t)extarea|FieldSet|Combo(B|b)ox|(Date|Wareki|Time)Picker)$/
|
|
13
2
|
const SEARCH_INPUT_REGEX = /SearchInput$/
|
|
14
|
-
const COMBOBOX_REGEX = /Combo(b
|
|
3
|
+
const COMBOBOX_REGEX = /Combo(B|b)ox$/
|
|
15
4
|
|
|
16
5
|
/**
|
|
17
6
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -23,7 +12,6 @@ module.exports = {
|
|
|
23
12
|
},
|
|
24
13
|
create(context) {
|
|
25
14
|
return {
|
|
26
|
-
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
27
15
|
JSXOpeningElement: (node) => {
|
|
28
16
|
const name = node.name.name
|
|
29
17
|
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
const { rootPath } = require('../../libs/common')
|
|
2
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
3
|
-
|
|
4
|
-
const SECTIONING_CONTENT_EXPECTED_NAMES = {
|
|
5
|
-
'(A|^a)rticle$': '(Article)$',
|
|
6
|
-
'(A|^a)side$': '(Aside)$',
|
|
7
|
-
'(N|^n)av$': '(Nav)$',
|
|
8
|
-
'(S|^s)ection$': '(Section)$',
|
|
9
|
-
}
|
|
10
|
-
const FIELDSET_EXPECTED_NAMES = {
|
|
11
|
-
'(FormControl)$': '(FormControl)$',
|
|
12
|
-
'(FormControls)$': '(FormControls)$',
|
|
13
|
-
'((F|^f)ieldset)$': '(Fieldset)$',
|
|
14
|
-
'(Fieldsets)$': '(Fieldsets)$',
|
|
15
|
-
}
|
|
16
|
-
const FORM_EXPECTED_NAMES = {
|
|
17
|
-
'((F|^f)orm)$': '(Form)$',
|
|
18
|
-
'(FormDialog)$': '(FormDialog)$',
|
|
19
|
-
'RemoteTrigger(.*)FormDialog$': '(RemoteTrigger(.*)FormDialog)$',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const WRAPPER_EXPECTED_NAMES = {
|
|
23
|
-
...FIELDSET_EXPECTED_NAMES,
|
|
24
|
-
...FORM_EXPECTED_NAMES,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const EXPECTED_NAMES = {
|
|
28
|
-
...SECTIONING_CONTENT_EXPECTED_NAMES,
|
|
29
|
-
...WRAPPER_EXPECTED_NAMES,
|
|
30
|
-
'SideNav$': '(SideNav)$',
|
|
31
|
-
'IndexNav$': '(IndexNav)$',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
35
2
|
|
|
36
3
|
const asRegex = /^(as|forwardedAs)$/
|
|
37
4
|
const asFormRegex = /^(form|fieldset)$/
|
|
@@ -41,9 +8,11 @@ const includeAsAttrFormOrFieldset = (a) => a.name?.name.match(asRegex) && asForm
|
|
|
41
8
|
const includeAsAttrSectioningContent = (a) => a.name?.name.match(asRegex) && asSectioningContentRegex.test(a.value.value)
|
|
42
9
|
const includeWrapper = (fn) => wrapperRegex.test(fn)
|
|
43
10
|
|
|
44
|
-
const sectioningContentRegex =
|
|
45
|
-
|
|
46
|
-
const
|
|
11
|
+
const sectioningContentRegex = /((A|^a)(rticle|side)|(N|^n)av|(S|^s)ection)$/
|
|
12
|
+
|
|
13
|
+
const formControlRegexStr = '(FormControl|(F|^f)ieldset)(s)?'
|
|
14
|
+
const formControlRegex = new RegExp(`${formControlRegexStr}$`)
|
|
15
|
+
const wrapperRegex = new RegExp(`(${formControlRegexStr}|(F|^f)orm(Dialog)?)$`)
|
|
47
16
|
const extRegex = /\.[a-z0-9]+?$/
|
|
48
17
|
const ignoreNavRegex = /(Side|Index)Nav$/
|
|
49
18
|
const formPartCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
@@ -146,7 +115,6 @@ module.exports = {
|
|
|
146
115
|
const notified = []
|
|
147
116
|
|
|
148
117
|
return {
|
|
149
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
150
118
|
JSXOpeningElement: (node) => {
|
|
151
119
|
const elementName = node.name.name
|
|
152
120
|
|
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'Article$': '(Article)$',
|
|
5
|
-
'Aside$': '(Aside)$',
|
|
6
|
-
'Nav$': '(Nav)$',
|
|
7
|
-
'Section$': '(Section)$',
|
|
8
|
-
'Center$': '(Center)$',
|
|
9
|
-
'Reel$': '(Reel)$',
|
|
10
|
-
'Sidebar$': '(Sidebar)$',
|
|
11
|
-
'Stack$': '(Stack)$',
|
|
12
|
-
'Base$': '(Base)$',
|
|
13
|
-
'BaseColumn$': '(BaseColumn)$',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
17
|
-
|
|
18
1
|
const BARE_SECTIONING_TAG_REGEX = /^(article|aside|nav|section)$/
|
|
19
2
|
const SECTIONING_REGEX = /((A(rticle|side))|Nav|Section)$/
|
|
20
|
-
const
|
|
3
|
+
const SECTIONING_FRAGMENT = 'SectioningFragment'
|
|
21
4
|
const LAYOUT_REGEX = /((C(ent|lust)er)|Reel|Sidebar|Stack|Base(Column)?)$/
|
|
22
5
|
const AS_REGEX = /^(as|forwardedAs)$/
|
|
23
6
|
|
|
24
|
-
const includeSectioningAsAttr = (a) => a.name?.name
|
|
7
|
+
const includeSectioningAsAttr = (a) => AS_REGEX.test(a.name?.name) && BARE_SECTIONING_TAG_REGEX.test(a.value.value)
|
|
25
8
|
|
|
26
9
|
const searchSectioningFragment = (node) => {
|
|
27
10
|
switch (node.type) {
|
|
28
11
|
case 'JSXElement':
|
|
29
|
-
return node.openingElement.name?.name
|
|
12
|
+
return SECTIONING_FRAGMENT === node.openingElement.name?.name ? node.openingElement : null
|
|
30
13
|
case 'Program':
|
|
31
14
|
return null
|
|
32
15
|
}
|
|
@@ -46,16 +29,15 @@ module.exports = {
|
|
|
46
29
|
},
|
|
47
30
|
create(context) {
|
|
48
31
|
return {
|
|
49
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
50
32
|
JSXOpeningElement: (node) => {
|
|
51
33
|
const name = node.name?.name || ''
|
|
52
34
|
let hit = null
|
|
53
35
|
let asAttr = null
|
|
54
36
|
|
|
55
|
-
if (
|
|
37
|
+
if (SECTIONING_REGEX.test(name)) {
|
|
56
38
|
hit = true
|
|
57
39
|
} else {
|
|
58
|
-
asAttr =
|
|
40
|
+
asAttr = LAYOUT_REGEX.test(name) && node.attributes.find(includeSectioningAsAttr)
|
|
59
41
|
|
|
60
42
|
if (asAttr) {
|
|
61
43
|
hit = true
|
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
|
-
const LAYOUT_EXPECTED_NAMES = {
|
|
4
|
-
'Center$': '(Center)$',
|
|
5
|
-
'Cluster$': '(Cluster)$',
|
|
6
|
-
'Reel$': '(Reel)$',
|
|
7
|
-
'Sidebar$': '(Sidebar)$',
|
|
8
|
-
'Stack$': '(Stack)$',
|
|
9
|
-
'Base$': '(Base)$',
|
|
10
|
-
'BaseColumn$': '(BaseColumn)$',
|
|
11
|
-
}
|
|
12
|
-
const EXPECTED_NAMES = {
|
|
13
|
-
...LAYOUT_EXPECTED_NAMES,
|
|
14
|
-
'PageHeading$': '(PageHeading)$',
|
|
15
|
-
'Heading$': '(Heading)$',
|
|
16
|
-
'^h1$': '(PageHeading)$',
|
|
17
|
-
'^h(|2|3|4|5|6)$': '(Heading)$',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const UNEXPECTED_NAMES = {
|
|
21
|
-
...LAYOUT_EXPECTED_NAMES,
|
|
22
|
-
'(Heading|^h(1|2|3|4|5|6))$': '(Heading)$',
|
|
23
|
-
}
|
|
24
|
-
|
|
25
1
|
const layoutRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack|Base(Column)?)$/
|
|
26
2
|
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
27
3
|
const asRegex = /^(as|forwardedAs)$/
|
|
@@ -65,7 +41,6 @@ module.exports = {
|
|
|
65
41
|
},
|
|
66
42
|
create(context) {
|
|
67
43
|
return {
|
|
68
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
69
44
|
JSXOpeningElement: (node) => {
|
|
70
45
|
const name = node.name.name || ''
|
|
71
46
|
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'(b|B)utton$': 'Button$',
|
|
7
|
-
'AnchorButton$': 'AnchorButton$',
|
|
8
|
-
'ButtonAnchor$': 'ButtonAnchor$',
|
|
9
|
-
'Anchor$': 'Anchor$',
|
|
10
|
-
'Link$': 'Link$',
|
|
11
|
-
'^a$': '(Anchor|Link)$',
|
|
12
|
-
}
|
|
1
|
+
const TRIGGER_REGEX = /(Dropdown|Dialog)Trigger$/
|
|
2
|
+
const HELP_DIALOG_TRIGGER_REGEX = /HelpDialogTrigger$/
|
|
3
|
+
const BUTTON_REGEX = /(B|^b)utton$/
|
|
4
|
+
const ANCHOR_BUTTON_REGEX = /AnchorButton$/
|
|
5
|
+
const FALSY_TEXT_REGEX = /^\s*\n+\s*$/
|
|
13
6
|
|
|
14
7
|
const filterFalsyJSXText = (cs) => cs.filter((c) => (
|
|
15
|
-
!(c.type === 'JSXText' && c.value
|
|
8
|
+
!(c.type === 'JSXText' && FALSY_TEXT_REGEX.test(c.value))
|
|
16
9
|
))
|
|
17
10
|
|
|
18
11
|
/**
|
|
@@ -25,7 +18,6 @@ module.exports = {
|
|
|
25
18
|
},
|
|
26
19
|
create(context) {
|
|
27
20
|
return {
|
|
28
|
-
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
29
21
|
JSXElement: (parentNode) => {
|
|
30
22
|
// HINT: 閉じタグが存在しない === 子が存在しない
|
|
31
23
|
// 子を持っていない場合はおそらく固定の要素を吐き出すコンポーネントと考えられるため
|
|
@@ -40,9 +32,9 @@ module.exports = {
|
|
|
40
32
|
return
|
|
41
33
|
}
|
|
42
34
|
|
|
43
|
-
const match = node.name.name.match(
|
|
35
|
+
const match = node.name.name.match(TRIGGER_REGEX)
|
|
44
36
|
|
|
45
|
-
if (!match || node.name.name
|
|
37
|
+
if (!match || HELP_DIALOG_TRIGGER_REGEX.test(node.name.name)) {
|
|
46
38
|
return
|
|
47
39
|
}
|
|
48
40
|
|
|
@@ -65,8 +57,8 @@ module.exports = {
|
|
|
65
57
|
|
|
66
58
|
if (
|
|
67
59
|
c.type !== 'JSXElement' ||
|
|
68
|
-
!c.openingElement.name.name
|
|
69
|
-
c.openingElement.name.name
|
|
60
|
+
!BUTTON_REGEX.test(c.openingElement.name.name) ||
|
|
61
|
+
ANCHOR_BUTTON_REGEX.test(c.openingElement.name.name)
|
|
70
62
|
) {
|
|
71
63
|
context.report({
|
|
72
64
|
node: c,
|