eslint-plugin-smarthr 0.3.10 → 0.3.12
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
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
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.3.12](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.11...v0.3.12) (2023-10-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-heading-in-sectioning-contentに"SectioningContent内のsmarthr-ui/Heading系コンポーネントにtag属性が設定されている場合エラー"になる機能を追加 ([#85](https://github.com/kufu/eslint-plugin-smarthr/issues/85)) ([c34b4f4](https://github.com/kufu/eslint-plugin-smarthr/commit/c34b4f43da89d7baf48c2536dcd381327064ef75))
|
|
11
|
+
* a11y-image-has-alt-attributeでaria-describedbyが設定されている場合、エラーにならないように修正 ([#84](https://github.com/kufu/eslint-plugin-smarthr/issues/84)) ([046ee0f](https://github.com/kufu/eslint-plugin-smarthr/commit/046ee0f082381627343371235102304a3f2a0938))
|
|
12
|
+
|
|
13
|
+
### [0.3.11](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.10...v0.3.11) (2023-09-27)
|
|
14
|
+
|
|
5
15
|
### [0.3.10](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.9...v0.3.10) (2023-09-20)
|
|
6
16
|
|
|
7
17
|
|
package/package.json
CHANGED
|
@@ -37,6 +37,7 @@ const UNEXPECTED_NAMES = {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
40
|
+
const pageHeadingRegex = /PageHeading$/
|
|
40
41
|
const declaratorHeadingRegex = /Heading$/
|
|
41
42
|
const sectioningRegex = /((A(rticle|side))|Nav|Section|^SectioningFragment)$/
|
|
42
43
|
const bareTagRegex = /^(article|aside|nav|section)$/
|
|
@@ -46,11 +47,15 @@ const noHeadingTagNames = ['span', 'legend']
|
|
|
46
47
|
const ignoreHeadingCheckParentType = ['Program', 'ExportNamedDeclaration']
|
|
47
48
|
|
|
48
49
|
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
49
|
-
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeading
|
|
50
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。
|
|
51
|
+
- 'as="section"' などでアウトラインを示している場合、as属性を指定した要素をsmarthr-ui/SectioningFragmentでラップしてください。
|
|
52
|
+
- 要素内のHeadingのレベルが自動計算されるようになります。`
|
|
50
53
|
const rootHeadingMessage = `${headingMessage}
|
|
51
54
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
52
55
|
const pageHeadingMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
53
56
|
const pageHeadingInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
57
|
+
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Section, SectioningFragmentのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
58
|
+
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
54
59
|
|
|
55
60
|
const VariableDeclaratorBareToSHR = (context, node) => {
|
|
56
61
|
if (!node.init) {
|
|
@@ -101,6 +106,8 @@ const searchBubbleUp = (node) => {
|
|
|
101
106
|
return searchBubbleUp(node.parent)
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
const findTagAttr = (a) => a.name?.name == 'tag'
|
|
110
|
+
|
|
104
111
|
module.exports = {
|
|
105
112
|
meta: {
|
|
106
113
|
type: 'suggestion',
|
|
@@ -128,34 +135,38 @@ module.exports = {
|
|
|
128
135
|
message,
|
|
129
136
|
})
|
|
130
137
|
// Headingに明示的にtag属性が設定されており、それらが span or legend の場合はHeading扱いしない
|
|
131
|
-
} else if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
} else if (elementName.match(headingRegex)) {
|
|
139
|
+
const tagAttr = node.attributes.find(findTagAttr)
|
|
140
|
+
|
|
141
|
+
if (!noHeadingTagNames.includes(tagAttr?.value.value)) {
|
|
142
|
+
const result = searchBubbleUp(node.parent)
|
|
143
|
+
let hit = false
|
|
144
|
+
|
|
145
|
+
if (result) {
|
|
146
|
+
if (elementName.match(pageHeadingRegex)) {
|
|
147
|
+
h1s.push(node)
|
|
148
|
+
|
|
149
|
+
if (h1s.length > 1) {
|
|
150
|
+
hit = true
|
|
151
|
+
context.report({
|
|
152
|
+
node,
|
|
153
|
+
message: pageHeadingMessage,
|
|
154
|
+
})
|
|
155
|
+
} else if (result.type !== 'Program') {
|
|
156
|
+
hit = true
|
|
157
|
+
context.report({
|
|
158
|
+
node,
|
|
159
|
+
message: pageHeadingInSectionMessage,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
} else if (result.type === 'Program') {
|
|
163
|
+
hit = true
|
|
142
164
|
context.report({
|
|
143
165
|
node,
|
|
144
|
-
message:
|
|
166
|
+
message: rootHeadingMessage,
|
|
145
167
|
})
|
|
146
|
-
} else if (
|
|
147
|
-
|
|
148
|
-
node,
|
|
149
|
-
message: pageHeadingInSectionMessage,
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
} else if (result.type === 'Program') {
|
|
153
|
-
context.report({
|
|
154
|
-
node,
|
|
155
|
-
message: rootHeadingMessage,
|
|
156
|
-
})
|
|
157
|
-
} else {
|
|
158
|
-
if (sections.find((s) => s === result)) {
|
|
168
|
+
} else if (sections.find((s) => s === result)) {
|
|
169
|
+
hit = true
|
|
159
170
|
context.report({
|
|
160
171
|
node,
|
|
161
172
|
message: headingMessage,
|
|
@@ -164,6 +175,13 @@ module.exports = {
|
|
|
164
175
|
sections.push(result)
|
|
165
176
|
}
|
|
166
177
|
}
|
|
178
|
+
|
|
179
|
+
if (!hit && tagAttr) {
|
|
180
|
+
context.report({
|
|
181
|
+
node,
|
|
182
|
+
message: noTagAttrMessage,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
167
185
|
}
|
|
168
186
|
}
|
|
169
187
|
},
|
|
@@ -16,6 +16,7 @@ const UNEXPECTED_NAMES = {
|
|
|
16
16
|
const REGEX_IMG = /(img|image)$/i // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
|
|
17
17
|
|
|
18
18
|
const findAltAttr = (a) => a.name?.name === 'alt'
|
|
19
|
+
const findAriaDescribedbyAttr = (a) => a.name?.name === 'aria-describedby'
|
|
19
20
|
const findSpreadAttr = (a) => a.type === 'JSXSpreadAttribute'
|
|
20
21
|
const isWithinSvgJsxElement = (node) => {
|
|
21
22
|
if (
|
|
@@ -31,7 +32,9 @@ const isWithinSvgJsxElement = (node) => {
|
|
|
31
32
|
const MESSAGE_NOT_EXIST_ALT = `画像にはalt属性を指定してください。
|
|
32
33
|
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
|
|
33
34
|
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
|
|
34
|
-
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>'
|
|
35
|
+
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。
|
|
36
|
+
- 文字情報が多い場合や画像の前後の画像と同じ内容を設定したい場合などは aria-describedby属性を利用することもできます。
|
|
37
|
+
- aria-describedby属性を利用する場合でもalt属性を併用することができます。`
|
|
35
38
|
const MESSAGE_NULL_ALT = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
|
|
36
39
|
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
|
|
37
40
|
|
|
@@ -67,6 +70,7 @@ module.exports = {
|
|
|
67
70
|
|
|
68
71
|
if (!alt) {
|
|
69
72
|
if (
|
|
73
|
+
!node.attributes.find(findAriaDescribedbyAttr) &&
|
|
70
74
|
(
|
|
71
75
|
matcher.input !== 'image' ||
|
|
72
76
|
!isWithinSvgJsxElement(node.parent)
|
|
@@ -13,11 +13,15 @@ const ruleTester = new RuleTester({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
const lowerMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
16
|
-
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeading
|
|
16
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。
|
|
17
|
+
- 'as=\"section\"' などでアウトラインを示している場合、as属性を指定した要素をsmarthr-ui/SectioningFragmentでラップしてください。
|
|
18
|
+
- 要素内のHeadingのレベルが自動計算されるようになります。`
|
|
17
19
|
const message = `${lowerMessage}
|
|
18
20
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
19
21
|
const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
20
22
|
const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
23
|
+
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Section, SectioningFragmentのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
24
|
+
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
21
25
|
|
|
22
26
|
ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
23
27
|
valid: [
|
|
@@ -88,5 +92,6 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
88
92
|
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>', errors: [ { message } ] },
|
|
89
93
|
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
|
|
90
94
|
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
|
|
95
|
+
{ code: '<Section><Heading tag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
91
96
|
],
|
|
92
97
|
});
|
|
@@ -15,7 +15,9 @@ const ruleTester = new RuleTester({
|
|
|
15
15
|
const messageNotExistAlt = `画像にはalt属性を指定してください。
|
|
16
16
|
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
|
|
17
17
|
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
|
|
18
|
-
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>'
|
|
18
|
+
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。
|
|
19
|
+
- 文字情報が多い場合や画像の前後の画像と同じ内容を設定したい場合などは aria-describedby属性を利用することもできます。
|
|
20
|
+
- aria-describedby属性を利用する場合でもalt属性を併用することができます。`
|
|
19
21
|
const messageNullAlt = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
|
|
20
22
|
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
|
|
21
23
|
|
|
@@ -37,6 +39,8 @@ ruleTester.run('a11y-image-has-alt-attribute', rule, {
|
|
|
37
39
|
{ code: '<HogeImg alt="hoge" />' },
|
|
38
40
|
{ code: '<HogeImage alt="hoge" />' },
|
|
39
41
|
{ code: '<HogeIcon />' },
|
|
42
|
+
{ code: '<HogeImage aria-describedby="hoge" />' },
|
|
43
|
+
{ code: '<HogeImage aria-describedby="hoge" alt="fuga" />' },
|
|
40
44
|
{ code: '<svg><image /></svg>' },
|
|
41
45
|
{ code: '<AnyImg {...hoge} />', options: [{ checkType: 'smart' }] },
|
|
42
46
|
],
|