eslint-plugin-smarthr 0.5.6 → 0.5.8
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 +16 -0
- package/README.md +2 -0
- package/package.json +3 -2
- package/rules/a11y-anchor-has-href-attribute/index.js +3 -0
- package/rules/a11y-clickable-element-has-text/index.js +3 -0
- package/rules/a11y-delegate-element-has-role-presentation/index.js +9 -1
- package/rules/a11y-form-control-in-form/index.js +3 -0
- package/rules/a11y-heading-in-sectioning-content/index.js +3 -0
- package/rules/a11y-image-has-alt-attribute/index.js +3 -0
- package/rules/a11y-input-has-name-attribute/index.js +3 -0
- package/rules/a11y-input-in-form-control/index.js +3 -0
- package/rules/a11y-numbered-text-within-ol/index.js +3 -0
- package/rules/a11y-prohibit-input-maxlength-attribute/README.md +35 -0
- package/rules/a11y-prohibit-input-maxlength-attribute/index.js +44 -0
- package/rules/a11y-prohibit-input-placeholder/index.js +3 -0
- package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +3 -0
- package/rules/a11y-replace-unreadable-symbol/README.md +38 -0
- package/rules/a11y-replace-unreadable-symbol/index.js +31 -0
- package/rules/a11y-trigger-has-button/index.js +3 -0
- package/rules/best-practice-for-button-element/index.js +3 -0
- package/rules/best-practice-for-date/index.js +3 -0
- package/rules/best-practice-for-layouts/index.js +11 -1
- package/rules/best-practice-for-remote-trigger-dialog/index.js +3 -0
- package/rules/format-import-path/index.js +3 -0
- package/rules/format-translate-component/index.js +3 -0
- package/rules/jsx-start-with-spread-attributes/index.js +3 -0
- package/rules/no-import-other-domain/index.js +3 -0
- package/rules/prohibit-export-array-type/index.js +3 -0
- package/rules/prohibit-file-name/index.js +3 -0
- package/rules/prohibit-import/index.js +3 -0
- package/rules/prohibit-path-within-template-literal/index.js +3 -0
- package/rules/require-barrel-import/index.js +3 -0
- package/rules/require-declaration/index.js +3 -0
- package/rules/require-export/index.js +3 -0
- package/rules/require-import/index.js +3 -0
- package/rules/trim-props/index.js +3 -0
- package/test/a11y-delegate-element-has-role-presantation.js +2 -0
- package/test/a11y-prohibit-input-maxlength-attribute.js +39 -0
- package/test/a11y-replace-unreadable-symbol.js +36 -0
- package/test/best-practice-for-layouts.js +3 -0
- package/tsconfig.json +12 -0
- package/types.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.5.8](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.7...v0.5.8) (2024-04-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-prohibit-input-maxlength-attribute ([#135](https://github.com/kufu/eslint-plugin-smarthr/issues/135)) ([9f9d010](https://github.com/kufu/eslint-plugin-smarthr/commit/9f9d010e819d936fe5f55556a8f65e6485c552ce))
|
|
11
|
+
* best-practice-for-layouts に <Stack align="center"> を <Center> に置き換えることを促すチェックを追加 ([#133](https://github.com/kufu/eslint-plugin-smarthr/issues/133)) ([5835530](https://github.com/kufu/eslint-plugin-smarthr/commit/58355308bf9a5d18d2d731e699c54806af969ed9))
|
|
12
|
+
|
|
13
|
+
### [0.5.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.6...v0.5.7) (2024-04-01)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* a11y-delegate-element-has-role-presentationでas, forwardedAsにform, fieldsetが指定されている場合、インタファクティブな要素として扱うように修正 ([#132](https://github.com/kufu/eslint-plugin-smarthr/issues/132)) ([3d629fa](https://github.com/kufu/eslint-plugin-smarthr/commit/3d629fa73e7346c229831a0075478fcbfe582de1))
|
|
19
|
+
* a11y-replace-unreadable-symbol ([#128](https://github.com/kufu/eslint-plugin-smarthr/issues/128)) ([9410ff9](https://github.com/kufu/eslint-plugin-smarthr/commit/9410ff9ad9ed5a0d18945e3567a1fee8c056f822))
|
|
20
|
+
|
|
5
21
|
### [0.5.6](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.5...v0.5.6) (2024-03-29)
|
|
6
22
|
|
|
7
23
|
|
package/README.md
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
- [a11y-input-has-name-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-has-name-attribute)
|
|
10
10
|
- [a11y-input-in-form-control](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-in-form-control)
|
|
11
11
|
- [a11y-numbered-text-within-ol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-numbered-text-within-ol)
|
|
12
|
+
- [a11y-prohibit-input-maxlength-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-maxlength-attribute)
|
|
12
13
|
- [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
|
|
13
14
|
- [a11y-prohibit-useless-sectioning-fragment](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-useless-sectioning-fragment)
|
|
15
|
+
- [a11y-replace-unreadable-symbol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-replace-unreadable-symbol)
|
|
14
16
|
- [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
|
|
15
17
|
- [best-practice-for-button-element](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-button-element)
|
|
16
18
|
- [best-practice-for-date](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-date)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"eslint": "^8.8.0",
|
|
29
29
|
"jest": "^27.4.7",
|
|
30
|
-
"standard-version": "^9.3.2"
|
|
30
|
+
"standard-version": "^9.3.2",
|
|
31
|
+
"typescript-eslint": "^7.5.0"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"eslint": "^7 || ^8"
|
|
@@ -44,6 +44,9 @@ const message = `a, buttonなどのクリッカブルな要素内にはテキス
|
|
|
44
44
|
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください
|
|
45
45
|
- クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
49
|
+
*/
|
|
47
50
|
module.exports = {
|
|
48
51
|
meta: {
|
|
49
52
|
type: 'problem',
|
|
@@ -43,6 +43,8 @@ const INTERACTIVE_COMPONENT_NAMES = Object.keys(EXPECTED_NAMES).join('|')
|
|
|
43
43
|
const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/
|
|
44
44
|
const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/
|
|
45
45
|
const INTERACTIVE_NODE_TYPE_REGEX = /^(JSXElement|JSXExpressionContainer|ConditionalExpression)$/
|
|
46
|
+
const AS_REGEX = /^(as|forwardedAs)$/
|
|
47
|
+
const AS_VALUE_REGEX = /^(form|fieldset)$/
|
|
46
48
|
|
|
47
49
|
const messageNonInteractiveEventHandler = (nodeName, interactiveComponentRegex, onAttrs) => {
|
|
48
50
|
const onAttrsText = onAttrs.join(', ')
|
|
@@ -79,6 +81,9 @@ const SCHEMA = [
|
|
|
79
81
|
}
|
|
80
82
|
]
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
86
|
+
*/
|
|
82
87
|
module.exports = {
|
|
83
88
|
meta: {
|
|
84
89
|
type: 'problem',
|
|
@@ -122,12 +127,15 @@ module.exports = {
|
|
|
122
127
|
let onAttrs = []
|
|
123
128
|
let roleMean = undefined
|
|
124
129
|
let isRolePresentation = false
|
|
130
|
+
let isAsInteractive = false
|
|
125
131
|
|
|
126
132
|
node.attributes.forEach((a) => {
|
|
127
133
|
const aName = a.name?.name || ''
|
|
128
134
|
|
|
129
135
|
if (aName.match(INTERACTIVE_ON_REGEX)) {
|
|
130
136
|
onAttrs.push(aName)
|
|
137
|
+
} else if (AS_REGEX.test(aName) && AS_VALUE_REGEX.test(a.value?.value || '')) {
|
|
138
|
+
isAsInteractive = true
|
|
131
139
|
} else if (aName === 'role') {
|
|
132
140
|
const v = a.value?.value || ''
|
|
133
141
|
|
|
@@ -140,7 +148,7 @@ module.exports = {
|
|
|
140
148
|
}
|
|
141
149
|
})
|
|
142
150
|
|
|
143
|
-
if (nodeName.match(interactiveComponentRegex)) {
|
|
151
|
+
if (isAsInteractive || nodeName.match(interactiveComponentRegex)) {
|
|
144
152
|
if (isRolePresentation) {
|
|
145
153
|
context.report({
|
|
146
154
|
node,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# smarthr/a11y-prohibit-input-maxlength-attribute
|
|
2
|
+
|
|
3
|
+
- input, textarea 要素に maxLength 属性を設定することを禁止するルールです
|
|
4
|
+
- maxLength属性がついた要素に、テキストをペーストすると、maxLength属性の値を超えた範囲が意図せず切り捨てられてしまう場合があります。
|
|
5
|
+
- maxLength 属性ではなく、pattern 属性と title 属性を組み合わせて form 要素でラップすることで、入力時でなく、submit 時にバリデーションすることができます。
|
|
6
|
+
|
|
7
|
+
## rules
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
{
|
|
11
|
+
rules: {
|
|
12
|
+
'smarthr/a11y-prohibit-input-maxlength-attribute': [
|
|
13
|
+
'error', // 'warn', 'off'
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## ❌ Incorrect
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
<input maxLength={30} />
|
|
23
|
+
<XxxInput maxLength={40} />
|
|
24
|
+
<textarea maxLength={50} />
|
|
25
|
+
<XxxTextarea maxLength={60} />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## ✅ Correct
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
<input />
|
|
32
|
+
<XxxInput />
|
|
33
|
+
<textarea />
|
|
34
|
+
<XxxTextarea />
|
|
35
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
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('|')})`)
|
|
11
|
+
|
|
12
|
+
const SCHEMA = []
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
16
|
+
*/
|
|
17
|
+
module.exports = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'problem',
|
|
20
|
+
schema: SCHEMA,
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
return {
|
|
24
|
+
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
25
|
+
JSXOpeningElement: (node) => {
|
|
26
|
+
if (node.name.type === 'JSXIdentifier' && INPUT_COMPONENT_NAMES.test(node.name.name)) {
|
|
27
|
+
const checkHasMaxLength = (attr) => attr.name?.name === 'maxLength'
|
|
28
|
+
const maxLengthAttr = node.attributes.find(checkHasMaxLength)
|
|
29
|
+
if (maxLengthAttr) {
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
message: `${node.name.name}にmaxLength属性を設定しないでください。
|
|
33
|
+
- maxLength属性がついた要素に、テキストをペーストすると、maxLength属性の値を超えた範囲が意図せず切り捨てられてしまう場合があります
|
|
34
|
+
- 以下のいずれかの方法で修正をおこなってください
|
|
35
|
+
- 方法1: pattern属性とtitle属性を組み合わせ、form要素でラップする
|
|
36
|
+
- 方法2: JavaScriptを用いたバリデーションを実装する`,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
module.exports.schema = SCHEMA
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# smarthr/a11y-replace-unreadable-symbol
|
|
2
|
+
|
|
3
|
+
- 一部記号はスクリーンリーダーで読み上げられない、もしくは記号名そのままで読み上げられてしまい、意図が正しく伝えられない場合があります
|
|
4
|
+
- それらの記号を適切に読み上げられるコンポーネントに置き換えることを促すルールです
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/a11y-replace-unreadable-symbol': 'error', // 'warn', 'off',
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## ❌ Incorrect
|
|
17
|
+
|
|
18
|
+
```jsx
|
|
19
|
+
<>XXXX年YY月ZZ日 〜 XXXX年YY月ZZ日</>
|
|
20
|
+
// スクリーンリーダーは "XXXX年YY月ZZ日XXXX年YY月ZZ日" と読み上げる場合があります
|
|
21
|
+
|
|
22
|
+
<p>選択できる数値の範囲は 0 ~ 9999 です</p>
|
|
23
|
+
// スクリーンリーダーは "選択できる数値の範囲は09999です" と読み上げる場合があります
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## ✅ Correct
|
|
27
|
+
|
|
28
|
+
```jsx
|
|
29
|
+
//
|
|
30
|
+
<>XXXX年YY月ZZ日 <RangeSeparator /> XXXX年YY月ZZ日</>
|
|
31
|
+
// スクリーンリーダーは "XXXX年YY月ZZ日からXXXX年YY月ZZ日" と読み上げます
|
|
32
|
+
|
|
33
|
+
<p>選択できる数値の範囲は 0 <RangeSeparator /> 9999 です</p>
|
|
34
|
+
// スクリーンリーダーは "選択できる数値の範囲は0から9999です" と読み上げます
|
|
35
|
+
|
|
36
|
+
<p>入力できる記号は <RangeSeparator decorators={{ text: '~', visuallyHiddenText: '半角チルダ' }} /> です</p>
|
|
37
|
+
// スクリーンリーダーは "入力できる記号は半角チルダです" と読み上げます
|
|
38
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const TILDE_REGEX = /([~〜])/
|
|
2
|
+
|
|
3
|
+
const SCHEMA = []
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
7
|
+
*/
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
schema: SCHEMA,
|
|
12
|
+
},
|
|
13
|
+
create(context) {
|
|
14
|
+
return {
|
|
15
|
+
JSXText: (node) => {
|
|
16
|
+
const matcher = node.value.match(TILDE_REGEX)
|
|
17
|
+
|
|
18
|
+
if (matcher) {
|
|
19
|
+
context.report({
|
|
20
|
+
node,
|
|
21
|
+
message: `"${matcher[1]}"はスクリーンリーダーが正しく読み上げることができない場合があるため、smarthr-ui/RangeSeparatorに置き換えてください。
|
|
22
|
+
- エラー表示されている行に"${matcher[1]}"が存在しない場合、改行文字を含む関係で行番号がずれている場合があります。数行下の範囲を確認してください
|
|
23
|
+
- smarthr-ui/RangeSeparatorに置き換えることでスクリーンリーダーが "から" と読み上げることができます
|
|
24
|
+
- 前後の文脈などで "から" と読まれることが不適切な場合 \`<RangeSeparator decorators={{ visuallyHiddenText: () => "ANY" }} />\` のようにdecoratorsを指定してください`,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
module.exports.schema = SCHEMA
|
|
@@ -15,6 +15,9 @@ const filterFalsyJSXText = (cs) => cs.filter((c) => (
|
|
|
15
15
|
!(c.type === 'JSXText' && c.value.match(/^\s*\n+\s*$/))
|
|
16
16
|
))
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
20
|
+
*/
|
|
18
21
|
module.exports = {
|
|
19
22
|
meta: {
|
|
20
23
|
type: 'problem',
|
|
@@ -25,6 +25,7 @@ const checkFalsyJSXText = (c) => (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
const findJustifyAttr = (a) => a.name?.name === 'justify'
|
|
28
|
+
const findAlignAttr = (a) => a.name?.name === 'align'
|
|
28
29
|
|
|
29
30
|
const searchChildren = (node) => {
|
|
30
31
|
if (
|
|
@@ -55,6 +56,9 @@ const searchChildren = (node) => {
|
|
|
55
56
|
|
|
56
57
|
const SCHEMA = []
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
61
|
+
*/
|
|
58
62
|
module.exports = {
|
|
59
63
|
meta: {
|
|
60
64
|
type: 'problem',
|
|
@@ -80,11 +84,17 @@ module.exports = {
|
|
|
80
84
|
return
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
const alignAttr = layoutType === 'Stack' ? node.attributes.find(findAlignAttr) : null
|
|
88
|
+
|
|
89
|
+
if (alignAttr && FLEX_END_REGEX.test(alignAttr.value.value)) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
if (searchChildren(children[0])) {
|
|
84
94
|
context.report({
|
|
85
95
|
node,
|
|
86
96
|
message:
|
|
87
|
-
justifyAttr && justifyAttr.value.value === 'center'
|
|
97
|
+
(justifyAttr && justifyAttr.value.value === 'center' || alignAttr && alignAttr.value.value === 'center')
|
|
88
98
|
? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください`
|
|
89
99
|
: `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。
|
|
90
100
|
- styleを確認し、div・spanなど、別要素でマークアップし直すか、${nodeName}を削除してください
|
|
@@ -8,6 +8,9 @@ const EXPECTED_NAMES = {
|
|
|
8
8
|
const REGEX_REMOTE_TRIGGER_DIALOG = /RemoteTrigger(Action|Message|Modeless)Dialog$/
|
|
9
9
|
const REGEX_REMOTE_DIALOG_TRIGGER = /RemoteDialogTrigger$/
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
13
|
+
*/
|
|
11
14
|
module.exports = {
|
|
12
15
|
meta: {
|
|
13
16
|
type: 'suggestion',
|
|
@@ -79,6 +79,9 @@ const calculateRelativeImportPath = ({ importPath, filteredDirs, filteredPaths }
|
|
|
79
79
|
return `${filteredDirs.length === 0 ? './' : [...Array(filteredDirs.length)].reduce((prev) => `${prev}../`, '')}${filteredPaths.join('/')}`.replace(/^(.+?)\/$/, '$1')
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
84
|
+
*/
|
|
82
85
|
module.exports = {
|
|
83
86
|
meta: {
|
|
84
87
|
type: 'suggestion',
|
|
@@ -32,6 +32,9 @@ const SCHEMA = [{
|
|
|
32
32
|
|
|
33
33
|
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください`
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
37
|
+
*/
|
|
35
38
|
module.exports = {
|
|
36
39
|
meta: {
|
|
37
40
|
type: 'suggestion',
|
|
@@ -83,6 +83,9 @@ const calculateReplacedImportPath = (source) => {
|
|
|
83
83
|
const pickImportedName = (s) => s.imported?.name
|
|
84
84
|
const findExistsSync = (p) => fs.existsSync(p)
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
88
|
+
*/
|
|
86
89
|
module.exports = {
|
|
87
90
|
meta: {
|
|
88
91
|
type: 'suggestion',
|
|
@@ -40,6 +40,9 @@ const useRegex = (use) => {
|
|
|
40
40
|
return new RegExp(`((${codeSeparator}(${actualUse})${codeSeparator})|(^(${actualUse})${codeSeparator})|${codeSeparator}(${actualUse})$)`)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
45
|
+
*/
|
|
43
46
|
module.exports = {
|
|
44
47
|
meta: {
|
|
45
48
|
type: 'suggestion',
|
|
@@ -32,6 +32,9 @@ const SCHEMA = [{
|
|
|
32
32
|
|
|
33
33
|
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} をimportしてください`
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
37
|
+
*/
|
|
35
38
|
module.exports = {
|
|
36
39
|
meta: {
|
|
37
40
|
type: 'suggestion',
|
|
@@ -56,6 +56,8 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
|
|
|
56
56
|
{ code: '<Wrapper onClick={any} role="presentation">{any1 ? (any2 ? <HogeLink /> : null) : null}</Wrapper>' },
|
|
57
57
|
{ code: '<Wrapper onClick={any} role="presentation">{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>' },
|
|
58
58
|
{ code: '<Wrapper onClick={any} role="slider">Hoge</Wrapper>' },
|
|
59
|
+
{ code: '<Wrapper onSubmit={any} as="form" />' },
|
|
60
|
+
{ code: '<Wrapper onSubmit={any} forwardedAs="fieldset">any</Wrapper>' },
|
|
59
61
|
],
|
|
60
62
|
invalid: [
|
|
61
63
|
{ code: '<Input role="presentation" />', errors: [ { message: messageInteractiveHasRolePresentation('Input') } ] },
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const rule = require('../rules/a11y-prohibit-input-maxlength-attribute')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 12,
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
experimentalObjectRestSpread: true,
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const expectedErrorMessage = (elName) => `${elName}にmaxLength属性を設定しないでください。
|
|
16
|
+
- maxLength属性がついた要素に、テキストをペーストすると、maxLength属性の値を超えた範囲が意図せず切り捨てられてしまう場合があります
|
|
17
|
+
- 以下のいずれかの方法で修正をおこなってください
|
|
18
|
+
- 方法1: pattern属性とtitle属性を組み合わせ、form要素でラップする
|
|
19
|
+
- 方法2: JavaScriptを用いたバリデーションを実装する`
|
|
20
|
+
|
|
21
|
+
ruleTester.run('a11y-prohibit-input-maxlength-attribute', rule, {
|
|
22
|
+
valid: [
|
|
23
|
+
{ code: `<input />` },
|
|
24
|
+
{ code: `<Input type="text" />` },
|
|
25
|
+
{ code: `<HogeInput value="hoge" />` },
|
|
26
|
+
{ code: `<textarea>hoge</textarea>` },
|
|
27
|
+
{ code: `<Textarea type="text" />` },
|
|
28
|
+
{ code: `<HogeTextarea value="hoge" />` },
|
|
29
|
+
{ code: `<><input /></>`}
|
|
30
|
+
],
|
|
31
|
+
invalid: [
|
|
32
|
+
{ code: `<input maxLength={30} />`, errors: [{ message: expectedErrorMessage('input') }] },
|
|
33
|
+
{ code: `<Input type="text" maxLength={40} />`, errors: [{ message: expectedErrorMessage('Input') }] },
|
|
34
|
+
{ code: `<HogeInput maxLength value="hoge" />`, errors: [{ message: expectedErrorMessage('HogeInput') }] },
|
|
35
|
+
{ code: `<textarea maxLength={50}>hoge</textarea>`, errors: [{ message: expectedErrorMessage('textarea') }]},
|
|
36
|
+
{ code: `<Textarea type="text" maxLength={60} />`, errors: [{ message: expectedErrorMessage('Textarea') }]},
|
|
37
|
+
{ code: `<HogeTextarea maxLength={70} value="hoge" />`, errors: [{ message: expectedErrorMessage('HogeTextarea') }]}
|
|
38
|
+
]
|
|
39
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const rule = require('../rules/a11y-replace-unreadable-symbol')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const generateErrorText = (symbol, replaced, read) => `"${symbol}"はスクリーンリーダーが正しく読み上げることができない場合があるため、smarthr-ui/${replaced}に置き換えてください。
|
|
5
|
+
- エラー表示されている行に"${symbol}"が存在しない場合、改行文字を含む関係で行番号がずれている場合があります。数行下の範囲を確認してください
|
|
6
|
+
- smarthr-ui/${replaced}に置き換えることでスクリーンリーダーが "${read}" と読み上げることができます
|
|
7
|
+
- 前後の文脈などで "${read}" と読まれることが不適切な場合 \`<RangeSeparator decorators={{ visuallyHiddenText: () => "ANY" }} />\` のようにdecoratorsを指定してください`
|
|
8
|
+
|
|
9
|
+
const ruleTester = new RuleTester({
|
|
10
|
+
parserOptions: {
|
|
11
|
+
ecmaVersion: 2018,
|
|
12
|
+
ecmaFeatures: {
|
|
13
|
+
experimentalObjectRestSpread: true,
|
|
14
|
+
jsx: true,
|
|
15
|
+
},
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
ruleTester.run('a11y-replace-unreadable-symbol', rule, {
|
|
21
|
+
valid: [
|
|
22
|
+
{ code: `<>ほげふが</>` },
|
|
23
|
+
{ code: `<RangeSeparator />` },
|
|
24
|
+
{ code: `<p>ほげ<RangeSeparator />ふが</p>` },
|
|
25
|
+
{ code: `<p>
|
|
26
|
+
ほげ
|
|
27
|
+
<RangeSeparator />
|
|
28
|
+
ふが
|
|
29
|
+
</p>` },
|
|
30
|
+
],
|
|
31
|
+
invalid: [
|
|
32
|
+
{ code: `<>~</>`, errors: [ { message: generateErrorText('~', 'RangeSeparator', 'から') } ] },
|
|
33
|
+
{ code: `<>ほげ~ふが</>`, errors: [ { message: generateErrorText('~', 'RangeSeparator', 'から') } ] },
|
|
34
|
+
{ code: `<p>ほげ〜ふが</p>`, errors: [ { message: generateErrorText('〜', 'RangeSeparator', 'から') } ] },
|
|
35
|
+
]
|
|
36
|
+
})
|
|
@@ -55,6 +55,8 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
55
55
|
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
|
|
56
56
|
{ code: `<Cluster justify="flex-end">{a}</Cluster>` },
|
|
57
57
|
{ code: `<HogeCluster justify="end">{a}</HogeCluster>` },
|
|
58
|
+
{ code: `<Stack align="flex-end">{a}</Stack>` },
|
|
59
|
+
{ code: `<HogeStack align="end">{a}</HogeStack>` },
|
|
58
60
|
],
|
|
59
61
|
invalid: [
|
|
60
62
|
{ code: `<Stack><Hoge /></Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
|
|
@@ -82,6 +84,7 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
82
84
|
{ code: `<AnyCluster>{a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
|
|
83
85
|
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
|
|
84
86
|
{ code: `<HogeCluster justify="center">{a}</HogeCluster>`, errors: [ { message: 'HogeCluster は smarthr-ui/Cluster ではなく smarthr-ui/Center でマークアップしてください' } ] },
|
|
87
|
+
{ code: `<HogeStack align="center">{a}</HogeStack>`, errors: [ { message: 'HogeStack は smarthr-ui/Stack ではなく smarthr-ui/Center でマークアップしてください' } ] },
|
|
85
88
|
]
|
|
86
89
|
})
|
|
87
90
|
|
package/tsconfig.json
ADDED
package/types.js
ADDED