eslint-plugin-smarthr 0.2.7 → 0.2.9
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 +21 -0
- package/README.md +1 -0
- package/libs/common_domain.js +1 -1
- package/package.json +1 -1
- package/rules/a11y-clickable-element-has-text/README.md +23 -1
- package/rules/a11y-clickable-element-has-text/index.js +44 -7
- package/rules/a11y-prohibit-input-placeholder/index.js +23 -6
- package/rules/require-declaration/README.md +62 -0
- package/rules/require-declaration/index.js +134 -0
- package/test/a11y-clickable-element-has-text.js +23 -0
- package/test/a11y-prohhibit-input-placeholder.js +27 -4
- package/test/require-declaration.js +159 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
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.2.9](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.8...v0.2.9) (2022-10-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-prohibit-input-placeholder を SearchInputに対応させる ([#38](https://github.com/kufu/eslint-plugin-smarthr/issues/38)) ([60de76b](https://github.com/kufu/eslint-plugin-smarthr/commit/60de76b58731436fe924a8a99da2242404141381))
|
|
11
|
+
* add require-declaration rule ([#34](https://github.com/kufu/eslint-plugin-smarthr/issues/34)) ([5dc6d44](https://github.com/kufu/eslint-plugin-smarthr/commit/5dc6d444e63f452f933bf6937207cfe23787732f))
|
|
12
|
+
* placeholder非推奨の対象に FieldSet, ComboBox も含める ([#35](https://github.com/kufu/eslint-plugin-smarthr/issues/35)) ([0e8d1d0](https://github.com/kufu/eslint-plugin-smarthr/commit/0e8d1d03377476fbd58adce17455e96533db69fa))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* a11y-clickable-element-has-textのバグを修正する ([#36](https://github.com/kufu/eslint-plugin-smarthr/issues/36)) ([4efc23d](https://github.com/kufu/eslint-plugin-smarthr/commit/4efc23d33ba6eec2c454b323f561de3f7a678fab))
|
|
18
|
+
|
|
19
|
+
### [0.2.8](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.7...v0.2.8) (2022-10-05)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* チェックするファイルのdirを取得するロジックから正規表現を取り除く ([#33](https://github.com/kufu/eslint-plugin-smarthr/issues/33)) ([c89548e](https://github.com/kufu/eslint-plugin-smarthr/commit/c89548e465e1f5aec49c6e1fc3b66c5bfefa6281))
|
|
25
|
+
|
|
5
26
|
### [0.2.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.5...v0.2.7) (2022-10-03)
|
|
6
27
|
|
|
7
28
|
|
package/README.md
CHANGED
|
@@ -13,5 +13,6 @@
|
|
|
13
13
|
- [prohibit-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-import)
|
|
14
14
|
- [redundant-name](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/redundant-name)
|
|
15
15
|
- [require-barrel-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-barrel-import)
|
|
16
|
+
- [require-declaration](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-declaration)
|
|
16
17
|
- [require-export](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-export)
|
|
17
18
|
- [require-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-import)
|
package/libs/common_domain.js
CHANGED
package/package.json
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
```js
|
|
8
8
|
{
|
|
9
9
|
rules: {
|
|
10
|
-
'smarthr/a11y-clickable-element-has-text':
|
|
10
|
+
'smarthr/a11y-clickable-element-has-text': [
|
|
11
|
+
'error', // 'warn', 'off'
|
|
12
|
+
// {
|
|
13
|
+
// componentsWithText: ['AnyComponentName'],
|
|
14
|
+
// },
|
|
15
|
+
]
|
|
11
16
|
},
|
|
12
17
|
}
|
|
13
18
|
```
|
|
@@ -59,3 +64,20 @@
|
|
|
59
64
|
```jsx
|
|
60
65
|
<YyyAnchoor />
|
|
61
66
|
```
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
69
|
+
/*
|
|
70
|
+
rules: {
|
|
71
|
+
'smarthr/a11y-clickable-element-has-text': [
|
|
72
|
+
'error',
|
|
73
|
+
{
|
|
74
|
+
componentsWithText: ['AnyComponent'],
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
<XxxButton>
|
|
81
|
+
<AnyComponent />
|
|
82
|
+
</XxxButton>
|
|
83
|
+
```
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
2
|
|
|
3
|
+
const SCHEMA = [
|
|
4
|
+
{
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
componentsWithText: { type: 'array', items: { type: 'string' }, default: [] },
|
|
8
|
+
},
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
|
|
3
13
|
const EXPECTED_NAMES = {
|
|
4
14
|
'SmartHRLogo$': 'SmartHRLogo$',
|
|
5
15
|
'(b|B)utton$': 'Button$',
|
|
@@ -19,9 +29,12 @@ module.exports = {
|
|
|
19
29
|
'format-styled-components': '{{ message }}',
|
|
20
30
|
'a11y-clickable-element-has-text': '{{ message }}',
|
|
21
31
|
},
|
|
22
|
-
schema:
|
|
32
|
+
schema: SCHEMA,
|
|
23
33
|
},
|
|
24
34
|
create(context) {
|
|
35
|
+
const option = context.options[0] || {}
|
|
36
|
+
const componentsWithText = option.componentsWithText || []
|
|
37
|
+
|
|
25
38
|
return {
|
|
26
39
|
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
27
40
|
JSXElement: (parentNode) => {
|
|
@@ -41,19 +54,43 @@ module.exports = {
|
|
|
41
54
|
return true
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
if (c.type === 'JSXFragment') {
|
|
58
|
+
if (c.children && filterFalsyJSXText(c.children).some(recursiveSearch)) {
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
if (c.type === 'JSXElement') {
|
|
45
66
|
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
|
|
46
67
|
if (c.openingElement.name.name.match(/SmartHRLogo$/)) {
|
|
47
68
|
return true
|
|
48
69
|
}
|
|
49
|
-
|
|
50
|
-
if (c.openingElement.
|
|
70
|
+
|
|
71
|
+
if (componentsWithText.includes(c.openingElement.name.name)) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// HINT: role & aria-label を同時に設定されている場合は許可
|
|
76
|
+
let existRole = false
|
|
77
|
+
let existAriaLabel = false
|
|
78
|
+
const result = c.openingElement.attributes.reduce((prev, a) => {
|
|
79
|
+
existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
|
|
80
|
+
existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
|
|
81
|
+
|
|
82
|
+
if (prev) {
|
|
83
|
+
return prev
|
|
84
|
+
}
|
|
85
|
+
|
|
51
86
|
if (!['visuallyHiddenText', 'alt'].includes(a.name.name)) {
|
|
52
|
-
return
|
|
87
|
+
return prev
|
|
53
88
|
}
|
|
54
89
|
|
|
55
|
-
return (!!a.value.value || a.value.type === 'JSXExpressionContainer')
|
|
56
|
-
})
|
|
90
|
+
return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
|
|
91
|
+
}, null)
|
|
92
|
+
|
|
93
|
+
if (result || (existRole && existAriaLabel)) {
|
|
57
94
|
return true
|
|
58
95
|
}
|
|
59
96
|
|
|
@@ -80,4 +117,4 @@ module.exports = {
|
|
|
80
117
|
}
|
|
81
118
|
},
|
|
82
119
|
}
|
|
83
|
-
module.exports.schema =
|
|
120
|
+
module.exports.schema = SCHEMA
|
|
@@ -2,7 +2,10 @@ const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
|
2
2
|
|
|
3
3
|
const EXPECTED_NAMES = {
|
|
4
4
|
'(i|I)nput$': 'Input$',
|
|
5
|
+
'SearchInput$': 'SearchInput$',
|
|
5
6
|
'(t|T)extarea$': 'Textarea$',
|
|
7
|
+
'FieldSet$': 'FieldSet$',
|
|
8
|
+
'ComboBox$': 'ComboBox$',
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
module.exports = {
|
|
@@ -18,26 +21,40 @@ module.exports = {
|
|
|
18
21
|
return {
|
|
19
22
|
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
20
23
|
JSXOpeningElement: (node) => {
|
|
21
|
-
|
|
24
|
+
const name = node.name.name
|
|
25
|
+
|
|
26
|
+
if (!name) {
|
|
22
27
|
return
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!match) {
|
|
30
|
+
if (!name.match(/((i|I)nput|(t|T)extarea|FieldSet|ComboBox)$/)) {
|
|
28
31
|
return
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const placeholder = node.attributes.find((a) => a.name?.name === 'placeholder')
|
|
32
35
|
|
|
33
36
|
if (placeholder) {
|
|
34
|
-
|
|
37
|
+
if (name.match(/SearchInput$/)) {
|
|
38
|
+
const tooltipMessage = node.attributes.find((a) => a.name?.name === 'tooltipMessage')
|
|
39
|
+
|
|
40
|
+
if (!tooltipMessage) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: placeholder,
|
|
43
|
+
messageId: 'a11y-prohibit-input-placeholder',
|
|
44
|
+
data: {
|
|
45
|
+
message: `${name} にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<${name} tooltipMessage="ヒント" />', '<${name} tooltipMessage={hint} placeholder={hint} />')`,
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
context.report({
|
|
35
51
|
node: placeholder,
|
|
36
52
|
messageId: 'a11y-prohibit-input-placeholder',
|
|
37
53
|
data: {
|
|
38
|
-
message:
|
|
54
|
+
message: `${name} にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
|
|
39
55
|
},
|
|
40
56
|
})
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
},
|
|
43
60
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# smarthr/require-declaration
|
|
2
|
+
|
|
3
|
+
- 対象ファイルに宣言してほしい、変数・関数・class・型などを定義するルールです
|
|
4
|
+
- コードの規約などを決める際に便利です
|
|
5
|
+
- import, exportを強制したい場合は以下を利用してください
|
|
6
|
+
- https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-import
|
|
7
|
+
- https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-export
|
|
8
|
+
|
|
9
|
+
## rules
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
{
|
|
13
|
+
rules: {
|
|
14
|
+
'smarthr/require-declaration': [
|
|
15
|
+
'error', // 'warn', 'off'
|
|
16
|
+
{
|
|
17
|
+
'\crews\/index\/slices\/': { // パスに合致する正規表現でファイル指定
|
|
18
|
+
'ActionCreatorsProps': { // 定義してほしい名称
|
|
19
|
+
type: 'type', // 定義したい種類 type | const | let | class | function | arrow-function
|
|
20
|
+
use: ['payload', 'AnyAction'], // 定義対象の内部で利用を強制したいものを指定する
|
|
21
|
+
reportMessage: `'type ActionCreatorsProps = { xxxYyy: (payload: XxxProps) => AnyAction }' というフォーマットで型を作成してください` // 省略可能
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
'^(?=.*\/slices\/[a-zA-Z0-9]+\.ts)(?!.*(\/modules\/|mock\.)).*$': { // slices以下のファイルで、かつフルパスにmodulesや `mock.` を含まないもの
|
|
25
|
+
'slice': {
|
|
26
|
+
type: 'const',
|
|
27
|
+
use: ['createSlice', 'path', 'initialState', 'reducers'],
|
|
28
|
+
reportMessage: `'const slice = createSlice({ name: path.xxxx, initialState, reducers })' というフォーマットでsliceを作成してください`
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## ❌ Incorrect
|
|
38
|
+
|
|
39
|
+
```jsx
|
|
40
|
+
// crews/index/slice/index.ts
|
|
41
|
+
|
|
42
|
+
type Actions = {
|
|
43
|
+
hoge: (payload: hogeProps) => any
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## ✅ Correct
|
|
49
|
+
|
|
50
|
+
```jsx
|
|
51
|
+
// crews/index/slice/index.ts
|
|
52
|
+
|
|
53
|
+
type ActionCreatorsProps = {
|
|
54
|
+
hoge: (payload: hogeProps) => AnyAction
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const slice = createSlice({
|
|
58
|
+
name: path.hoge,
|
|
59
|
+
initialState,
|
|
60
|
+
reducers,
|
|
61
|
+
})
|
|
62
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const SCHEMA = [
|
|
2
|
+
{
|
|
3
|
+
type: 'object',
|
|
4
|
+
patternProperties: {
|
|
5
|
+
'.+': {
|
|
6
|
+
type: 'object',
|
|
7
|
+
patternProperties: {
|
|
8
|
+
'.+': {
|
|
9
|
+
type: 'object',
|
|
10
|
+
required: ['type'],
|
|
11
|
+
properties: {
|
|
12
|
+
type: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
pattern: '^(type|const|let|class|function|arrow-function)$',
|
|
15
|
+
},
|
|
16
|
+
use: {
|
|
17
|
+
type: 'array',
|
|
18
|
+
items: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
reportMessage: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: true,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
additionalProperties: true,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const find = (type, ds, rd) => ds.find((d) => d.type === type && d.id.name === rd)
|
|
37
|
+
const codeSeparator = '[^a-zA-Z0-1_$]'
|
|
38
|
+
const useRegex = (use) => {
|
|
39
|
+
const actualUse = use.replaceAll('.', '\.')
|
|
40
|
+
return new RegExp(`((${codeSeparator}(${actualUse})${codeSeparator})|(^(${actualUse})${codeSeparator})|${codeSeparator}(${actualUse})$)`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
meta: {
|
|
45
|
+
type: 'suggestion',
|
|
46
|
+
messages: {
|
|
47
|
+
'require-declaration': '{{ message }}',
|
|
48
|
+
},
|
|
49
|
+
schema: SCHEMA,
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
const options = context.options[0]
|
|
53
|
+
const filename = context.getFilename()
|
|
54
|
+
const targetPathRegexs = Object.keys(options)
|
|
55
|
+
const targetRequires = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
|
|
56
|
+
|
|
57
|
+
if (targetRequires.length === 0) {
|
|
58
|
+
return {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
Program: (node) => {
|
|
63
|
+
const declarations = node.body.filter((i) => i.type.match(/Declaration$/)).map((d) => d.declaration || d)
|
|
64
|
+
|
|
65
|
+
if (declarations.length === 0) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
targetRequires.forEach((requireKey) => {
|
|
70
|
+
const option = options[requireKey]
|
|
71
|
+
|
|
72
|
+
Object.keys(option).forEach((requireDeclaration) => {
|
|
73
|
+
const localOption = option[requireDeclaration]
|
|
74
|
+
let hit
|
|
75
|
+
|
|
76
|
+
switch (localOption.type) {
|
|
77
|
+
case 'type':
|
|
78
|
+
hit = find('TSTypeAliasDeclaration', declarations, requireDeclaration)
|
|
79
|
+
break
|
|
80
|
+
case 'class':
|
|
81
|
+
hit = find('ClassDeclaration', declarations, requireDeclaration)
|
|
82
|
+
break
|
|
83
|
+
case 'function':
|
|
84
|
+
hit = find('FunctionDeclaration', declarations, requireDeclaration)
|
|
85
|
+
break
|
|
86
|
+
case 'const':
|
|
87
|
+
case 'let':
|
|
88
|
+
hit = declarations.find((d) => d.type === 'VariableDeclaration' && d.kind === localOption.type && d.declarations.some((dd) => {
|
|
89
|
+
if (dd.id.name) {
|
|
90
|
+
return dd.id.name === requireDeclaration
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// const { hoge } = fuga パターン
|
|
94
|
+
return dd.id.properties.some((p) => p.key.name === requireDeclaration)
|
|
95
|
+
}))
|
|
96
|
+
break
|
|
97
|
+
case 'arrow-function':
|
|
98
|
+
hit = declarations.find((d) => d.type === 'VariableDeclaration' && d.declarations.some((dd) => dd.id.name === requireDeclaration && dd.init.type === 'ArrowFunctionExpression'))
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!hit) {
|
|
103
|
+
context.report({
|
|
104
|
+
node,
|
|
105
|
+
messageId: 'require-declaration',
|
|
106
|
+
data: {
|
|
107
|
+
message: localOption.reportMessage || `${localOption.type} ${requireDeclaration}が宣言されていません`,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
} else if (localOption.use) {
|
|
111
|
+
const code = context.getSourceCode().getText(hit)
|
|
112
|
+
let reported = false
|
|
113
|
+
|
|
114
|
+
localOption.use.forEach((u) => {
|
|
115
|
+
if (!code.match(useRegex(u)) && (!localOption.reportMessage || !reported)) {
|
|
116
|
+
context.report({
|
|
117
|
+
node: hit,
|
|
118
|
+
messageId: 'require-declaration',
|
|
119
|
+
data: {
|
|
120
|
+
message: localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
reported = true
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports.schema = SCHEMA
|
|
@@ -96,6 +96,18 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
96
96
|
{
|
|
97
97
|
code: `<a><PrefixSmartHRLogo /></a>`,
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
code: `<a><>ほげ</></a>`,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
code: `<a><svg role="img" aria-label="hoge" /></a>`,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
code: `<a><AnyComponent /></a>`,
|
|
107
|
+
options: [{
|
|
108
|
+
componentsWithText: ['AnyComponent']
|
|
109
|
+
}],
|
|
110
|
+
},
|
|
99
111
|
],
|
|
100
112
|
invalid: [
|
|
101
113
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
|
|
@@ -151,5 +163,16 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
151
163
|
code: `<button><SmartHRLogoSuffix /></button>`,
|
|
152
164
|
errors: [{ message: defaultErrorMessage }]
|
|
153
165
|
},
|
|
166
|
+
{
|
|
167
|
+
code: `<a><div role="article" aria-label="hoge" /></a>`,
|
|
168
|
+
errors: [{ message: defaultErrorMessage }]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
code: `<a><AnyComponent /></a>`,
|
|
172
|
+
options: [{
|
|
173
|
+
componentsWithText: ['HogeComponent']
|
|
174
|
+
}],
|
|
175
|
+
errors: [{ message: defaultErrorMessage }]
|
|
176
|
+
},
|
|
154
177
|
]
|
|
155
178
|
})
|
|
@@ -22,10 +22,21 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
22
22
|
{ code: 'const HogeInput = styled(Input)``' },
|
|
23
23
|
{ code: 'const HogeInput = styled(StyledInput)``' },
|
|
24
24
|
{ code: 'const HogeTextarea = styled(Textarea)``' },
|
|
25
|
+
{ code: 'const hoge = styled.fieldset``' },
|
|
26
|
+
{ code: 'const HogeFieldSet = styled(FieldSet)``' },
|
|
27
|
+
{ code: 'const HogeComboBox = styled(ComboBox)``' },
|
|
28
|
+
{ code: 'const HogeSearchInput = styled(SearchInput)``' },
|
|
25
29
|
{ code: `<input />` },
|
|
26
30
|
{ code: `<textarea />` },
|
|
31
|
+
{ code: `<FieldSet />` },
|
|
32
|
+
{ code: `<ComboBox />` },
|
|
27
33
|
{ code: `<StyledInput />` },
|
|
28
34
|
{ code: `<HogeTextarea />` },
|
|
35
|
+
{ code: `<FugaFieldSet />` },
|
|
36
|
+
{ code: `<CustomComboBox />` },
|
|
37
|
+
{ code: `<SearchInput />` },
|
|
38
|
+
{ code: `<CustomSearchInput tooltipMessage="hoge" />` },
|
|
39
|
+
{ code: `<CustomSearchInput tooltipMessage="hoge" placeholder="fuga" />` },
|
|
29
40
|
],
|
|
30
41
|
invalid: [
|
|
31
42
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
|
|
@@ -33,9 +44,21 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
33
44
|
{ code: 'const Hoge = styled(StyledInput)``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
|
|
34
45
|
{ code: 'const Hoge = styled.textarea``', errors: [ { message: `Hogeを正規表現 "/Textarea$/" がmatchする名称に変更してください` } ] },
|
|
35
46
|
{ code: 'const Hoge = styled(StyledTextarea)``', errors: [ { message: `Hogeを正規表現 "/Textarea$/" がmatchする名称に変更してください` } ] },
|
|
36
|
-
{ code:
|
|
37
|
-
{ code:
|
|
38
|
-
{
|
|
39
|
-
|
|
47
|
+
{ code: 'const Hoge = styled(FieldSet)``', errors: [ { message: `Hogeを正規表現 "/FieldSet$/" がmatchする名称に変更してください` } ] },
|
|
48
|
+
{ code: 'const Hoge = styled(ComboBox)``', errors: [ { message: `Hogeを正規表現 "/ComboBox$/" がmatchする名称に変更してください` } ] },
|
|
49
|
+
{
|
|
50
|
+
code: 'const Hoge = styled(SearchInput)``',
|
|
51
|
+
errors: [
|
|
52
|
+
{ message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` },
|
|
53
|
+
{ message: `Hogeを正規表現 "/SearchInput$/" がmatchする名称に変更してください` },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{ code: `<input placeholder />`, errors: [ { message: `input にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><input /><Hint>ヒント</Hint></div>')` } ] },
|
|
57
|
+
{ code: `<textarea placeholder="hoge" />`, errors: [ { message: `textarea にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><textarea /><Hint>ヒント</Hint></div>')` } ] },
|
|
58
|
+
{ code: `<StyledInput placeholder={any} />`, errors: [ { message: `StyledInput にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><StyledInput /><Hint>ヒント</Hint></div>')` } ] },
|
|
59
|
+
{ code: `<HogeTextarea placeholder="any" />`, errors: [ { message: `HogeTextarea にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeTextarea /><Hint>ヒント</Hint></div>')` } ] },
|
|
60
|
+
{ code: `<HogeFieldSet placeholder="any" />`, errors: [ { message: `HogeFieldSet にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeFieldSet /><Hint>ヒント</Hint></div>')` } ] },
|
|
61
|
+
{ code: `<HogeComboBox placeholder="any" />`, errors: [ { message: `HogeComboBox にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeComboBox /><Hint>ヒント</Hint></div>')` } ] },
|
|
62
|
+
{ code: `<SearchInput placeholder="any" />`, errors: [ { message: `SearchInput にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<SearchInput tooltipMessage="ヒント" />', '<SearchInput tooltipMessage={hint} placeholder={hint} />')` } ] },
|
|
40
63
|
]
|
|
41
64
|
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const rule = require('../rules/require-declaration')
|
|
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 options = [
|
|
16
|
+
{
|
|
17
|
+
'^.+$': {
|
|
18
|
+
hoge: {
|
|
19
|
+
type: 'const',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
ruleTester.run('format-translate-component', rule, {
|
|
26
|
+
valid: [
|
|
27
|
+
{
|
|
28
|
+
code: 'const hoge = any',
|
|
29
|
+
filename: 'hoge.js',
|
|
30
|
+
options: [
|
|
31
|
+
{
|
|
32
|
+
'^.+$': {
|
|
33
|
+
hoge: {
|
|
34
|
+
type: 'const',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: 'const hoge = any',
|
|
42
|
+
filename: 'hoge.js',
|
|
43
|
+
options: [
|
|
44
|
+
{
|
|
45
|
+
'fuga\.': {
|
|
46
|
+
hoge: {
|
|
47
|
+
type: 'const',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
code: 'function abc(arg1) { return arg1 * 10 }',
|
|
55
|
+
filename: 'hoge.js',
|
|
56
|
+
options: [
|
|
57
|
+
{
|
|
58
|
+
'hoge\.': {
|
|
59
|
+
abc: {
|
|
60
|
+
type: 'function',
|
|
61
|
+
use: [ 'arg1' ]
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: 'const fuga = () => { return undefined }',
|
|
69
|
+
filename: 'hoge.js',
|
|
70
|
+
options: [
|
|
71
|
+
{
|
|
72
|
+
'hoge\.': {
|
|
73
|
+
fuga: {
|
|
74
|
+
type: 'arrow-function',
|
|
75
|
+
use: [ 'return undefined' ]
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
invalid: [
|
|
83
|
+
{
|
|
84
|
+
code: 'const hoge = any',
|
|
85
|
+
filename: 'hoge.js',
|
|
86
|
+
options: [
|
|
87
|
+
{
|
|
88
|
+
'^.+$': {
|
|
89
|
+
fuga: {
|
|
90
|
+
type: 'const',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
errors: [{ message: 'const fugaが宣言されていません' }],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
code: 'const hoge = any',
|
|
99
|
+
filename: 'hoge.js',
|
|
100
|
+
options: [
|
|
101
|
+
{
|
|
102
|
+
'^.+$': {
|
|
103
|
+
fuga: {
|
|
104
|
+
type: 'const',
|
|
105
|
+
reportMessage: 'fugaを定義しろ!',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
errors: [{ message: 'fugaを定義しろ!' }],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
code: 'const hoge = abc',
|
|
114
|
+
filename: 'hoge.js',
|
|
115
|
+
options: [
|
|
116
|
+
{
|
|
117
|
+
'^.+$': {
|
|
118
|
+
hoge: {
|
|
119
|
+
type: 'const',
|
|
120
|
+
use: ['fuga'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
errors: [{ message: 'const hoge では fuga を利用してください' }],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
code: 'let hoge = () => undefined',
|
|
129
|
+
filename: 'hoge.js',
|
|
130
|
+
options: [
|
|
131
|
+
{
|
|
132
|
+
'^.+$': {
|
|
133
|
+
hoge: {
|
|
134
|
+
type: 'arrow-function',
|
|
135
|
+
use: ['num', 'parseInt(num, 10)'],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
errors: [{ message: 'arrow-function hoge では num を利用してください' }, { message: 'arrow-function hoge では parseInt(num, 10) を利用してください' }],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
code: 'let hoge = () => undefined',
|
|
144
|
+
filename: 'hoge.js',
|
|
145
|
+
options: [
|
|
146
|
+
{
|
|
147
|
+
'^.+$': {
|
|
148
|
+
hoge: {
|
|
149
|
+
type: 'arrow-function',
|
|
150
|
+
use: ['num', 'temp', 'parseInt(num, 10)'],
|
|
151
|
+
reportMessage: 'hoge関数は `const hoge = (num) => { const temp = parseInt(num, 10); /* any code. */ }` のように定義してください'
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
errors: [{ message: 'hoge関数は `const hoge = (num) => { const temp = parseInt(num, 10); /* any code. */ }` のように定義してください' }],
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
})
|