eslint-plugin-smarthr 0.3.11 → 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,14 @@
|
|
|
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
|
+
|
|
5
13
|
### [0.3.11](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.10...v0.3.11) (2023-09-27)
|
|
6
14
|
|
|
7
15
|
### [0.3.10](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.9...v0.3.10) (2023-09-20)
|
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)$/
|
|
@@ -53,6 +54,8 @@ const rootHeadingMessage = `${headingMessage}
|
|
|
53
54
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
54
55
|
const pageHeadingMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
55
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属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
56
59
|
|
|
57
60
|
const VariableDeclaratorBareToSHR = (context, node) => {
|
|
58
61
|
if (!node.init) {
|
|
@@ -103,6 +106,8 @@ const searchBubbleUp = (node) => {
|
|
|
103
106
|
return searchBubbleUp(node.parent)
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
const findTagAttr = (a) => a.name?.name == 'tag'
|
|
110
|
+
|
|
106
111
|
module.exports = {
|
|
107
112
|
meta: {
|
|
108
113
|
type: 'suggestion',
|
|
@@ -130,34 +135,38 @@ module.exports = {
|
|
|
130
135
|
message,
|
|
131
136
|
})
|
|
132
137
|
// Headingに明示的にtag属性が設定されており、それらが span or legend の場合はHeading扱いしない
|
|
133
|
-
} else if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
144
164
|
context.report({
|
|
145
165
|
node,
|
|
146
|
-
message:
|
|
166
|
+
message: rootHeadingMessage,
|
|
147
167
|
})
|
|
148
|
-
} else if (
|
|
149
|
-
|
|
150
|
-
node,
|
|
151
|
-
message: pageHeadingInSectionMessage,
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
} else if (result.type === 'Program') {
|
|
155
|
-
context.report({
|
|
156
|
-
node,
|
|
157
|
-
message: rootHeadingMessage,
|
|
158
|
-
})
|
|
159
|
-
} else {
|
|
160
|
-
if (sections.find((s) => s === result)) {
|
|
168
|
+
} else if (sections.find((s) => s === result)) {
|
|
169
|
+
hit = true
|
|
161
170
|
context.report({
|
|
162
171
|
node,
|
|
163
172
|
message: headingMessage,
|
|
@@ -166,6 +175,13 @@ module.exports = {
|
|
|
166
175
|
sections.push(result)
|
|
167
176
|
}
|
|
168
177
|
}
|
|
178
|
+
|
|
179
|
+
if (!hit && tagAttr) {
|
|
180
|
+
context.report({
|
|
181
|
+
node,
|
|
182
|
+
message: noTagAttrMessage,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
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)
|
|
@@ -20,6 +20,8 @@ const message = `${lowerMessage}
|
|
|
20
20
|
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
21
21
|
const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
22
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属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
23
25
|
|
|
24
26
|
ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
25
27
|
valid: [
|
|
@@ -90,5 +92,6 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
90
92
|
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>', errors: [ { message } ] },
|
|
91
93
|
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
|
|
92
94
|
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
|
|
95
|
+
{ code: '<Section><Heading tag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
93
96
|
],
|
|
94
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
|
],
|