eslint-plugin-smarthr 1.8.0 → 1.8.1
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 +9 -0
- package/package.json +2 -2
- package/rules/a11y-anchor-has-href-attribute/index.js +1 -1
- package/rules/a11y-delegate-element-has-role-presentation/index.js +21 -21
- package/rules/a11y-heading-in-sectioning-content/index.js +27 -9
- package/test/a11y-delegate-element-has-role-presentation.js +2 -2
- package/test/a11y-heading-in-sectioning-content.js +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
## [1.8.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.8.0...eslint-plugin-smarthr-v1.8.1) (2025-06-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* a11y-anchor-has-href-attribute での特殊分岐条件を react-router-dom から react-router に変更 ([#678](https://github.com/kufu/tamatebako/issues/678)) ([5941384](https://github.com/kufu/tamatebako/commit/59413842b5b7e5ca6ee88a511792ae6a81be988b))
|
|
11
|
+
* a11y-delegate-element-has-role-presentationでインタラクティブな要素を判定する際、コンポーネント名が複数形でも許容する ([#679](https://github.com/kufu/tamatebako/issues/679)) ([67567bd](https://github.com/kufu/tamatebako/commit/67567bde757624ed46649fb0401e24dc904865ba))
|
|
12
|
+
* a11y-heading-in-sectioning-contentでNavにaria-labelが設定されている場合を許容する ([#680](https://github.com/kufu/tamatebako/issues/680)) ([12f26be](https://github.com/kufu/tamatebako/commit/12f26be9d461d1580711263ae974ed948c74fc1d))
|
|
13
|
+
|
|
5
14
|
## [1.8.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.7.0...eslint-plugin-smarthr-v1.8.0) (2025-06-09)
|
|
6
15
|
|
|
7
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"eslintplugin",
|
|
38
38
|
"smarthr"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "18fc771c7e65e784d224990f2ce857c150cd69e2"
|
|
41
41
|
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
const INTERACTIVE_COMPONENT_NAMES = `(${[
|
|
2
|
-
'(B|b)utton',
|
|
3
|
-
'(Check|Combo)(B|b)ox',
|
|
4
|
-
'(Date(timeLocal)?|Time|Month|Wareki)Picker',
|
|
5
|
-
'(I|i)nput(File)?',
|
|
6
|
-
'(S|s)elect',
|
|
7
|
-
'(T|t)extarea',
|
|
8
|
-
'(ActionDialogWith|RemoteDialog)Trigger',
|
|
9
|
-
'AccordionPanel',
|
|
2
|
+
'(B|b)utton(s)?',
|
|
3
|
+
'(Check|Combo)(B|b)ox(es|s)?',
|
|
4
|
+
'(Date(timeLocal)?|Time|Month|Wareki)Picker(s)?',
|
|
5
|
+
'(I|i)nput(File)?(s)?',
|
|
6
|
+
'(S|s)elect(s)?',
|
|
7
|
+
'(T|t)extarea(s)?',
|
|
8
|
+
'(ActionDialogWith|RemoteDialog)Trigger(s)?',
|
|
9
|
+
'AccordionPanel(s)?',
|
|
10
10
|
'^a',
|
|
11
11
|
'Anchor',
|
|
12
|
-
'Link',
|
|
13
|
-
'DropZone',
|
|
14
|
-
'Field(S|s)et',
|
|
15
|
-
'FilterDropdown',
|
|
16
|
-
'(F|f)orm(Control|Group|Dialog)?',
|
|
17
|
-
'Pagination',
|
|
18
|
-
'RadioButton(Panel)?',
|
|
19
|
-
'RemoteTrigger(.+)Dialog',
|
|
20
|
-
'RightFixedNote',
|
|
21
|
-
'SegmentedControl',
|
|
22
|
-
'SideNav',
|
|
23
|
-
'Switch',
|
|
24
|
-
'TabItem',
|
|
12
|
+
'Link(s)?',
|
|
13
|
+
'DropZone(s)?',
|
|
14
|
+
'Field(S|s)et(s)?',
|
|
15
|
+
'FilterDropdown(s)?',
|
|
16
|
+
'(F|f)orm(Control|Group|Dialog)?(s)?',
|
|
17
|
+
'Pagination(s)?',
|
|
18
|
+
'RadioButton(Panel)?(s)?',
|
|
19
|
+
'RemoteTrigger(.+)Dialog(s)?',
|
|
20
|
+
'RightFixedNote(s)?',
|
|
21
|
+
'SegmentedControl(s)?',
|
|
22
|
+
'SideNav(s)?',
|
|
23
|
+
'Switch(s)?',
|
|
24
|
+
'TabItem(s)?',
|
|
25
25
|
].join('|')})$`
|
|
26
26
|
const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/
|
|
27
27
|
const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/
|
|
@@ -10,9 +10,9 @@ const ignoreCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
|
10
10
|
const noHeadingTagNamesRegex = /^(span|legend)$/
|
|
11
11
|
const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
12
12
|
const headingAttributeRegex = /^(heading|title)$/
|
|
13
|
+
const ariaLabelRegex = /^aria-label(ledby)?$/
|
|
13
14
|
|
|
14
15
|
const includeSectioningAsAttr = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
|
|
15
|
-
const findHeadingAttribute = (a) => headingAttributeRegex.test(a.name?.name || '')
|
|
16
16
|
|
|
17
17
|
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
18
18
|
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
|
|
@@ -250,23 +250,41 @@ module.exports = {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
} else if (!node.selfClosing) {
|
|
253
|
-
const isSection =
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
const isSection = elementName.match(sectioningRegex)
|
|
254
|
+
let isNav = false
|
|
255
|
+
|
|
256
|
+
if (isSection) {
|
|
257
|
+
isNav = isSection[1] === 'Nav'
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < node.attributes.length; i++) {
|
|
260
|
+
const attrName = node.attributes[i].name?.name || ''
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
// HINT: SectioningContent系コンポーネントの拡張の場合、title, heading属性などにHeadingのテキストが仕込まれている場合がある
|
|
264
|
+
// 対象属性を持っている場合はcorrectとして扱う
|
|
265
|
+
headingAttributeRegex.test(attrName) ||
|
|
266
|
+
// HINT: Navかつaria-labelを持っている場合も許容する
|
|
267
|
+
isNav && ariaLabelRegex.test(attrName)
|
|
268
|
+
) {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
}
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
const layoutSectionAsAttr = !isSection && layoutComponentRegex.test(elementName) ? node.attributes.find(includeSectioningAsAttr) : null
|
|
262
275
|
|
|
263
|
-
if (
|
|
276
|
+
if (
|
|
277
|
+
(isSection || layoutSectionAsAttr) &&
|
|
278
|
+
!searchBubbleUpSections(node.parent.parent) &&
|
|
279
|
+
!forInSearchChildren(node.parent.children)
|
|
280
|
+
) {
|
|
264
281
|
context.report({
|
|
265
282
|
node,
|
|
266
283
|
message: `${isSection ? elementName : `<${elementName} ${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}">`} はHeading要素を含んでいません。
|
|
267
284
|
- SectioningContentはHeadingを含むようにマークアップする必要があります
|
|
268
285
|
- ${elementName}に設定しているいずれかの属性がHeading,もしくはHeadingのテキストに該当する場合、その属性の名称を ${headingAttributeRegex.toString()} にマッチする名称に変更してください
|
|
269
|
-
- Headingにするべき適切な文字列が存在しない場合、 ${isSection ? `${elementName} は削除するか、SectioningContentではない要素に差し替えてください` : `${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}"を削除、もしくは別の要素に変更してください`}
|
|
286
|
+
- Headingにするべき適切な文字列が存在しない場合、 ${isSection ? `${elementName} は削除するか、SectioningContentではない要素に差し替えてください` : `${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}"を削除、もしくは別の要素に変更してください`}${isNav ? `
|
|
287
|
+
- nav要素の場合、aria-label、もしくはaria-labelledby属性を設定し、どんなナビゲーションなのかがわかる名称を設定してください` : ''}`,
|
|
270
288
|
})
|
|
271
289
|
}
|
|
272
290
|
}
|
|
@@ -11,7 +11,7 @@ const ruleTester = new RuleTester({
|
|
|
11
11
|
},
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
const defaultInteractiveMessage = '((B|b)utton
|
|
14
|
+
const defaultInteractiveMessage = '((B|b)utton(s)?|(Check|Combo)(B|b)ox(es|s)?|(Date(timeLocal)?|Time|Month|Wareki)Picker(s)?|(I|i)nput(File)?(s)?|(S|s)elect(s)?|(T|t)extarea(s)?|(ActionDialogWith|RemoteDialog)Trigger(s)?|AccordionPanel(s)?|^a|Anchor|Link(s)?|DropZone(s)?|Field(S|s)et(s)?|FilterDropdown(s)?|(F|f)orm(Control|Group|Dialog)?(s)?|Pagination(s)?|RadioButton(Panel)?(s)?|RemoteTrigger(.+)Dialog(s)?|RightFixedNote(s)?|SegmentedControl(s)?|SideNav(s)?|Switch(s)?|TabItem(s)?)$'
|
|
15
15
|
const defaultInteractiveRegex = `/(${defaultInteractiveMessage})/`
|
|
16
16
|
const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
|
|
17
17
|
const onAttrsText = onAttrs.join(', ')
|
|
@@ -50,7 +50,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
|
|
|
50
50
|
{ code: '<Wrapper onClick={any} role="presentation"><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }] },
|
|
51
51
|
{ code: '<Wrapper onClick={any} role="presentation"><any><Link /></any></Wrapper>' },
|
|
52
52
|
{ code: '<Wrapper onClick={any} role="presentation">{any && <AnyButton />}</Wrapper>' },
|
|
53
|
-
{ code: '<Wrapper onClick={any} role="presentation">{any || <
|
|
53
|
+
{ code: '<Wrapper onClick={any} role="presentation">{any || <AnyButtons />}</Wrapper>' },
|
|
54
54
|
{ code: '<Wrapper onClick={any} role="presentation">{any1 && (any2 || any3) && <AnyButton />}</Wrapper>' },
|
|
55
55
|
{ code: '<Wrapper onClick={any} role="presentation">{any ? <AnyButton /> : null}</Wrapper>' },
|
|
56
56
|
{ code: '<Wrapper onClick={any} role="presentation">{any1 ? (any2 ? <HogeLink /> : null) : null}</Wrapper>' },
|
|
@@ -19,10 +19,11 @@ const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存
|
|
|
19
19
|
const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
20
20
|
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
21
21
|
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
22
|
-
const notHaveHeadingMessage = (elementName) => `${elementName} はHeading要素を含んでいません。
|
|
22
|
+
const notHaveHeadingMessage = (elementName, isNav) => `${elementName} はHeading要素を含んでいません。
|
|
23
23
|
- SectioningContentはHeadingを含むようにマークアップする必要があります
|
|
24
24
|
- ${elementName}に設定しているいずれかの属性がHeading,もしくはHeadingのテキストに該当する場合、その属性の名称を /^(heading|title)$/ にマッチする名称に変更してください
|
|
25
|
-
- Headingにするべき適切な文字列が存在しない場合、 ${elementName} は削除するか、SectioningContent
|
|
25
|
+
- Headingにするべき適切な文字列が存在しない場合、 ${elementName} は削除するか、SectioningContentではない要素に差し替えてください${isNav ? `
|
|
26
|
+
- nav要素の場合、aria-label、もしくはaria-labelledby属性を設定し、どんなナビゲーションなのかがわかる名称を設定してください` : ''}`
|
|
26
27
|
|
|
27
28
|
ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
28
29
|
valid: [
|
|
@@ -48,6 +49,8 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
48
49
|
{ code: '<HogeStack forwardedAs="section"><div><Heading>hoge</Heading></div></HogeStack>' },
|
|
49
50
|
{ code: '<HogeBase as="aside"><Heading>hoge</Heading></HogeBase>' },
|
|
50
51
|
{ code: '<HogeBaseColumn forwardedAs="nav"><Heading>hoge</Heading></HogeBaseColumn>' },
|
|
52
|
+
{ code: '<HogeNav aria-label="any"><Any /></HogeNav>' },
|
|
53
|
+
{ code: '<HogeNav aria-labelledby="any"><Any /></HogeNav>' },
|
|
51
54
|
],
|
|
52
55
|
invalid: [
|
|
53
56
|
{ code: 'const StyledArticle = styled.article``', errors: [ { message: `"article"を利用せず、smarthr-ui/Articleを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.article" -> "styled(Article)")` } ] },
|
|
@@ -64,5 +67,7 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
64
67
|
{ code: '<Section></Section>', errors: [ { message: notHaveHeadingMessage('Section') } ] },
|
|
65
68
|
{ code: '<Aside><HogeSection></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') }, { message: notHaveHeadingMessage('HogeSection') } ] },
|
|
66
69
|
{ code: '<Aside any="hoge"><HogeSection><Heading /></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') } ] },
|
|
70
|
+
{ code: '<HogeNav><Any /></HogeNav>', errors: [ { message: notHaveHeadingMessage('HogeNav', true) } ] },
|
|
71
|
+
{ code: '<HogeNav aria-diabled="true"><Any /></HogeNav>', errors: [ { message: notHaveHeadingMessage('HogeNav', true) } ] },
|
|
67
72
|
],
|
|
68
73
|
});
|