eslint-plugin-smarthr 6.2.3 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/package.json +4 -4
- package/rules/a11y-anchor-has-href-attribute/README.md +2 -2
- package/rules/a11y-anchor-has-href-attribute/index.js +1 -1
- package/rules/a11y-heading-in-sectioning-content/index.js +4 -3
- package/rules/a11y-help-link-with-support-href/README.md +55 -5
- package/rules/a11y-image-has-alt-attribute/README.md +57 -22
- package/rules/a11y-input-has-name-attribute/README.md +33 -24
- package/rules/a11y-input-in-form-control/README.md +52 -28
- package/rules/a11y-numbered-text-within-ol/README.md +35 -4
- package/rules/best-practice-for-layouts/index.js +4 -3
- package/rules/best-practice-for-unnesessary-early-return/README.md +86 -1
- package/rules/best-practice-for-unnesessary-early-return/index.js +31 -12
- package/rules/trim-props/index.js +10 -21
- package/test/a11y-heading-in-sectioning-content.js +3 -2
- package/test/best-practice-for-layouts.js +11 -11
- package/test/best-practice-for-unnesessary-early-return.js +43 -1
- package/test/trim-props.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
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
|
+
## [6.4.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.3.0...eslint-plugin-smarthr-v6.4.0) (2026-02-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **best-practice-for-unnesessary-early-return:** 早期return直後に単独のifが存在する場合、一つのifにまとめるように促すチェックを追加 ([#1082](https://github.com/kufu/tamatebako/issues/1082)) ([e7a6d70](https://github.com/kufu/tamatebako/commit/e7a6d709a5e633c0fbb65f9f0331746cfd752124))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **best-practice-for-layouts:** flatMapメソッドの場合も正しくチェックできるように修正する ([#1079](https://github.com/kufu/tamatebako/issues/1079)) ([838af61](https://github.com/kufu/tamatebako/commit/838af61523370a321fc95272aca742072a04c235))
|
|
16
|
+
|
|
17
|
+
## [6.3.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.2.3...eslint-plugin-smarthr-v6.3.0) (2026-02-06)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **a11y-heading-in-sectioning-content:** tag属性がunrecommendedTagにrenameされたためチェック対象を調整 ([#1066](https://github.com/kufu/tamatebako/issues/1066)) ([5b524f3](https://github.com/kufu/tamatebako/commit/5b524f3a712e48953ec6aad18028f3f19d6b86ca))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* selectorでhas(>...)をやめ、直接属性で絞り込むように調整 ([#1069](https://github.com/kufu/tamatebako/issues/1069)) ([540426f](https://github.com/kufu/tamatebako/commit/540426f0ea90ba9b8cd67242067dcc8e4c855b5f))
|
|
28
|
+
|
|
5
29
|
## [6.2.3](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.2.2...eslint-plugin-smarthr-v6.2.3) (2026-02-05)
|
|
6
30
|
|
|
7
31
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
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": ">=22.
|
|
9
|
+
"node": ">=22.22.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.54.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": "dbca48a75081a64d1f28acaccc411f0886569ca0"
|
|
41
41
|
}
|
|
@@ -39,7 +39,7 @@ next/link コンポーネント直下のa要素にhref属性が指定されて
|
|
|
39
39
|
<Link href={hoge}><a>any</a></Link>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
## spread-attributesが設定されているなら許容したい場合
|
|
43
43
|
|
|
44
44
|
下記の様にspread attributesが設定されていれば、href属性が設定されている扱いにしたい場合、lintのoptionとして `checkType` に `allow-spread-attributes` を設定してください。
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ next/link コンポーネント直下のa要素にhref属性が指定されて
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
便利な設定ではありますが、**href属性が実際に設定されているかは判定出来ていないため、チェック漏れが発生する可能性があります。**
|
|
53
|
-
|
|
53
|
+
設定する場合は慎重に検討してください。
|
|
54
54
|
|
|
55
55
|
## rules
|
|
56
56
|
|
|
@@ -46,7 +46,7 @@ const OPTION = (() => {
|
|
|
46
46
|
const ANCHOR_ELEMENT = 'JSXOpeningElement[name.name=/(Anchor|Link|^a)$/]'
|
|
47
47
|
const HREF_ATTRIBUTE = `JSXAttribute[name.name=${OPTION.react_router ? '/^(href|to)$/' : '"href"'}]`
|
|
48
48
|
const NULL_HREF_ATTRIBUTE_VALUES = `${HREF_ATTRIBUTE}:matches(${['#', ''].reduce((prev, v) => {
|
|
49
|
-
return `${prev}
|
|
49
|
+
return `${prev},[value.type="Literal"][value.value="${v}"],[value.type="JSXExpressionContainer"][value.expression.value="${v}"]`
|
|
50
50
|
}, '[value=null]')})`
|
|
51
51
|
const NEXT_LINK_REGEX = /Link$/
|
|
52
52
|
// HINT: next/link で `Link > a` という構造がありえるので直上のJSXElementを調べる
|
|
@@ -11,6 +11,7 @@ const noHeadingTagNamesRegex = /^(span|legend)$/
|
|
|
11
11
|
const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
12
12
|
const headingAttributeRegex = /^(heading|title)$/
|
|
13
13
|
const ariaLabelRegex = /^aria-label(ledby)?$/
|
|
14
|
+
const tagAttrRegex = /^(tag|unrecommendedTag)$/
|
|
14
15
|
|
|
15
16
|
const includeSectioningAsAttr = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
|
|
16
17
|
|
|
@@ -23,9 +24,9 @@ const pageHeadingMessage = `smarthr-ui/PageHeading が同一ファイル内に
|
|
|
23
24
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
24
25
|
const pageHeadingInSectionMessage = `smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。
|
|
25
26
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
26
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
27
|
+
const noTagAttrMessage = `tag属性、unrecommendedTag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性、unrecommendedTag属性を削除してください。
|
|
27
28
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
28
|
-
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
29
|
+
- tag属性、unrecommendedTag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
29
30
|
|
|
30
31
|
const VariableDeclaratorBareToSHR = (context, node) => {
|
|
31
32
|
if (!node.init) {
|
|
@@ -177,7 +178,7 @@ const forInSearchChildren = (ary) => {
|
|
|
177
178
|
return r
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
const findTagAttr = (a) => a.name?.name
|
|
181
|
+
const findTagAttr = (a) => tagAttrRegex.test(a.name?.name)
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
184
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
# smarthr/a11y-help-link-with-support-href
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[ヘルプページ](https://support.smarthr.jp/)へのリンクは[smarthr-ui/HelpLink](https://smarthr.design/products/components/text-link/help-link/)を使うことを促すルールです
|
|
4
|
+
|
|
5
|
+
## なぜ[ヘルプページ](https://support.smarthr.jp/)へのリンクはsmarthr-ui/HelpLinkを使う必要があるのか
|
|
6
|
+
ヘルプページへのリンクは、通常のテキストリンクと設定するべき属性が異なるため、専用のコンポーネントとして[smarthr-ui/HelpLink](https://smarthr.design/products/components/text-link/help-link/)が定義されています。<br />
|
|
7
|
+
例えば `rel="help"` は `現在のページとリンク先の関係を表す` ものであり `ヘルプページへのリンク` であることが明確になります。
|
|
8
|
+
|
|
9
|
+
## [ヘルプページ](https://support.smarthr.jp/)へのリンクとして扱われる条件
|
|
10
|
+
|
|
11
|
+
a要素、もしくはa要素と思われるコンポーネントのhref属性に `support` 関連と思われる値が設定さている場合がチェック対象になります。
|
|
12
|
+
|
|
13
|
+
### a要素、もしくはa要素と思われるコンポーネントの判定方法
|
|
14
|
+
|
|
15
|
+
a要素として判定されるコンポーネントの条件は以下のいずれかに合致する場合です。
|
|
16
|
+
|
|
17
|
+
- a要素
|
|
18
|
+
- コンポーネント名の末尾が `Link` `Anchor` `AnchorButton` のいずれかであること
|
|
19
|
+
|
|
20
|
+
### hrefがsupport関連のものかどうかの判定方法
|
|
21
|
+
|
|
22
|
+
hrefに設定されたURLがsupport関連であると判定される条件は以下のいずれかに合致する場合です。
|
|
23
|
+
|
|
24
|
+
- ドメイン部分に `support` という文言を含む場合
|
|
25
|
+
- `path` もしくは `xxxPath` というオブジェクト以下から `support` という値を参照する場合
|
|
26
|
+
- 例:
|
|
27
|
+
- path.support
|
|
28
|
+
- path.support.xxxx.yyyy
|
|
29
|
+
- path.xxxx.support.yyyy
|
|
30
|
+
- 変数の場合、`support` と `href` もしくは `url` という単語を含む場合
|
|
31
|
+
- 大文字小文字の組み合わせも許容されています
|
|
32
|
+
- 例:
|
|
33
|
+
- supportURL
|
|
34
|
+
- xxxSupportYyyHref
|
|
5
35
|
|
|
6
36
|
## rules
|
|
7
37
|
|
|
@@ -18,17 +48,37 @@
|
|
|
18
48
|
## ❌ Incorrect
|
|
19
49
|
|
|
20
50
|
```jsx
|
|
51
|
+
// supportという文字列を含むドメインの場合、smarthr-ui/HelpLinkを利用する必要がある
|
|
21
52
|
<TextLink href="https://support.smarthr.jp/xxxxx">any</TextLink>
|
|
22
53
|
<a href={`//support.smarthr.jp/${hoge}`}>any</a>
|
|
23
|
-
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```jsx
|
|
57
|
+
// path、xxxPath形式のオブジェクト以下で `support` という値を参照する場合もHelpLinkの利用が必要
|
|
24
58
|
<AnyAnchor href={path.support.xxxx.yyyy} />
|
|
59
|
+
<AnyAnchor href={path.xxxx.support.yyyy} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```jsx
|
|
63
|
+
// supportという単語が含まれる変数名が設定されている場合も同様にHelpLinkを利用する必要がある
|
|
64
|
+
<AnchorButton href={supportURL}>any</AnchorButton>
|
|
65
|
+
<AnchorButton href={xxxSupportYyyHref}>any</AnchorButton>
|
|
25
66
|
```
|
|
26
67
|
|
|
68
|
+
|
|
27
69
|
## ✅ Correct
|
|
28
70
|
|
|
29
71
|
```jsx
|
|
30
72
|
<HelpLink href="https://support.smarthr.jp/xxxxx">any</HelpLink>
|
|
31
73
|
<HelpLink href={`//support.smarthr.jp/${hoge}`}>any</HelpLink>
|
|
32
|
-
|
|
33
|
-
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```jsx
|
|
77
|
+
<AnyHelpLink href={path.support.xxxx.yyyy} />
|
|
78
|
+
<AnyHelpLink href={path.xxxx.support.yyyy} />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```jsx
|
|
82
|
+
<StyledHelpLink href={supportURL}>any</StyledHelpLink>
|
|
83
|
+
<StyledHelpLink href={xxxSupportYyyHref}>any</StyledHelpLink>
|
|
34
84
|
```
|
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
# smarthr/a11y-image-has-alt-attribute
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
画像にalt属性(代替テキスト)を設定することを強制するルールです
|
|
4
|
+
|
|
5
|
+
## なぜ画像に代替テキストが必要なのか
|
|
6
|
+
|
|
7
|
+
スクリーンリーダーなどの一部のブラウザで**対象画像が何を表しているのか?という情報が欠落することを防ぐ**目的があります。<br />
|
|
8
|
+
閲覧可能なUI上では十分な情報が存在していても、テキスト読み上げなどでは情報が不足することがあるため、**画像が何を表しているのかを説明する必要があります。**
|
|
9
|
+
|
|
10
|
+
[詳細: 画像に代替テキストが付与されている](https://smarthr.design/accessibility/check-list/alternative-text/)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### 画像に代替テキストが不要な場合について
|
|
14
|
+
|
|
15
|
+
例外的に**装飾目的の画像はaltに空文字を指定する**ことが許容されます。(例: `<XxxImage alt="" />`)<br />
|
|
16
|
+
**ただしこのルールでは上記のようなalt指定もエラーになります。**<br />
|
|
17
|
+
理由は**SmartHRのプロダクトにおいて装飾目的の画像はほぼ全てアイコンとなる**ためです。<br />
|
|
18
|
+
smarthr-uiが提供するIconではないアイコンのコンポーネントを定義する場合、コンポーネント名の末尾を `Icon` にすることでこのチェックを回避出来ます。<br />
|
|
19
|
+
SmartHRのプロダクトにおいて、アイコンを単一で使う可能性は低く、利用する場合でも[a11y-clickable-element-has-text](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-clickable-element-has-text)で別途チェックされます。
|
|
20
|
+
|
|
21
|
+
[詳細: 装飾目的の画像が無視できるようになっている](https://smarthr.design/accessibility/check-list/decorative-image/)
|
|
22
|
+
|
|
23
|
+
## alt以外の代替テキストの設定方法について
|
|
24
|
+
|
|
25
|
+
**前提として可能な限りalt属性を利用してください。** 迷うような条件の場合、alt属性を利用すれば大きな問題は発生しないでしょう。<br />
|
|
26
|
+
例外として以下の場合 `aria-describedby` 属性を利用して代替テキストを設定することが出来ます。
|
|
27
|
+
|
|
28
|
+
1. 設定するべき代替テキストが、すでに他要素でテキスト、もしくは代替テキストとして存在している
|
|
29
|
+
2. 1であり、かつその要素と直接関係性がある場合
|
|
30
|
+
|
|
31
|
+
わかり易い例として以下の様なパターンが考えられます。
|
|
32
|
+
|
|
33
|
+
- Tableに値が一覧されており、それをグラフ画像として表示する場合
|
|
34
|
+
- 別アイコンにチェックアイコンを重ねる場合
|
|
35
|
+
|
|
36
|
+
## spread-attributesが設定されているなら許容したい場合
|
|
37
|
+
|
|
38
|
+
下記の様にspread attributesが設定されていれば、代替テキストが設定されている扱いにしたい場合、lintのoptionとして `checkType` に `allow-spread-attributes` を設定してください。
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
// checkType: 'allow-spread-attributes'
|
|
42
|
+
<XxxImage {...args} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
便利な設定ではありますが、**代替テキストが実際に設定されているかは判定出来ていないため、チェック漏れが発生する可能性があります。**
|
|
46
|
+
設定する場合は慎重に検討してください。
|
|
5
47
|
|
|
6
48
|
## rules
|
|
7
49
|
|
|
@@ -19,27 +61,24 @@
|
|
|
19
61
|
## ❌ Incorrect
|
|
20
62
|
|
|
21
63
|
```jsx
|
|
64
|
+
// 画像に代替テキストが設定されていないためNG
|
|
22
65
|
<Img />
|
|
23
66
|
```
|
|
67
|
+
|
|
24
68
|
```jsx
|
|
69
|
+
// 子要素が存在する場合でも同様にNG
|
|
70
|
+
// この場合、Imageは画像要素ではない場合がありますが
|
|
71
|
+
// その場合は画像と勘違いしない名称に変更してください
|
|
25
72
|
<Image>
|
|
26
73
|
<Any />
|
|
27
74
|
</Image>
|
|
28
75
|
```
|
|
76
|
+
|
|
29
77
|
```jsx
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```jsx
|
|
78
|
+
// spread attributesにに代替テキスト用の属性が含まれていてもNG
|
|
79
|
+
|
|
33
80
|
// checkType: 'always'
|
|
34
81
|
<XxxImage {...args} />
|
|
35
|
-
<YyyIcon {...args} any="any" />
|
|
36
|
-
```
|
|
37
|
-
```jsx
|
|
38
|
-
import styled from 'styled-components'
|
|
39
|
-
|
|
40
|
-
const StyledHoge = styled.img``
|
|
41
|
-
const StyledFuga = styled(Img)``
|
|
42
|
-
const StyledPiyo = styled(Icon)``
|
|
43
82
|
```
|
|
44
83
|
|
|
45
84
|
## ✅ Correct
|
|
@@ -47,23 +86,19 @@ const StyledPiyo = styled(Icon)``
|
|
|
47
86
|
```jsx
|
|
48
87
|
<Img alt="message" />
|
|
49
88
|
```
|
|
89
|
+
|
|
50
90
|
```jsx
|
|
51
91
|
<Image alt="message">
|
|
52
92
|
<Any />
|
|
53
93
|
</Image>
|
|
54
94
|
```
|
|
95
|
+
|
|
55
96
|
```jsx
|
|
56
|
-
|
|
97
|
+
// Iconは画像として扱われますが、例外的に代替テキストを設定しなくてもOK
|
|
98
|
+
<Icon />
|
|
57
99
|
```
|
|
100
|
+
|
|
58
101
|
```jsx
|
|
59
102
|
// checkType: 'allow-spread-attributes'
|
|
60
103
|
<XxxImage {...args} />
|
|
61
|
-
<YyyIcon {...args} any="any" />
|
|
62
|
-
```
|
|
63
|
-
```jsx
|
|
64
|
-
import styled from 'styled-components'
|
|
65
|
-
|
|
66
|
-
const StyledImage = styled.img``
|
|
67
|
-
const StyledImg = styled(Img)``
|
|
68
|
-
const StyledIcon = styled(Icon)``
|
|
69
104
|
```
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
# smarthr/a11y-input-has-name-attribute
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
input, textarea, select など入力要素に name 属性を設定することを強制するルールです。
|
|
4
|
+
|
|
5
|
+
## なぜ入力要素にname属性を設定する必要があるのか
|
|
6
|
+
|
|
7
|
+
入力要素にname属性を適切に設定することで、キーボード操作や補完機能が適切に動作するようになります。<br />
|
|
8
|
+
例えば入力要素の一種である `input[type="radio"]` は同一のname属性が設定されたものは一つのグループとして扱われ、矢印キーでグループ内を適切に移動できるようになります。<br />
|
|
9
|
+
他にはname属性に `address` `zip_code` など住所が連想される単語を含めた場合、ブラウザの補完機能で住所が自動入力される可能性が高まります。
|
|
10
|
+
|
|
11
|
+
## name属性に設定する文字列のフォーマットについて
|
|
12
|
+
|
|
13
|
+
なるべく同一のフォーマットをもちいることで自動補完される可能性を上げるためname属性が一定のフォーマットになるようチェックを行っています。<br />
|
|
14
|
+
使える文字は以下のとおりです。
|
|
15
|
+
|
|
16
|
+
- 半角英数の大文字・小文字
|
|
17
|
+
- 一部記号(`_` `[` `]`)
|
|
18
|
+
|
|
19
|
+
## spread-attributesが設定されているなら許容したい場合
|
|
20
|
+
|
|
21
|
+
下記の様にspread attributesが設定されていれば、name属性が設定されている扱いにしたい場合、lintのoptionとして `checkType` に `allow-spread-attributes` を設定してください。
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
// checkType: 'allow-spread-attributes'
|
|
25
|
+
<AnyInput {...args} />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
便利な設定ではありますが、**name属性が実際に設定されているかは判定出来ていないため、チェック漏れが発生する可能性があります。**
|
|
29
|
+
設定する場合は慎重に検討してください。
|
|
8
30
|
|
|
9
31
|
## rules
|
|
10
32
|
|
|
@@ -22,24 +44,18 @@
|
|
|
22
44
|
## ❌ Incorrect
|
|
23
45
|
|
|
24
46
|
```jsx
|
|
47
|
+
// name属性が存在しないためNG
|
|
25
48
|
<RadioButton />
|
|
26
49
|
<Input type="radio" />
|
|
27
50
|
<input type="text" />
|
|
28
51
|
<Textarea />
|
|
29
52
|
<Select />
|
|
30
|
-
|
|
31
|
-
// checkType: 'always'
|
|
32
|
-
<AnyInput {...args} />
|
|
33
|
-
<AnyInput {...args} any="any" />
|
|
34
53
|
```
|
|
35
54
|
|
|
36
|
-
|
|
37
55
|
```jsx
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const StyledFuga = styled(Input)``;
|
|
42
|
-
const StyledPiyo = styled(RadioButton)``;
|
|
56
|
+
// 仮にargsにname属性が存在していてもNG
|
|
57
|
+
// checkType: 'always'
|
|
58
|
+
<AnyInput {...args} />
|
|
43
59
|
```
|
|
44
60
|
|
|
45
61
|
## ✅ Correct
|
|
@@ -50,16 +66,9 @@ const StyledPiyo = styled(RadioButton)``;
|
|
|
50
66
|
<input type="text" name="any" />
|
|
51
67
|
<Textarea name="some" />
|
|
52
68
|
<Select name="piyo" />
|
|
53
|
-
|
|
54
|
-
// checkType: 'allow-spread-attributes'
|
|
55
|
-
<AnyInput {...args} />
|
|
56
|
-
<AnyInput {...args} any="any" />
|
|
57
69
|
```
|
|
58
70
|
|
|
59
71
|
```jsx
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const StyledInput = styled.input``;
|
|
63
|
-
const StyledInput = styled(Input)``;
|
|
64
|
-
const StyledRadioButton = styled(RadioButton)``;
|
|
72
|
+
// checkType: 'allow-spread-attributes'
|
|
73
|
+
<AnyInput {...args} />
|
|
65
74
|
```
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
# smarthr/a11y-input-in-form-control
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
入力要素をsmarthr-ui/FormControl、もしくはsmarthr-ui/Fieldsetで囲むことを促すルールです
|
|
4
|
+
|
|
5
|
+
## なぜFormControl、Fieldsetで囲まなければならないのか
|
|
6
|
+
|
|
7
|
+
FormControl、Fieldsetはそれぞれlabel要素、fieldset要素とlegend要素を含み、それらは適切に入力要素と関連付けされます。<br />
|
|
8
|
+
例えばFormControlでInputをラップした場合、FormControlが出力するlabel要素は適切にInputと紐づくので、label要素をクリックすることで入力要素にfocusされます。<br />
|
|
9
|
+
この動作はブラウザがデフォルトで持つ機能であり、当たり前に期待される動作のため必ず設定する必要性があります。<br />
|
|
10
|
+
|
|
11
|
+
また入力要素は必ずラベルとなるテキストを何らかの形でもつ必要があります。<br />
|
|
12
|
+
紐づくラベルを持たない入力要素はスクリーンリーダーなど一部のブラウザに置いて **その入力要素がなんであるか?という情報が欠落した状態** になり、ユーザー操作に影響します。<br />
|
|
13
|
+
|
|
14
|
+
同様にFieldsetは複数の入力要素やFormControlを一つのグループとして扱うことを表します。<br />
|
|
15
|
+
適切に設定することでformの内容をわかりやすく示すことができます。
|
|
16
|
+
|
|
17
|
+
### SectioningContent ではなく Fieldset の利用を推奨する理由
|
|
18
|
+
|
|
19
|
+
このルールではform要素以下でSectioninContent(Section,Article,Aside,Nav)を発見した場合、Fieldsetに変更することを促します。<br />
|
|
20
|
+
SectioninContentでもmarkupの観点としては問題ありませんが、一部スクリーンリーダーなどに搭載されている **特定の要素に対するジャンプ機能** などを利用する際にデメリットが生じます。<br />
|
|
21
|
+
|
|
22
|
+
form要素以下で見出しとして利用される事が多いlabel要素、legend要素以外に**Headingが混ざった場合、ジャンプ機能によって意図せずスキップされてしまう可能性**が高まります。
|
|
23
|
+
|
|
24
|
+
fielset要素はmarkupとしては必須でinputを持つ必要性はないこと、form内に存在する見出しはformの別の入力要素に対する説明である場合がほとんどであることなどからこのルールではform要素以下では全ての見出しをlabel, legendに統一するためこのチェックを行っています。
|
|
6
25
|
|
|
7
26
|
## rules
|
|
8
27
|
|
|
@@ -25,33 +44,42 @@
|
|
|
25
44
|
```jsx
|
|
26
45
|
// FormControlで囲まれていないためNG
|
|
27
46
|
<Input />
|
|
47
|
+
```
|
|
28
48
|
|
|
49
|
+
```jsx
|
|
29
50
|
// FormControl・FieldsetではなくSectionでマークアップされているためNG
|
|
30
51
|
<Section>
|
|
31
52
|
<Heading />
|
|
32
53
|
<Select />
|
|
33
54
|
</Section>
|
|
55
|
+
```
|
|
34
56
|
|
|
35
|
-
|
|
57
|
+
```jsx
|
|
58
|
+
// RadioButton, Checkboxは内部にlabel要素を持つためFormControlで囲むことは不適切
|
|
59
|
+
// 見出しを設定したい場合、Fieldsetでグルーピングする
|
|
36
60
|
<FormControl title="any heading">
|
|
37
61
|
<RadioButton>{a.label}</RadioButton>
|
|
38
62
|
</FormControl>
|
|
63
|
+
```
|
|
39
64
|
|
|
65
|
+
```jsx
|
|
40
66
|
// FormControlが複数の入力要素を持ってしまっているのでNG
|
|
41
67
|
<FormControl title="any heading">
|
|
42
68
|
<Input />
|
|
43
69
|
<Combobox />
|
|
44
70
|
</FormControl>
|
|
71
|
+
```
|
|
45
72
|
|
|
46
|
-
|
|
73
|
+
```jsx
|
|
47
74
|
// FormControlがネストしてしまっているのでNG
|
|
48
75
|
<FormControl>
|
|
49
76
|
<SubFormControl>
|
|
50
77
|
<Checkbox />
|
|
51
78
|
</SubFormControl>
|
|
52
79
|
</FormControl>
|
|
80
|
+
```
|
|
53
81
|
|
|
54
|
-
|
|
82
|
+
```jsx
|
|
55
83
|
// Fieldsetには role="group" がデフォルトで設定されているのでNG
|
|
56
84
|
<Fieldset role="group" />
|
|
57
85
|
```
|
|
@@ -62,42 +90,38 @@
|
|
|
62
90
|
<FormControl title="any heading">
|
|
63
91
|
<Input />
|
|
64
92
|
</FormControl>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```jsx
|
|
96
|
+
// smarthr-ui/Checkbox はlabelを含むため、なんの入力要素かが単独で伝えられるので
|
|
97
|
+
// FormControl・Fieldsetで囲む必要はない (Fieldsetで囲んでも問題はない)
|
|
98
|
+
<Checkbox />
|
|
99
|
+
```
|
|
65
100
|
|
|
101
|
+
```jsx
|
|
66
102
|
<Fieldset title="any heading">
|
|
67
103
|
{radios.map((a) => (
|
|
68
104
|
<RadioButton>{a.label}</RadioButton>
|
|
69
105
|
))}
|
|
70
106
|
</Fieldset>
|
|
107
|
+
```
|
|
71
108
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<WarekiPicker />
|
|
76
|
-
</FormControl>
|
|
77
|
-
|
|
78
|
-
<Fieldset title="any heading">
|
|
79
|
-
<FormControl role="group">
|
|
109
|
+
```jsx
|
|
110
|
+
<Fieldset title="date range">
|
|
111
|
+
<FormControl label={{ text: "start", unrecommendedHide: true }}>
|
|
80
112
|
<WarekiPicker />
|
|
81
|
-
|
|
113
|
+
</FormControl>
|
|
114
|
+
~
|
|
115
|
+
<FormControl label={{ text: "end", unrecommendedHide: true }}>
|
|
82
116
|
<WarekiPicker />
|
|
83
|
-
</FormControl
|
|
117
|
+
</FormControl>
|
|
84
118
|
</Fieldset>
|
|
119
|
+
```
|
|
85
120
|
|
|
121
|
+
```jsx
|
|
86
122
|
// childrenを持たないFieldset、FormControlは入力要素として扱うためOK
|
|
87
123
|
<Fieldset title="any heading">
|
|
88
124
|
<HogeFieldset />
|
|
89
125
|
<FugaFormControl />
|
|
90
126
|
</Fieldset>
|
|
91
|
-
|
|
92
|
-
// Sectionより先にFormControl・Fieldsetで囲んでいるためOK
|
|
93
|
-
<Section>
|
|
94
|
-
<Heading />
|
|
95
|
-
<FormControl title="any heading">
|
|
96
|
-
<Input />
|
|
97
|
-
</FormControl>
|
|
98
|
-
</Section>
|
|
99
|
-
|
|
100
|
-
// smarthr-ui/Checkbox はlabelを含むため、なんの入力要素かが単独で伝えられるので
|
|
101
|
-
// FormControl・Fieldsetで囲む必要はない (囲んでも問題はない)
|
|
102
|
-
<Checkbox />
|
|
103
127
|
```
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
# smarthr/a11y-numbered-text-within-ol
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
"1. hoge", "2. fuga" ... のように連番のテキストはol要素でマークアップすることを促すルールです
|
|
4
|
+
|
|
5
|
+
## 連番のテキストをolでマークアップするメリット
|
|
6
|
+
|
|
7
|
+
ol要素でマークアップすることで連番テキストをもつ要素同士の関係、順番に意味があることを適切に表すことが出来ます。<br />
|
|
8
|
+
連番のテキストをol要素でマークアップすることは必須ではありませんが、このルールを導入することでマークアップの基準を揃えることができ、ユーザーに対する情報提供の方法が統一されます。
|
|
9
|
+
|
|
10
|
+
## 連番として扱われるテキスト
|
|
11
|
+
|
|
12
|
+
以下の条件に当てはまった場合、連番が設定されているテキストとして扱われます。
|
|
13
|
+
|
|
14
|
+
- 数値と数値以外の二文字が先頭にある文字列(Aとします)
|
|
15
|
+
- A以降で、Aに設定された数値+1の値を先頭にあり、かつその直後に数値以外の二文字がある文字列
|
|
16
|
+
|
|
17
|
+
## olとして扱われる要素・コンポーネント
|
|
18
|
+
|
|
19
|
+
以下はol要素として扱われます
|
|
20
|
+
|
|
21
|
+
- ol
|
|
22
|
+
- OrderedListが名称のsuffixにつくコンポーネント
|
|
23
|
+
- 例:
|
|
24
|
+
- OrderedList
|
|
25
|
+
- XxxOrderedList
|
|
26
|
+
- XxxOrderedYyyList
|
|
5
27
|
|
|
6
28
|
## rules
|
|
7
29
|
|
|
@@ -19,17 +41,23 @@
|
|
|
19
41
|
// ol要素で囲まれていないためNG
|
|
20
42
|
<Any>1. hoge</Any>
|
|
21
43
|
<Any>2. fuga</Any>
|
|
44
|
+
```
|
|
22
45
|
|
|
46
|
+
```jsx
|
|
23
47
|
// 属性でも同様にチェックする
|
|
24
48
|
<Any title="1. hoge" />
|
|
25
49
|
<Any title="2. fuga" />
|
|
50
|
+
```
|
|
26
51
|
|
|
52
|
+
```jsx
|
|
27
53
|
// ol要素内で連番を設定しているとNG
|
|
28
54
|
<OrderedList>
|
|
29
55
|
<li>1. hoge</li>
|
|
30
56
|
<li>2. fuga</li>
|
|
31
57
|
</OrderedList>
|
|
58
|
+
```
|
|
32
59
|
|
|
60
|
+
```jsx
|
|
33
61
|
// 同一のol要素で囲まれていないためNG
|
|
34
62
|
<OrderedList>
|
|
35
63
|
<li>hoge</li>
|
|
@@ -37,7 +65,6 @@
|
|
|
37
65
|
<OrderedList>
|
|
38
66
|
<li>fuga</li>
|
|
39
67
|
</OrderedList>>
|
|
40
|
-
|
|
41
68
|
```
|
|
42
69
|
|
|
43
70
|
## ✅ Correct
|
|
@@ -47,12 +74,16 @@
|
|
|
47
74
|
<li>hoge</li>
|
|
48
75
|
<li>fuga</li>
|
|
49
76
|
</ol>
|
|
77
|
+
```
|
|
50
78
|
|
|
79
|
+
```jsx
|
|
51
80
|
<OrderedList>
|
|
52
81
|
<Any title="hoge" />
|
|
53
82
|
<Any title="fuga" />
|
|
54
83
|
</OrderedList>
|
|
84
|
+
```
|
|
55
85
|
|
|
86
|
+
```jsx
|
|
56
87
|
// デフォルトの連番からフォーマット、スタイルを変更したい場合
|
|
57
88
|
// counter-reset + counter-increment で表現する
|
|
58
89
|
// 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
|
@@ -71,7 +102,7 @@
|
|
|
71
102
|
|
|
72
103
|
const OrderedList = styled.ol`
|
|
73
104
|
list-style: none; // デフォルトのstyleを消す
|
|
74
|
-
counter-reset: hoge; //
|
|
105
|
+
counter-reset: hoge; // カウンターの名称。同一html内で被らない名称にする
|
|
75
106
|
`
|
|
76
107
|
const NumberedHeading = styled(Heading)`
|
|
77
108
|
&::before {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const MULTI_CHILDREN_REGEX = /(Cluster|Stack)$/
|
|
2
2
|
const REGEX_NLSP = /^\s*\n+\s*$/
|
|
3
3
|
const FLEX_END_REGEX = /^(flex-)?end$/
|
|
4
|
+
const MAP_METHOD_REGEX = /^(map|flatMap)$/
|
|
4
5
|
|
|
5
6
|
const LAYOUT_COMPONENT_REGEX_WITHOUT_STACK = /(Center|Cluster|Container|Reel|Sidebar)$/
|
|
6
7
|
const LAYOUT_COMPONENT_REGEX = /(Center|Cluster|Container|Reel|Stack|Sidebar)$/
|
|
@@ -44,7 +45,7 @@ const searchChildren = (node) => {
|
|
|
44
45
|
case 'ChainExpression':
|
|
45
46
|
return searchChildren(node.expression)
|
|
46
47
|
case 'CallExpression':
|
|
47
|
-
return node.callee.property?.name
|
|
48
|
+
return !MAP_METHOD_REGEX.test(node.callee.property?.name)
|
|
48
49
|
case 'ConditionalExpression':
|
|
49
50
|
return searchChildren(node.consequent) && searchChildren(node.alternate)
|
|
50
51
|
case 'LogicalExpression':
|
|
@@ -185,7 +186,7 @@ module.exports = {
|
|
|
185
186
|
message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください${DETAIL_LINK_MESSAGE}`,
|
|
186
187
|
})
|
|
187
188
|
},
|
|
188
|
-
[`JSXElement
|
|
189
|
+
[`JSXElement[openingElement.name.name=/RadioButton(Panel)?$/] ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
189
190
|
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
190
191
|
|
|
191
192
|
context.report({
|
|
@@ -193,7 +194,7 @@ module.exports = {
|
|
|
193
194
|
message: `RadioButton, RadioButtonPanelの子孫に${component}を置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
194
195
|
})
|
|
195
196
|
},
|
|
196
|
-
[`JSXElement
|
|
197
|
+
[`JSXElement[openingElement.name.name=/Checkbox?$/] ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
197
198
|
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
198
199
|
|
|
199
200
|
context.report({
|
|
@@ -27,12 +27,97 @@ const anyAction = (a) => {
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
- 上記のような条件に修正することで、本質的に実行したい処理の条件がわかりやすくなります
|
|
30
|
-
-
|
|
30
|
+
- このルールは基本的に以下の条件すべてに合致する場合、NGにします
|
|
31
31
|
- 早期return以降にifやelse、switch, tryがない
|
|
32
32
|
- 早期returnの条件ブロック以降に変数宣言がない
|
|
33
33
|
- 早期returnの条件ブロック以降にreturnがない
|
|
34
34
|
- 早期returnが値を返していない
|
|
35
35
|
|
|
36
|
+
## 特殊なパターンのチェックについて
|
|
37
|
+
|
|
38
|
+
### 早期returnが連続する場合
|
|
39
|
+
|
|
40
|
+
下記のように早期returnが連続している場合、NGになります。
|
|
41
|
+
|
|
42
|
+
```jsx
|
|
43
|
+
const anyAction = (a, b) => {
|
|
44
|
+
if (a) return
|
|
45
|
+
if (b) { return }
|
|
46
|
+
|
|
47
|
+
const hoge = 'any'
|
|
48
|
+
...
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
これらの早期returnは本質的に一つの条件のためまとめてください。
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
const anyAction = (a, b) => {
|
|
56
|
+
if (a || b) return
|
|
57
|
+
|
|
58
|
+
const hoge = 'any'
|
|
59
|
+
...
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
上記のように一つの条件にまとめることで一連の条件であること、よりよい条件がある場合見つけやすくなることなどのメリットがあります。
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
### 早期return直後にifが単独で存在する場合
|
|
67
|
+
|
|
68
|
+
下記の様に早期return直後にifが単独で存在する場合、NGになります。
|
|
69
|
+
|
|
70
|
+
```jsx
|
|
71
|
+
const anyAction = (a, b) => {
|
|
72
|
+
if (a) return
|
|
73
|
+
|
|
74
|
+
if (b) {
|
|
75
|
+
...
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
これらのifは本質的に一つの条件のためまとめてください。
|
|
81
|
+
|
|
82
|
+
```jsx
|
|
83
|
+
const anyAction = (a, b) => {
|
|
84
|
+
if (!a && b) {
|
|
85
|
+
...
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
上記のように一つの条件にまとめることで一連の条件であること、よりよい条件がある場合見つけやすくなることなどのメリットがあります。
|
|
91
|
+
このチェックは **早期return直後にifが単独で存在する場合** のみNGとするため **早期returnの直後にelseをもつifが存在する場合** や **早期returnの直後に複数のifが存在する場合** はNGになりません。
|
|
92
|
+
|
|
93
|
+
```jsx
|
|
94
|
+
// 早期returnの直後にelseをもつifが存在する場合はOK
|
|
95
|
+
const anyAction = (a, b) => {
|
|
96
|
+
if (a) return
|
|
97
|
+
|
|
98
|
+
if (b) {
|
|
99
|
+
...
|
|
100
|
+
} else {
|
|
101
|
+
// else ifの場合もNGにならない
|
|
102
|
+
...
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
// 早期returnの直後に複数のifが存在する場合はOK
|
|
109
|
+
const anyAction = (a, b, c) => {
|
|
110
|
+
if (a) return
|
|
111
|
+
|
|
112
|
+
if (b) {
|
|
113
|
+
...
|
|
114
|
+
}
|
|
115
|
+
if (c) {
|
|
116
|
+
...
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
36
121
|
## rules
|
|
37
122
|
|
|
38
123
|
```js
|
|
@@ -40,8 +40,9 @@ module.exports = {
|
|
|
40
40
|
const action = (node) => {
|
|
41
41
|
const fn = searchFunction(node).body.body
|
|
42
42
|
// 0: 最初の早期returnを検索中
|
|
43
|
-
// 1: 最初の早期return
|
|
44
|
-
// 2: 1
|
|
43
|
+
// 1: 最初の早期returnを発見直後
|
|
44
|
+
// 2: 1の直後に早期returnではないifが見つかった場合
|
|
45
|
+
// 3: 1の後にif以外が見つかった場合
|
|
45
46
|
let flg = 0
|
|
46
47
|
|
|
47
48
|
for (let i = 0; i < fn.length; i++) {
|
|
@@ -62,25 +63,43 @@ module.exports = {
|
|
|
62
63
|
case 'TryStatement':
|
|
63
64
|
return
|
|
64
65
|
case 'IfStatement':
|
|
65
|
-
if (flg === 1
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
if (flg === 1) {
|
|
67
|
+
if (node === getEarlyReturn(b)) {
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
message: `早期returnのifが分割されています${DETAIL_LINK}
|
|
69
71
|
- 一つのifにまとめるよう、条件を調整してください`,
|
|
70
|
-
|
|
72
|
+
})
|
|
73
|
+
return
|
|
74
|
+
} else if (!b.alternate) {
|
|
75
|
+
flg = 2
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
71
78
|
}
|
|
79
|
+
|
|
72
80
|
return
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
flg =
|
|
83
|
+
flg = 3
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
switch (flg) {
|
|
87
|
+
case 2:
|
|
88
|
+
context.report({
|
|
89
|
+
node,
|
|
90
|
+
message: `本質的に一つの条件が複数のifに分割されています${DETAIL_LINK}
|
|
91
|
+
- 直後のifと一つにまとめるよう、条件を調整してください`,
|
|
92
|
+
})
|
|
93
|
+
return
|
|
94
|
+
case 3:
|
|
95
|
+
context.report({
|
|
96
|
+
node,
|
|
97
|
+
message: `後続の処理の逆の条件の早期returnのため修正してください。${DETAIL_LINK}
|
|
81
98
|
- 本質的に行いたい処理の条件とは逆がifに記述されているため、ロジックを確認する際条件を逆転させて考える余計な手間が発生しています
|
|
82
99
|
- 条件を逆転させたうえで後続の処理をifの内部に移動してください`,
|
|
83
|
-
|
|
100
|
+
})
|
|
101
|
+
return
|
|
102
|
+
}
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
return {
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
const SCHEMA = []
|
|
2
2
|
|
|
3
|
-
const searchBubbleUp = (node) => {
|
|
4
|
-
switch (node.type) {
|
|
5
|
-
case 'Program':
|
|
6
|
-
case 'JSXAttribute':
|
|
7
|
-
return null
|
|
8
|
-
case 'TemplateLiteral':
|
|
9
|
-
return node
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return searchBubbleUp(node.parent)
|
|
13
|
-
}
|
|
14
3
|
|
|
15
4
|
/**
|
|
16
5
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -23,20 +12,20 @@ module.exports = {
|
|
|
23
12
|
},
|
|
24
13
|
create(context) {
|
|
25
14
|
const checker = (node) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
node,
|
|
30
|
-
message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
|
|
15
|
+
return context.report({
|
|
16
|
+
node,
|
|
17
|
+
message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
|
|
31
18
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/trim-props`,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
19
|
+
fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(/^('|"|`)\s+/, '$1').replace(/\s+('|"|`)$/, '$1')),
|
|
20
|
+
})
|
|
35
21
|
}
|
|
36
22
|
|
|
37
23
|
return {
|
|
38
|
-
'JSXAttribute Literal[value=/(^ | $)/]': checker,
|
|
39
|
-
'JSXAttribute
|
|
24
|
+
'JSXAttribute > Literal[value=/(^ | $)/]': checker,
|
|
25
|
+
'JSXAttribute > JSXExpressionContainer>Literal[value=/(^ | $)/]': checker,
|
|
26
|
+
'JSXAttribute > JSXExpressionContainer > TemplateLiteral > TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/])': (node) => {
|
|
27
|
+
checker(node.parent)
|
|
28
|
+
},
|
|
40
29
|
}
|
|
41
30
|
},
|
|
42
31
|
}
|
|
@@ -20,9 +20,9 @@ const pageMessage = `smarthr-ui/PageHeading が同一ファイル内に複数存
|
|
|
20
20
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
21
21
|
const pageInSectionMessage = `smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。
|
|
22
22
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
23
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
23
|
+
const noTagAttrMessage = `tag属性、unrecommendedTag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性、unrecommendedTag属性を削除してください。
|
|
24
24
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
25
|
-
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
25
|
+
- tag属性、unrecommendedTag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
26
26
|
const notHaveHeadingMessage = (elementName, isNav) => `${elementName} はHeading要素を含んでいません。
|
|
27
27
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
28
28
|
- SectioningContentはHeadingを含むようにマークアップする必要があります
|
|
@@ -69,6 +69,7 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
69
69
|
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
|
|
70
70
|
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
|
|
71
71
|
{ code: '<Section><Heading tag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
72
|
+
{ code: '<Section><Heading unrecommendedTag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
72
73
|
{ code: '<Section></Section>', errors: [ { message: notHaveHeadingMessage('Section') } ] },
|
|
73
74
|
{ code: '<Aside><HogeSection></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') }, { message: notHaveHeadingMessage('HogeSection') } ] },
|
|
74
75
|
{ code: '<Aside any="hoge"><HogeSection><Heading /></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') } ] },
|
|
@@ -17,7 +17,7 @@ const errorMessage = (type, name) => `${name}には子要素が一つしか無
|
|
|
17
17
|
- styleを確認し、div・spanなど、別要素でマークアップし直すか、${name}を削除してください
|
|
18
18
|
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
|
|
19
19
|
|
|
20
|
-
ruleTester.run('best-practice-for-
|
|
20
|
+
ruleTester.run('best-practice-for-layouts', rule, {
|
|
21
21
|
valid: [
|
|
22
22
|
{ code: `<Center />` },
|
|
23
23
|
{ code: `<Cluster />` },
|
|
@@ -30,30 +30,30 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
30
30
|
{ code: `<Stack><Hoge /><Hoge /></Stack>` },
|
|
31
31
|
{ code: `<Stack>{a}<Hoge /></Stack>` },
|
|
32
32
|
{ code: `<AnyStack>{a.map(action)}</AnyStack>` },
|
|
33
|
-
{ code: `<AnyStack>{a?.
|
|
33
|
+
{ code: `<AnyStack>{a?.flatMap(action)}</AnyStack>` },
|
|
34
34
|
{ code: `<AnyStack>{a.b?.map(action)}</AnyStack>` },
|
|
35
|
-
{ code: `<AnyStack>{a?.b?.
|
|
35
|
+
{ code: `<AnyStack>{a?.b?.flatMap(action)}</AnyStack>` },
|
|
36
36
|
{ code: `<AnyStack>{a && <><Hoge /><Hoge /></>}</AnyStack>` },
|
|
37
37
|
{ code: `<AnyStack>{a && a.map(action)}</AnyStack>` },
|
|
38
|
-
{ code: `<AnyStack>{a && a.b.
|
|
38
|
+
{ code: `<AnyStack>{a && a.b.flatMap(action)}</AnyStack>` },
|
|
39
39
|
{ code: `<AnyStack>{a || <><Hoge /><Hoge /></>}</AnyStack>` },
|
|
40
40
|
{ code: `<AnyStack>{a || a.map(action)}</AnyStack>` },
|
|
41
|
-
{ code: `<AnyStack>{a || a.b.
|
|
41
|
+
{ code: `<AnyStack>{a || a.b.flatMap(action)}</AnyStack>` },
|
|
42
42
|
{ code: `<AnyStack>{a ? a.b.map(action) : <Hoge />}</AnyStack>` },
|
|
43
|
-
{ code: `<AnyStack>{a ? <Hoge /> : a.b.
|
|
43
|
+
{ code: `<AnyStack>{a ? <Hoge /> : a.b.flatMap(action)}</AnyStack>` },
|
|
44
44
|
{ code: `<AnyStack>{a ? <Hoge /> : a ? <Hoge /> : a.b.map(action)}</AnyStack>` },
|
|
45
45
|
{ code: `<Cluster><Hoge /><Hoge /></Cluster>` },
|
|
46
46
|
{ code: `<Cluster>{a}<Hoge /></Cluster>` },
|
|
47
|
-
{ code: `<AnyCluster>{a.
|
|
47
|
+
{ code: `<AnyCluster>{a.flatMap(action)}</AnyCluster>` },
|
|
48
48
|
{ code: `<AnyCluster>{a && <><Hoge /><Hoge /></>}</AnyCluster>` },
|
|
49
49
|
{ code: `<AnyCluster>{a && a.map(action)}</AnyCluster>` },
|
|
50
|
-
{ code: `<AnyCluster>{a && a.b.
|
|
50
|
+
{ code: `<AnyCluster>{a && a.b.flatMap(action)}</AnyCluster>` },
|
|
51
51
|
{ code: `<AnyCluster>{a || <><Hoge /><Hoge /></>}</AnyCluster>` },
|
|
52
|
-
{ code: `<AnyCluster>{a || a.
|
|
52
|
+
{ code: `<AnyCluster>{a || a.flatMap(action)}</AnyCluster>` },
|
|
53
53
|
{ code: `<AnyCluster>{a || a.b.map(action)}</AnyCluster>` },
|
|
54
|
-
{ code: `<AnyCluster>{a ? a.b.
|
|
54
|
+
{ code: `<AnyCluster>{a ? a.b.flatMap(action) : <Hoge />}</AnyCluster>` },
|
|
55
55
|
{ code: `<AnyCluster>{a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
|
|
56
|
-
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.
|
|
56
|
+
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.flatMap(action)}</AnyCluster>` },
|
|
57
57
|
{ code: `<Cluster justify="flex-end">{a}</Cluster>` },
|
|
58
58
|
{ code: `<HogeCluster justify="end" gap={0}>{a}</HogeCluster>` },
|
|
59
59
|
{ code: `<Stack align="flex-end">{a}</Stack>` },
|
|
@@ -18,6 +18,9 @@ const UNNESESSARY_EARLY_RETURN_ERROR = `後続の処理の逆の条件の早期r
|
|
|
18
18
|
const SPLIT_EARLY_RETURN_ERROR = `早期returnのifが分割されています
|
|
19
19
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return
|
|
20
20
|
- 一つのifにまとめるよう、条件を調整してください`
|
|
21
|
+
const SPLIT_CONFIG_ERROR = `本質的に一つの条件が複数のifに分割されています
|
|
22
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return
|
|
23
|
+
- 直後のifと一つにまとめるよう、条件を調整してください`
|
|
21
24
|
|
|
22
25
|
ruleTester.run('best-practice-for-unnesessary-early-return', rule, {
|
|
23
26
|
valid: [
|
|
@@ -109,6 +112,36 @@ ruleTester.run('best-practice-for-unnesessary-early-return', rule, {
|
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
` },
|
|
115
|
+
{ code: `
|
|
116
|
+
const anyAction = (a) => {
|
|
117
|
+
if (!a) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (b) { /* any 1 */ } else { /* any 2 */ }
|
|
122
|
+
}
|
|
123
|
+
` },
|
|
124
|
+
{ code: `
|
|
125
|
+
const anyAction = (a) => {
|
|
126
|
+
if (!a) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (b) { /* any 1 */ }
|
|
131
|
+
if (c) { /* any 2 */ }
|
|
132
|
+
}
|
|
133
|
+
` },
|
|
134
|
+
{ code: `
|
|
135
|
+
const anyAction = (a) => {
|
|
136
|
+
if (!a) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (b) { /* any 1 */ }
|
|
141
|
+
if (c) { /* any 2 */ }
|
|
142
|
+
any()
|
|
143
|
+
}
|
|
144
|
+
` },
|
|
112
145
|
],
|
|
113
146
|
invalid: [
|
|
114
147
|
{ code: `
|
|
@@ -184,6 +217,15 @@ ruleTester.run('best-practice-for-unnesessary-early-return', rule, {
|
|
|
184
217
|
|
|
185
218
|
otherAction()
|
|
186
219
|
}
|
|
187
|
-
`, errors: [ SPLIT_EARLY_RETURN_ERROR ] },
|
|
220
|
+
`, errors: [ UNNESESSARY_EARLY_RETURN_ERROR, SPLIT_EARLY_RETURN_ERROR ] },
|
|
221
|
+
{ code: `
|
|
222
|
+
const anyAction = (a, b) => {
|
|
223
|
+
if (!a) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (b) { /* any 1 */ }
|
|
228
|
+
}
|
|
229
|
+
`, errors: [ SPLIT_CONFIG_ERROR ] },
|
|
188
230
|
]
|
|
189
231
|
})
|
package/test/trim-props.js
CHANGED
|
@@ -89,12 +89,12 @@ ruleTester.run('trim-props', rule, {
|
|
|
89
89
|
{
|
|
90
90
|
code: '<div data-spec={` a${b} c `}>....</div>',
|
|
91
91
|
output: '<div data-spec={`a${b} c`}>....</div>',
|
|
92
|
-
errors: [{ message: ERROR_MESSAGE }],
|
|
92
|
+
errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
code: '<div data-spec={` a${b ? ` ${c} ` : " "} d `}>....</div>',
|
|
96
96
|
output: '<div data-spec={`a${b ? ` ${c} ` : " "} d`}>....</div>',
|
|
97
|
-
errors: [{ message: ERROR_MESSAGE }],
|
|
97
|
+
errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
|
|
98
98
|
},
|
|
99
99
|
],
|
|
100
100
|
})
|