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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
## [1.5.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.5.0...eslint-plugin-smarthr-v1.5.1) (2025-05-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* require-barrel-import のロジックミスを修正する ([#619](https://github.com/kufu/tamatebako/issues/619)) ([60b21f7](https://github.com/kufu/tamatebako/commit/60b21f7949aa21e85b944825f9a55dda380943fa))
|
|
11
|
+
|
|
12
|
+
## [1.5.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.4.2...eslint-plugin-smarthr-v1.5.0) (2025-05-12)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* 個別のルールで行っていたコンポーネントの命名規則チェックをcomponent-name ルールとして統一する ([#610](https://github.com/kufu/tamatebako/issues/610)) ([a73b7e7](https://github.com/kufu/tamatebako/commit/a73b7e7e305bbc84fe7001ca9ed040c3889550f4))
|
|
18
|
+
|
|
5
19
|
## [1.4.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.4.1...eslint-plugin-smarthr-v1.4.2) (2025-03-27)
|
|
6
20
|
|
|
7
21
|
|
package/libs/common_domain.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
const { replacePaths, rootPath } = require('./common')
|
|
4
|
+
const { getParentDir } = require('./util')
|
|
4
5
|
|
|
5
6
|
const BASE_SCHEMA_PROPERTIES = {
|
|
6
7
|
globalModuleDir: { type: 'array', items: { type: 'string' } },
|
|
@@ -14,11 +15,7 @@ const calculateDomainContext = (context) => {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const filename = context.getFilename()
|
|
17
|
-
const parentDir = (
|
|
18
|
-
const dir = filename.split('/')
|
|
19
|
-
dir.pop()
|
|
20
|
-
return dir.join('/')
|
|
21
|
-
})()
|
|
18
|
+
const parentDir = getParentDir(filename)
|
|
22
19
|
const humanizeParentDir = parentDir.replace(new RegExp(`^${rootPath}/(.+)$`), '$1')
|
|
23
20
|
|
|
24
21
|
return {
|
|
@@ -34,7 +31,7 @@ const calculateDomainNode = (calclatedContext, node) => {
|
|
|
34
31
|
const importPath = node.source.value
|
|
35
32
|
const { option, parentDir, humanizeParentDir } = calclatedContext
|
|
36
33
|
|
|
37
|
-
let replacedPath = importPath
|
|
34
|
+
let replacedPath = importPath
|
|
38
35
|
|
|
39
36
|
if (replacePaths) {
|
|
40
37
|
const exts = ['.ts', '.tsx', '.js', '.jsx', '']
|
|
@@ -75,7 +72,7 @@ const calculateDomainNode = (calclatedContext, node) => {
|
|
|
75
72
|
if (
|
|
76
73
|
!resolvedImportPath ||
|
|
77
74
|
option.globalModuleDir &&
|
|
78
|
-
option.globalModuleDir.some((global) =>
|
|
75
|
+
option.globalModuleDir.some((global) =>
|
|
79
76
|
!!resolvedImportPath.match(new RegExp(`^${path.resolve(`${process.cwd()}/${global}`)}`))
|
|
80
77
|
)
|
|
81
78
|
) {
|
|
@@ -137,4 +134,4 @@ const calculateDomainNode = (calclatedContext, node) => {
|
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
136
|
|
|
140
|
-
module.exports = { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode }
|
|
137
|
+
module.exports = { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode, getParentDir }
|
package/libs/util.js
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=22.15.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "jest"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"json5": "^2.2.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"typescript-eslint": "^8.
|
|
29
|
+
"typescript-eslint": "^8.32.0"
|
|
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": "e74469a80bae406252eea39f939efa557857bc88"
|
|
41
41
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const JSON5 = require('json5')
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
|
|
4
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
5
|
-
|
|
6
4
|
const OPTION = (() => {
|
|
7
5
|
const file = `${process.cwd()}/package.json`
|
|
8
6
|
|
|
@@ -12,26 +10,40 @@ const OPTION = (() => {
|
|
|
12
10
|
|
|
13
11
|
const json = JSON5.parse(fs.readFileSync(file))
|
|
14
12
|
const dependencies = [
|
|
15
|
-
...Object.keys(json.dependencies
|
|
16
|
-
...Object.keys(json.devDependencies
|
|
13
|
+
...(json.dependencies ? Object.keys(json.dependencies) : []),
|
|
14
|
+
...(json.devDependencies ? Object.keys(json.devDependencies) : []),
|
|
17
15
|
]
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
let nextjs = false
|
|
18
|
+
let react_router = false
|
|
19
|
+
const result = () => ({
|
|
20
|
+
nextjs,
|
|
21
|
+
react_router,
|
|
22
|
+
})
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
24
|
+
for (let i = 0; i < dependencies.length; i++) {
|
|
25
|
+
switch (dependencies[i]) {
|
|
26
|
+
case 'next':
|
|
27
|
+
nextjs = true
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
if (react_router) {
|
|
30
|
+
return result()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
break
|
|
34
|
+
case 'react-router-dom':
|
|
35
|
+
react_router = true
|
|
36
|
+
|
|
37
|
+
if (nextjs) {
|
|
38
|
+
return result()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result()
|
|
46
|
+
})()
|
|
35
47
|
|
|
36
48
|
const REGEX_TARGET = /(Anchor|Link|^a)$/
|
|
37
49
|
const check = (node, checkType) => {
|
|
@@ -43,7 +55,7 @@ const baseCheck = (node, checkType) => {
|
|
|
43
55
|
const nodeName = node.name.name || ''
|
|
44
56
|
|
|
45
57
|
if (
|
|
46
|
-
|
|
58
|
+
REGEX_TARGET.test(nodeName) &&
|
|
47
59
|
checkExistAttribute(node, findHrefAttribute) &&
|
|
48
60
|
(checkType !== 'allow-spread-attributes' || !node.attributes.some(findSpreadAttr))
|
|
49
61
|
) {
|
|
@@ -106,7 +118,6 @@ module.exports = {
|
|
|
106
118
|
const checkType = option.checkType || 'always'
|
|
107
119
|
|
|
108
120
|
return {
|
|
109
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
110
121
|
JSXOpeningElement: (node) => {
|
|
111
122
|
const nodeName = check(node, checkType)
|
|
112
123
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
1
|
const SCHEMA = [
|
|
4
2
|
{
|
|
5
3
|
type: 'object',
|
|
@@ -10,22 +8,6 @@ const SCHEMA = [
|
|
|
10
8
|
}
|
|
11
9
|
]
|
|
12
10
|
|
|
13
|
-
const EXPECTED_NAMES = {
|
|
14
|
-
'SmartHRLogo$': 'SmartHRLogo$',
|
|
15
|
-
'(b|B)utton$': 'Button$',
|
|
16
|
-
'Anchor$': 'Anchor$',
|
|
17
|
-
'Link$': 'Link$',
|
|
18
|
-
'Text$': 'Text$',
|
|
19
|
-
'Message$': 'Message$',
|
|
20
|
-
'^a$': '(Anchor|Link)$',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const UNEXPECTED_NAMES = {
|
|
24
|
-
'(B|^b)utton$': '(Button)$',
|
|
25
|
-
'(Anchor|^a)$': '(Anchor)$',
|
|
26
|
-
'(Link|^a)$': '(Link)$',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
11
|
const REGEX_NLSP = /^\s*\n+\s*$/
|
|
30
12
|
const REGEX_CLICKABLE_ELEMENT = /^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/
|
|
31
13
|
const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
|
|
@@ -33,9 +15,7 @@ const REGEX_TEXT_COMPONENT = /(Text|Message)$/
|
|
|
33
15
|
const REGEX_JSX_TYPE = /^(JSXText|JSXExpressionContainer)$/
|
|
34
16
|
|
|
35
17
|
const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
|
|
36
|
-
const checkFalsyJSXText = (c) => (
|
|
37
|
-
!(c.type === 'JSXText' && c.value.match(REGEX_NLSP))
|
|
38
|
-
)
|
|
18
|
+
const checkFalsyJSXText = (c) => c.type !== 'JSXText' || !REGEX_NLSP.test(c.value)
|
|
39
19
|
|
|
40
20
|
const message = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください
|
|
41
21
|
- 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
|
|
@@ -55,7 +35,6 @@ module.exports = {
|
|
|
55
35
|
const componentsWithText = option.componentsWithText || []
|
|
56
36
|
|
|
57
37
|
return {
|
|
58
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
59
38
|
JSXElement: (parentNode) => {
|
|
60
39
|
// HINT: 閉じタグが存在しない === テキストノードが存在しない
|
|
61
40
|
if (!parentNode.closingElement) {
|
|
@@ -64,7 +43,7 @@ module.exports = {
|
|
|
64
43
|
|
|
65
44
|
const node = parentNode.openingElement
|
|
66
45
|
|
|
67
|
-
if (!node.name.name || !node.name.name
|
|
46
|
+
if (!node.name.name || !REGEX_CLICKABLE_ELEMENT.test(node.name.name)) {
|
|
68
47
|
return
|
|
69
48
|
}
|
|
70
49
|
|
|
@@ -79,13 +58,13 @@ module.exports = {
|
|
|
79
58
|
}
|
|
80
59
|
case 'JSXElement': {
|
|
81
60
|
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
|
|
82
|
-
if (c.openingElement.name.name
|
|
61
|
+
if (REGEX_SMARTHR_LOGO.test(c.openingElement.name.name)) {
|
|
83
62
|
return true
|
|
84
63
|
}
|
|
85
64
|
|
|
86
65
|
const tagName = c.openingElement.name.name
|
|
87
66
|
|
|
88
|
-
if (
|
|
67
|
+
if (REGEX_TEXT_COMPONENT.test(tagName) || componentsWithText.includes(tagName)) {
|
|
89
68
|
return true
|
|
90
69
|
}
|
|
91
70
|
|
|
@@ -1,47 +1,28 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'(
|
|
5
|
-
'(
|
|
6
|
-
'(s
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'DropZone
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'TabItem
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'(f|F)orm$': 'Form$',
|
|
28
|
-
'ActionDialogWithTrigger$': 'ActionDialogWithTrigger$',
|
|
29
|
-
'RemoteDialogTrigger$': 'RemoteDialogTrigger$',
|
|
30
|
-
'RemoteTrigger(.+)Dialog$': 'RemoteTrigger(.+)Dialog$',
|
|
31
|
-
'FormDialog$': 'FormDialog$',
|
|
32
|
-
'Pagination$': 'Pagination$',
|
|
33
|
-
'SideNav$': 'SideNav$',
|
|
34
|
-
'AccordionPanel$': 'AccordionPanel$',
|
|
35
|
-
'FilterDropdown$': 'FilterDropdown$',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const UNEXPECTED_NAMES = {
|
|
39
|
-
'(B|^b)utton$': '(Button)$',
|
|
40
|
-
'(Anchor|^a)$': '(Anchor)$',
|
|
41
|
-
'(Link|^a)$': '(Link)$',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const INTERACTIVE_COMPONENT_NAMES = Object.keys(EXPECTED_NAMES).join('|')
|
|
1
|
+
const INTERACTIVE_COMPONENT_NAMES = `(${[
|
|
2
|
+
'(B|b)utton',
|
|
3
|
+
'(Check|Combo)(B|b)ox',
|
|
4
|
+
'(Date(timeLocal)?|Time|Month|Wareki)Picker',
|
|
5
|
+
'(I|i)nput(File)?',
|
|
6
|
+
'(S|s)elect',
|
|
7
|
+
'(T|t)extarea',
|
|
8
|
+
'(ActionDialogWith|RemoteDialog)Trigger',
|
|
9
|
+
'AccordionPanel',
|
|
10
|
+
'^a',
|
|
11
|
+
'Anchor',
|
|
12
|
+
'Link',
|
|
13
|
+
'DropZone',
|
|
14
|
+
'Field(S|s)et',
|
|
15
|
+
'FilterDropdown',
|
|
16
|
+
'(F|f)orm(Control|Group|Dialog)?',
|
|
17
|
+
'Pagination',
|
|
18
|
+
'RadioButton(Panel)?',
|
|
19
|
+
'RemoteTrigger(.+)Dialog',
|
|
20
|
+
'RightFixedNote',
|
|
21
|
+
'SegmentedControl',
|
|
22
|
+
'SideNav',
|
|
23
|
+
'Switch',
|
|
24
|
+
'TabItem',
|
|
25
|
+
].join('|')})$`
|
|
45
26
|
const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/
|
|
46
27
|
const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/
|
|
47
28
|
const INTERACTIVE_NODE_TYPE_REGEX = /^(JSXElement|JSXExpressionContainer|ConditionalExpression)$/
|
|
@@ -94,13 +75,13 @@ module.exports = {
|
|
|
94
75
|
create(context) {
|
|
95
76
|
const options = context.options[0]
|
|
96
77
|
const interactiveComponentRegex = new RegExp(`(${INTERACTIVE_COMPONENT_NAMES}${options?.additionalInteractiveComponentRegex ? `|${options.additionalInteractiveComponentRegex.join('|')}` : ''})`)
|
|
97
|
-
const findInteractiveNode = (ec) => ec && ec.type
|
|
78
|
+
const findInteractiveNode = (ec) => ec && INTERACTIVE_NODE_TYPE_REGEX.test(ec.type) && isHasInteractive(ec)
|
|
98
79
|
const isHasInteractive = (c) => {
|
|
99
80
|
switch (c.type) {
|
|
100
81
|
case 'JSXElement': {
|
|
101
82
|
const name = c.openingElement.name.name
|
|
102
83
|
|
|
103
|
-
if (name &&
|
|
84
|
+
if (name && interactiveComponentRegex.test(name)) {
|
|
104
85
|
return true
|
|
105
86
|
} else if (c.children.length > 0) {
|
|
106
87
|
return !!c.children.find(isHasInteractive)
|
|
@@ -122,7 +103,6 @@ module.exports = {
|
|
|
122
103
|
}
|
|
123
104
|
|
|
124
105
|
return {
|
|
125
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
126
106
|
JSXOpeningElement: (node) => {
|
|
127
107
|
const nodeName = node.name.name || '';
|
|
128
108
|
|
|
@@ -134,7 +114,7 @@ module.exports = {
|
|
|
134
114
|
node.attributes.forEach((a) => {
|
|
135
115
|
const aName = a.name?.name || ''
|
|
136
116
|
|
|
137
|
-
if (
|
|
117
|
+
if (INTERACTIVE_ON_REGEX.test(aName)) {
|
|
138
118
|
onAttrs.push(aName)
|
|
139
119
|
} else if (AS_REGEX.test(aName) && AS_VALUE_REGEX.test(a.value?.value || '')) {
|
|
140
120
|
isAsInteractive = true
|
|
@@ -144,13 +124,13 @@ module.exports = {
|
|
|
144
124
|
if (v === 'presentation') {
|
|
145
125
|
isRolePresentation = true
|
|
146
126
|
roleMean = v
|
|
147
|
-
} else if (
|
|
127
|
+
} else if (MEANED_ROLE_REGEX.test(v)) {
|
|
148
128
|
roleMean = v
|
|
149
129
|
}
|
|
150
130
|
}
|
|
151
131
|
})
|
|
152
132
|
|
|
153
|
-
if (isAsInteractive ||
|
|
133
|
+
if (isAsInteractive || interactiveComponentRegex.test(nodeName)) {
|
|
154
134
|
if (isRolePresentation) {
|
|
155
135
|
context.report({
|
|
156
136
|
node,
|
|
@@ -204,11 +184,10 @@ module.exports = {
|
|
|
204
184
|
case 'JSXElement': {
|
|
205
185
|
const name = n.openingElement.name.name || ''
|
|
206
186
|
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (forInSearchChildren(n.openingElement.attributes)) {
|
|
187
|
+
if (
|
|
188
|
+
interactiveComponentRegex.test(name) ||
|
|
189
|
+
forInSearchChildren(n.openingElement.attributes)
|
|
190
|
+
) {
|
|
212
191
|
return true
|
|
213
192
|
}
|
|
214
193
|
|
|
@@ -1,36 +1,11 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const FIELDSET_EXPECTED_NAMES = {
|
|
4
|
-
'((F|^f)ieldset)$': '(Fieldset)$',
|
|
5
|
-
'(Fieldsets)$': '(Fieldsets)$',
|
|
6
|
-
}
|
|
7
|
-
const FORM_CONTROL_EXPECTED_NAMES = {
|
|
8
|
-
...FIELDSET_EXPECTED_NAMES,
|
|
9
|
-
'(FormGroup)$': '(FormGroup)$',
|
|
10
|
-
'(FormControl)$': '(FormControl)$',
|
|
11
|
-
'(FormControls)$': '(FormControls)$',
|
|
12
|
-
}
|
|
13
|
-
const FORM_EXPECTED_NAMES = {
|
|
14
|
-
'((F|^f)orm)$': '(Form)$',
|
|
15
|
-
'(FormDialog)$': '(FormDialog)$',
|
|
16
|
-
'RemoteTrigger(.*)FormDialog$': 'RemoteTrigger(.*)FormDialog$',
|
|
17
|
-
'FilterDropdown$': '(FilterDropdown)$',
|
|
18
|
-
}
|
|
19
|
-
const EXPECTED_NAMES = {
|
|
20
|
-
...FORM_CONTROL_EXPECTED_NAMES,
|
|
21
|
-
...FORM_EXPECTED_NAMES,
|
|
22
|
-
}
|
|
23
|
-
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
24
|
-
|
|
25
|
-
const targetRegex = new RegExp(`(${Object.keys(FORM_CONTROL_EXPECTED_NAMES).join('|')})`)
|
|
26
|
-
const wrapperRegex = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
|
|
1
|
+
const targetRegex = /((F|^f)ieldset|(F|^f)orm(Group|Control))(s)?$/
|
|
2
|
+
const wrapperRegex = /((F|^f)ieldset(s)?|(F|^f)orm((Group|Control)(s)?)?|(RemoteTrigger(.*))?FormDialog|FilterDropdown)$/
|
|
27
3
|
const ignoreCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
28
|
-
const
|
|
29
|
-
const declaratorTargetRegex = new RegExp(messageFieldset)
|
|
4
|
+
const declaratorTargetRegex = /(Fieldset|Form(Group|Control))(s)?$/
|
|
30
5
|
const asRegex = /^(as|forwardedAs)$/
|
|
31
6
|
const bareTagRegex = /^(form|fieldset)$/
|
|
32
7
|
|
|
33
|
-
const includeAsAttrFormOrFieldset = (a) => a.name?.name
|
|
8
|
+
const includeAsAttrFormOrFieldset = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
|
|
34
9
|
|
|
35
10
|
const searchBubbleUp = (node) => {
|
|
36
11
|
switch (node.type) {
|
|
@@ -73,7 +48,6 @@ module.exports = {
|
|
|
73
48
|
},
|
|
74
49
|
create(context) {
|
|
75
50
|
return {
|
|
76
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
77
51
|
JSXOpeningElement: (node) => {
|
|
78
52
|
const elementName = node.name.name
|
|
79
53
|
|
|
@@ -87,7 +61,7 @@ module.exports = {
|
|
|
87
61
|
- form要素で囲むことでスクリーンリーダーに入力フォームであることが正しく伝わる、入力要素にfocusした状態でEnterを押せばsubmitできる、inputのpattern属性を利用できるなどのメリットがあります
|
|
88
62
|
- 以下のいずれかの方法で修正をおこなってください
|
|
89
63
|
- 方法1: form要素で ${elementName} を囲んでください。smarthr-ui/ActionDialog、もしくはsmarthr-ui/RemoteTriggerActionDialogを利用している場合、smarthr-ui/FormDialog、smarthr-ui/RemoteTriggerFormDialogに置き換えてください
|
|
90
|
-
- 方法2: ${elementName} がコンポーネント内の一要素であり、かつその親コンポーネントがFormControl、もしくはFieldsetを表現するものである場合、親コンポーネント名を "${
|
|
64
|
+
- 方法2: ${elementName} がコンポーネント内の一要素であり、かつその親コンポーネントがFormControl、もしくはFieldsetを表現するものである場合、親コンポーネント名を "${declaratorTargetRegex}" とマッチするものに変更してください`,
|
|
91
65
|
})
|
|
92
66
|
}
|
|
93
67
|
}
|
|
@@ -1,55 +1,3 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'PageHeading$': 'PageHeading$',
|
|
5
|
-
'Heading$': 'Heading$',
|
|
6
|
-
'^h1$': 'PageHeading$',
|
|
7
|
-
'^h(|2|3|4|5|6)$': 'Heading$',
|
|
8
|
-
'Article$': 'Article$',
|
|
9
|
-
'Aside$': 'Aside$',
|
|
10
|
-
'Nav$': 'Nav$',
|
|
11
|
-
'Section$': 'Section$',
|
|
12
|
-
'ModelessDialog$': 'ModelessDialog$',
|
|
13
|
-
'Center$': 'Center$',
|
|
14
|
-
'Cluster$': '(Cluster)$',
|
|
15
|
-
'Reel$': 'Reel$',
|
|
16
|
-
'Sidebar$': 'Sidebar$',
|
|
17
|
-
'Stack$': 'Stack$',
|
|
18
|
-
'Base$': 'Base$',
|
|
19
|
-
'BaseColumn$': 'BaseColumn$',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています
|
|
23
|
-
- childrenにHeadingを含まない場合、コンポーネントの名称から"{{expected}}"を取り除いてください
|
|
24
|
-
- childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/{{expected}}をexendしてください
|
|
25
|
-
- "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"{{expected}}"を設定し、そのコンポーネント内でsmarthr-ui/{{expected}}を利用してください`
|
|
26
|
-
const UNEXPECTED_NAMES = {
|
|
27
|
-
'(Heading|^h(1|2|3|4|5|6))$': '(Heading)$',
|
|
28
|
-
'(A|^a)rticle$': [
|
|
29
|
-
'(Article)$',
|
|
30
|
-
unexpectedMessageTemplate,
|
|
31
|
-
],
|
|
32
|
-
'(A|^a)side$': [
|
|
33
|
-
'(Aside)$',
|
|
34
|
-
unexpectedMessageTemplate,
|
|
35
|
-
],
|
|
36
|
-
'(N|^n)av$': [
|
|
37
|
-
'(Nav)$',
|
|
38
|
-
unexpectedMessageTemplate,
|
|
39
|
-
],
|
|
40
|
-
'(S|^s)ection$': [
|
|
41
|
-
'(Section)$',
|
|
42
|
-
unexpectedMessageTemplate,
|
|
43
|
-
],
|
|
44
|
-
'Center$': '(Center)$',
|
|
45
|
-
'Cluster$': '(Cluster)$',
|
|
46
|
-
'Reel$': '(Reel)$',
|
|
47
|
-
'Sidebar$': '(Sidebar)$',
|
|
48
|
-
'Stack$': '(Stack)$',
|
|
49
|
-
'Base$': '(Base)$',
|
|
50
|
-
'BaseColumn$': '(BaseColumn)$',
|
|
51
|
-
}
|
|
52
|
-
|
|
53
1
|
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
54
2
|
const pageHeadingRegex = /PageHeading$/
|
|
55
3
|
const declaratorHeadingRegex = /Heading$/
|
|
@@ -63,7 +11,7 @@ const noHeadingTagNamesRegex = /^(span|legend)$/
|
|
|
63
11
|
const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
64
12
|
const headingAttributeRegex = /^(heading|title)$/
|
|
65
13
|
|
|
66
|
-
const includeSectioningAsAttr = (a) => a.name?.name
|
|
14
|
+
const includeSectioningAsAttr = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
|
|
67
15
|
const findHeadingAttribute = (a) => headingAttributeRegex.test(a.name?.name || '')
|
|
68
16
|
|
|
69
17
|
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
@@ -117,7 +65,7 @@ const searchBubbleUp = (node) => {
|
|
|
117
65
|
|
|
118
66
|
if (
|
|
119
67
|
// Headingコンポーネントの拡張なので対象外
|
|
120
|
-
node.type === 'VariableDeclarator' && node.parent.parent?.type
|
|
68
|
+
node.type === 'VariableDeclarator' && ignoreHeadingCheckParentTypeRegex.test(node.parent.parent?.type) && declaratorHeadingRegex.test(node.id.name) ||
|
|
121
69
|
node.type === 'FunctionDeclaration' && ignoreHeadingCheckParentTypeRegex.test(node.parent.type) && declaratorHeadingRegex.test(node.id.name) ||
|
|
122
70
|
// ModelessDialogのheaderにHeadingを設定している場合も対象外
|
|
123
71
|
node.type === 'JSXAttribute' && node.name.name === 'header' && modelessDialogRegex.test(node.parent.name.name)
|
|
@@ -197,7 +145,7 @@ const searchChildren = (n) => {
|
|
|
197
145
|
} else if (
|
|
198
146
|
(
|
|
199
147
|
headingRegex.test(name) &&
|
|
200
|
-
!n.openingElement.attributes.find(findTagAttr)?.value.value
|
|
148
|
+
!noHeadingTagNamesRegex.test(n.openingElement.attributes.find(findTagAttr)?.value.value)
|
|
201
149
|
) ||
|
|
202
150
|
forInSearchChildren(n.openingElement.attributes)
|
|
203
151
|
) {
|
|
@@ -238,15 +186,11 @@ module.exports = {
|
|
|
238
186
|
create(context) {
|
|
239
187
|
let h1s = []
|
|
240
188
|
let sections = []
|
|
241
|
-
let { VariableDeclarator, ...formatter } = generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES, unexpectedMessageTemplate })
|
|
242
|
-
|
|
243
|
-
formatter.VariableDeclarator = (node) => {
|
|
244
|
-
VariableDeclarator(node)
|
|
245
|
-
VariableDeclaratorBareToSHR(context, node)
|
|
246
|
-
}
|
|
247
189
|
|
|
248
190
|
return {
|
|
249
|
-
|
|
191
|
+
VariableDeclarator: (node) => {
|
|
192
|
+
VariableDeclaratorBareToSHR(context, node)
|
|
193
|
+
},
|
|
250
194
|
JSXOpeningElement: (node) => {
|
|
251
195
|
const elementName = node.name.name || ''
|
|
252
196
|
const message = reportMessageBareToSHR(elementName, false)
|
|
@@ -260,7 +204,7 @@ module.exports = {
|
|
|
260
204
|
} else if (headingRegex.test(elementName)) {
|
|
261
205
|
const tagAttr = node.attributes.find(findTagAttr)
|
|
262
206
|
|
|
263
|
-
if (!tagAttr?.value.value
|
|
207
|
+
if (!noHeadingTagNamesRegex.test(tagAttr?.value.value)) {
|
|
264
208
|
const result = searchBubbleUp(node.parent)
|
|
265
209
|
let hit = false
|
|
266
210
|
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
-
|
|
3
|
-
const EXPECTED_NAMES = {
|
|
4
|
-
'Img$': 'Img$',
|
|
5
|
-
'Image$': 'Image$',
|
|
6
|
-
'Icon$': 'Icon$',
|
|
7
|
-
'^(img|svg)$': '(Img|Image|Icon)$',
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const UNEXPECTED_NAMES = {
|
|
11
|
-
'(Img|^(img|svg))$': '(Img)$',
|
|
12
|
-
'(Image|^(img|svg))$': '(Image)$',
|
|
13
|
-
'(Icon|^(img|svg))$': '(Icon)$',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
1
|
const REGEX_IMG = /(img|image)$/i // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
|
|
17
2
|
|
|
18
3
|
const findAltAttr = (a) => a.name?.name === 'alt'
|
|
@@ -61,7 +46,6 @@ module.exports = {
|
|
|
61
46
|
const checkType = option.checkType || 'always'
|
|
62
47
|
|
|
63
48
|
return {
|
|
64
|
-
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
65
49
|
JSXOpeningElement: (node) => {
|
|
66
50
|
if (node.name.name) {
|
|
67
51
|
const matcher = node.name.name.match(REGEX_IMG)
|