eslint-plugin-smarthr 0.5.7 → 0.5.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 +18 -0
- package/README.md +1 -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 +3 -0
- package/rules/a11y-form-control-in-form/index.js +3 -0
- package/rules/a11y-heading-in-sectioning-content/README.md +1 -1
- package/rules/a11y-heading-in-sectioning-content/index.js +10 -5
- 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/README.md +1 -1
- 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 +6 -1
- package/rules/a11y-replace-unreadable-symbol/index.js +3 -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 +42 -6
- 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-heading-in-sectioning-content.js +4 -4
- package/test/a11y-prohibit-input-maxlength-attribute.js +39 -0
- package/test/a11y-prohibit-useless-sectioning-fragment.js +2 -0
- package/test/best-practice-for-layouts.js +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
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.9](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.7...v0.5.9) (2024-04-22)
|
|
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
|
+
* a11y系コンポーネントでBase,BaseColumnにas="section"などSectioningContentが指定された場合、outlineが切られていると判断するよう修正 ([#138](https://github.com/kufu/eslint-plugin-smarthr/issues/138)) ([7b62c62](https://github.com/kufu/eslint-plugin-smarthr/commit/7b62c6293cf057e97616992dd30fcf67b758f32f))
|
|
12
|
+
* 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))
|
|
13
|
+
* best-practice-for-layoutsでStackコンポーネントにgap={0}が指定されている場合、適切に置き換え・または削除を促すように修正 ([#137](https://github.com/kufu/eslint-plugin-smarthr/issues/137)) ([2a11919](https://github.com/kufu/eslint-plugin-smarthr/commit/2a11919ffa20ddbbc6a59210ec14d81d2d510cda))
|
|
14
|
+
|
|
15
|
+
### [0.5.8](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.7...v0.5.8) (2024-04-09)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* 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))
|
|
21
|
+
* 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))
|
|
22
|
+
|
|
5
23
|
### [0.5.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.6...v0.5.7) (2024-04-01)
|
|
6
24
|
|
|
7
25
|
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
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)
|
|
14
15
|
- [a11y-replace-unreadable-symbol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-replace-unreadable-symbol)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
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',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# smarthr/a11y-heading-in-sectioning-content
|
|
2
2
|
|
|
3
|
-
- Headingコンポーネントをsmarthr-ui/SectioningContent(Article, Aside, Nav, Section
|
|
3
|
+
- Headingコンポーネントをsmarthr-ui/SectioningContent(Article, Aside, Nav, Section) のいずれかで囲むことを促すルールです
|
|
4
4
|
- article, aside, nav, section で Heading とHeadingの対象となる範囲を囲むとブラウザが正確に解釈できるようになるメリットがあります
|
|
5
5
|
- またsmarthr-ui/SectioningContentで smarthr-ui/Headingを囲むことで、Headingのレベル(h1~h6)を自動的に計算するメリットもあります
|
|
6
6
|
- Headingコンポーネントをsmarthr-ui/Layout(Center, Reel, Sidebar, Stack) のいずれかで囲んでおり、かつas, forwardedAsのいずれかの属性で 'section', 'article', 'aside', 'nav' が指定されている場合、SectioningContentで囲んでいるものとして扱われるようになります
|
|
@@ -14,6 +14,8 @@ const EXPECTED_NAMES = {
|
|
|
14
14
|
'Reel$': 'Reel$',
|
|
15
15
|
'Sidebar$': 'Sidebar$',
|
|
16
16
|
'Stack$': 'Stack$',
|
|
17
|
+
'Base$': 'Base$',
|
|
18
|
+
'BaseColumn$': 'BaseColumn$',
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています
|
|
@@ -42,6 +44,8 @@ const UNEXPECTED_NAMES = {
|
|
|
42
44
|
'Reel$': '(Reel)$',
|
|
43
45
|
'Sidebar$': '(Sidebar)$',
|
|
44
46
|
'Stack$': '(Stack)$',
|
|
47
|
+
'Base$': '(Base)$',
|
|
48
|
+
'BaseColumn$': '(BaseColumn)$',
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
@@ -50,7 +54,7 @@ const declaratorHeadingRegex = /Heading$/
|
|
|
50
54
|
const sectioningRegex = /((A(rticle|side))|Nav|Section|^SectioningFragment)$/
|
|
51
55
|
const bareTagRegex = /^(article|aside|nav|section)$/
|
|
52
56
|
const modelessDialogRegex = /ModelessDialog$/
|
|
53
|
-
const layoutComponentRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
|
|
57
|
+
const layoutComponentRegex = /((C(ent|lust)er)|Reel|Sidebar|Stack|Base(Column)?)$/
|
|
54
58
|
const asRegex = /^(as|forwardedAs)$/
|
|
55
59
|
const ignoreCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
56
60
|
const noHeadingTagNamesRegex = /^(span|legend)$/
|
|
@@ -61,14 +65,12 @@ const includeSectioningAsAttr = (a) => a.name?.name.match(asRegex) && bareTagReg
|
|
|
61
65
|
const findHeadingAttribute = (a) => headingAttributeRegex.test(a.name?.name || '')
|
|
62
66
|
|
|
63
67
|
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
64
|
-
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeading
|
|
65
|
-
- 'as="section"' などでアウトラインを示している場合、as属性を指定した要素をsmarthr-ui/SectioningFragmentでラップしてください。
|
|
66
|
-
- 要素内のHeadingのレベルが自動計算されるようになります。`
|
|
68
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
|
|
67
69
|
const rootHeadingMessage = `${headingMessage}
|
|
68
70
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
69
71
|
const pageHeadingMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
70
72
|
const pageHeadingInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
71
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Section
|
|
73
|
+
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
72
74
|
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
73
75
|
|
|
74
76
|
const VariableDeclaratorBareToSHR = (context, node) => {
|
|
@@ -223,6 +225,9 @@ const forInSearchChildren = (ary) => {
|
|
|
223
225
|
|
|
224
226
|
const findTagAttr = (a) => a.name?.name == 'tag'
|
|
225
227
|
|
|
228
|
+
/**
|
|
229
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
230
|
+
*/
|
|
226
231
|
module.exports = {
|
|
227
232
|
meta: {
|
|
228
233
|
type: 'problem',
|
|
@@ -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
|
|
@@ -9,6 +9,8 @@ const EXPECTED_NAMES = {
|
|
|
9
9
|
'Reel$': '(Reel)$',
|
|
10
10
|
'Sidebar$': '(Sidebar)$',
|
|
11
11
|
'Stack$': '(Stack)$',
|
|
12
|
+
'Base$': '(Base)$',
|
|
13
|
+
'BaseColumn$': '(BaseColumn)$',
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
@@ -16,7 +18,7 @@ const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
|
16
18
|
const BARE_SECTIONING_TAG_REGEX = /^(article|aside|nav|section)$/
|
|
17
19
|
const SECTIONING_REGEX = /((A(rticle|side))|Nav|Section)$/
|
|
18
20
|
const SECTIONING_FRAGMENT_REGEX = /^SectioningFragment$/
|
|
19
|
-
const LAYOUT_REGEX = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
|
|
21
|
+
const LAYOUT_REGEX = /((C(ent|lust)er)|Reel|Sidebar|Stack|Base(Column)?)$/
|
|
20
22
|
const AS_REGEX = /^(as|forwardedAs)$/
|
|
21
23
|
|
|
22
24
|
const includeSectioningAsAttr = (a) => a.name?.name?.match(AS_REGEX) && a.value.value?.match(BARE_SECTIONING_TAG_REGEX)
|
|
@@ -34,6 +36,9 @@ const searchSectioningFragment = (node) => {
|
|
|
34
36
|
|
|
35
37
|
const SCHEMA = []
|
|
36
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
41
|
+
*/
|
|
37
42
|
module.exports = {
|
|
38
43
|
meta: {
|
|
39
44
|
type: 'problem',
|
|
@@ -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',
|
|
@@ -24,8 +24,6 @@ const checkFalsyJSXText = (c) => (
|
|
|
24
24
|
)
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
const findJustifyAttr = (a) => a.name?.name === 'justify'
|
|
28
|
-
|
|
29
27
|
const searchChildren = (node) => {
|
|
30
28
|
if (
|
|
31
29
|
node.type === 'JSXFragment' ||
|
|
@@ -55,6 +53,9 @@ const searchChildren = (node) => {
|
|
|
55
53
|
|
|
56
54
|
const SCHEMA = []
|
|
57
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
58
|
+
*/
|
|
58
59
|
module.exports = {
|
|
59
60
|
meta: {
|
|
60
61
|
type: 'problem',
|
|
@@ -70,21 +71,56 @@ module.exports = {
|
|
|
70
71
|
const matcher = nodeName.match(MULTI_CHILDREN_REGEX)
|
|
71
72
|
|
|
72
73
|
if (matcher) {
|
|
74
|
+
const layoutType = matcher[1]
|
|
75
|
+
let justifyAttr = null
|
|
76
|
+
let alignAttr = null
|
|
77
|
+
let gapAttr = null
|
|
78
|
+
|
|
79
|
+
node.attributes.forEach((a) => {
|
|
80
|
+
switch (a.name?.name) {
|
|
81
|
+
case 'justify':
|
|
82
|
+
justifyAttr = a
|
|
83
|
+
break
|
|
84
|
+
case 'align':
|
|
85
|
+
alignAttr = a
|
|
86
|
+
break
|
|
87
|
+
case 'gap':
|
|
88
|
+
gapAttr = a
|
|
89
|
+
break
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (layoutType === 'Stack') {
|
|
94
|
+
if (alignAttr && FLEX_END_REGEX.test(alignAttr.value.value)) {
|
|
95
|
+
return
|
|
96
|
+
} else if (gapAttr?.value.type === 'JSXExpressionContainer' && gapAttr.value.expression.value === 0) {
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
message: `${nodeName} に "gap={0}" が指定されており、smarthr-ui/${layoutType} の利用方法として誤っている可能性があります。以下の修正方法を検討してください。
|
|
100
|
+
- 方法1: 子要素を一つにまとめられないか検討してください
|
|
101
|
+
- 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
|
|
102
|
+
- 方法2: 子要素のstyleを確認しgap属性を0以外にできないか検討してください
|
|
103
|
+
- 子要素が個別に持っているmarginなどのstyleを${nodeName}のgap属性で共通化できないか確認してください
|
|
104
|
+
- 方法3: 別要素でマークアップし直すか、${nodeName}を削除してください
|
|
105
|
+
- 親要素に smarthr-ui/Cluster, smarthr-ui/Stack などが存在している場合、div・spanなどで1要素にまとめる必要がある場合があります
|
|
106
|
+
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください`
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
73
111
|
const children = node.parent.children.filter(checkFalsyJSXText)
|
|
74
112
|
|
|
75
113
|
if (children.length === 1) {
|
|
76
|
-
const layoutType = matcher[1]
|
|
77
|
-
const justifyAttr = layoutType === 'Cluster' ? node.attributes.find(findJustifyAttr) : null
|
|
78
|
-
|
|
79
114
|
if (justifyAttr && FLEX_END_REGEX.test(justifyAttr.value.value)) {
|
|
80
115
|
return
|
|
81
116
|
}
|
|
82
117
|
|
|
118
|
+
|
|
83
119
|
if (searchChildren(children[0])) {
|
|
84
120
|
context.report({
|
|
85
121
|
node,
|
|
86
122
|
message:
|
|
87
|
-
justifyAttr
|
|
123
|
+
(justifyAttr?.value.value === 'center' || alignAttr?.value.value === 'center')
|
|
88
124
|
? `${nodeName} は smarthr-ui/${layoutType} ではなく smarthr-ui/Center でマークアップしてください`
|
|
89
125
|
: `${nodeName}には子要素が一つしか無いため、${layoutType}でマークアップする意味がありません。
|
|
90
126
|
- 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',
|
|
@@ -13,14 +13,12 @@ const ruleTester = new RuleTester({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
const lowerMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
16
|
-
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeading
|
|
17
|
-
- 'as=\"section\"' などでアウトラインを示している場合、as属性を指定した要素をsmarthr-ui/SectioningFragmentでラップしてください。
|
|
18
|
-
- 要素内のHeadingのレベルが自動計算されるようになります。`
|
|
16
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
|
|
19
17
|
const message = `${lowerMessage}
|
|
20
18
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
21
19
|
const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
22
20
|
const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
23
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Section
|
|
21
|
+
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
24
22
|
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
25
23
|
const notHaveHeadingMessage = (elementName) => `${elementName} はHeading要素を含んでいません。
|
|
26
24
|
- SectioningContentはHeadingを含むようにマークアップする必要があります
|
|
@@ -74,6 +72,8 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
74
72
|
{ code: '<HogeReel forwardedAs="aside"><div><Heading>hoge</Heading></div></HogeReel>' },
|
|
75
73
|
{ code: '<HogeSidebar forwardedAs="nav"><div><Heading>hoge</Heading></div></HogeSidebar>' },
|
|
76
74
|
{ code: '<HogeStack forwardedAs="section"><div><Heading>hoge</Heading></div></HogeStack>' },
|
|
75
|
+
{ code: '<HogeBase as="aside"><Heading>hoge</Heading></HogeBase>' },
|
|
76
|
+
{ code: '<HogeBaseColumn forwardedAs="nav"><Heading>hoge</Heading></HogeBaseColumn>' },
|
|
77
77
|
],
|
|
78
78
|
invalid: [
|
|
79
79
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
@@ -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
|
+
})
|
|
@@ -27,5 +27,7 @@ ruleTester.run('a11y-prohibit-useless-sectioning-fragment', rule, {
|
|
|
27
27
|
{ code: `<SectioningFragment><AnyAside>hoge</AnyAside></SectioningFragment>`, errors: [ { message: error('<AnyAside>') } ] },
|
|
28
28
|
{ code: `<SectioningFragment><HogeStack as="aside">hoge</HogeStack></SectioningFragment>`, errors: [ { message: error('<HogeStack as="aside">') } ] },
|
|
29
29
|
{ code: `<SectioningFragment><HogeReel forwardedAs="nav">hoge</HogeReel></SectioningFragment>`, errors: [ { message: error('<HogeReel forwardedAs="nav">') } ] },
|
|
30
|
+
{ code: `<SectioningFragment><FugaBase as="article">hoge</FugaBase></SectioningFragment>`, errors: [ { message: error('<FugaBase as="article">') } ] },
|
|
31
|
+
{ code: `<SectioningFragment><FugaBaseColumn as="article">hoge</FugaBaseColumn></SectioningFragment>`, errors: [ { message: error('<FugaBaseColumn as="article">') } ] },
|
|
30
32
|
]
|
|
31
33
|
})
|
|
@@ -54,7 +54,9 @@ ruleTester.run('best-practice-for-button-element', rule, {
|
|
|
54
54
|
{ code: `<AnyCluster>{a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
|
|
55
55
|
{ code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.map(action)}</AnyCluster>` },
|
|
56
56
|
{ code: `<Cluster justify="flex-end">{a}</Cluster>` },
|
|
57
|
-
{ code: `<HogeCluster justify="end">{a}</HogeCluster>` },
|
|
57
|
+
{ code: `<HogeCluster justify="end" gap={0}>{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,15 @@ 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 でマークアップしてください' } ] },
|
|
88
|
+
{ code: `<HogeStack gap={0}>{a}{b}</HogeStack>`, errors: [ { message: `HogeStack に "gap={0}" が指定されており、smarthr-ui/Stack の利用方法として誤っている可能性があります。以下の修正方法を検討してください。
|
|
89
|
+
- 方法1: 子要素を一つにまとめられないか検討してください
|
|
90
|
+
- 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
|
|
91
|
+
- 方法2: 子要素のstyleを確認しgap属性を0以外にできないか検討してください
|
|
92
|
+
- 子要素が個別に持っているmarginなどのstyleをHogeStackのgap属性で共通化できないか確認してください
|
|
93
|
+
- 方法3: 別要素でマークアップし直すか、HogeStackを削除してください
|
|
94
|
+
- 親要素に smarthr-ui/Cluster, smarthr-ui/Stack などが存在している場合、div・spanなどで1要素にまとめる必要がある場合があります
|
|
95
|
+
- as, forwardedAsなどでSectioningContent系要素に変更している場合、対応するsmarthr-ui/Section, Aside, Nav, Article のいずれかに差し替えてください` } ] },
|
|
85
96
|
]
|
|
86
97
|
})
|
|
87
98
|
|