eslint-plugin-smarthr 0.3.3 → 0.3.4
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,13 @@
|
|
|
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.4](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.3...v0.3.4) (2023-07-18)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-heading-in-sectioning-content を smarthr-ui/PageHeading に対応させる ([#66](https://github.com/kufu/eslint-plugin-smarthr/issues/66)) ([5abc13c](https://github.com/kufu/eslint-plugin-smarthr/commit/5abc13c73f57242fbe8b6016bc6e598fd0403f1c))
|
|
11
|
+
|
|
5
12
|
### [0.3.3](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.2...v0.3.3) (2023-07-10)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED
|
@@ -39,14 +39,19 @@
|
|
|
39
39
|
</section>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
## ✅ Correct
|
|
43
|
-
|
|
44
42
|
```jsx
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
43
|
+
<>
|
|
44
|
+
<PageHeading> // 同じファイル内に複数のPageHeadingが存在するとNG
|
|
45
|
+
hoge
|
|
46
|
+
</PageHeading>
|
|
47
|
+
<PageHeading>
|
|
48
|
+
fuga
|
|
49
|
+
</PageHeading>
|
|
50
|
+
</>
|
|
48
51
|
```
|
|
49
52
|
|
|
53
|
+
## ✅ Correct
|
|
54
|
+
|
|
50
55
|
```jsx
|
|
51
56
|
<Section>
|
|
52
57
|
<Heading>hoge</Heading>
|
|
@@ -57,14 +62,17 @@
|
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
```jsx
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
<>
|
|
66
|
+
<PageHeading>Page Name.</PageHeading>
|
|
67
|
+
<Section>
|
|
68
|
+
<Heading>
|
|
69
|
+
hoge
|
|
70
|
+
</Heading>
|
|
71
|
+
</Section>
|
|
72
|
+
<StyledSection>
|
|
73
|
+
<Heading>
|
|
74
|
+
fuga
|
|
75
|
+
</Heading>
|
|
76
|
+
</StyledSection>
|
|
77
|
+
</>
|
|
70
78
|
```
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
2
|
|
|
3
3
|
const EXPECTED_NAMES = {
|
|
4
|
+
'PageHeading$': 'PageHeading$',
|
|
4
5
|
'Heading$': 'Heading$',
|
|
5
|
-
'^
|
|
6
|
+
'^h1$': 'PageHeading$',
|
|
7
|
+
'^h(|2|3|4|5|6)$': 'Heading$',
|
|
6
8
|
'Article$': 'Article$',
|
|
7
9
|
'Aside$': 'Aside$',
|
|
8
10
|
'Nav$': 'Nav$',
|
|
9
11
|
'Section$': 'Section$',
|
|
12
|
+
'ModelessDialog$': 'ModelessDialog$',
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/
|
|
13
16
|
const declaratorHeadingRegex = /Heading$/
|
|
14
17
|
const sectioningRegex = /((A(rticle|side))|Nav|Section|^SectioningFragment)$/
|
|
15
18
|
const bareTagRegex = /^(article|aside|nav|section)$/
|
|
16
|
-
const
|
|
17
|
-
const messageSuffix = 'Sectioning Content(Article, Aside, Nav, Section)でHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。現在のマークアップの構造を変更したくない場合はSectioningFragmentコンポーネントを利用してください。'
|
|
19
|
+
const modelessDialogRegex = /ModelessDialog$/
|
|
18
20
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const noHeadingTagNames = ['span', 'legend']
|
|
22
|
+
|
|
23
|
+
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
24
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
|
|
25
|
+
const rootHeadingMessage = `${headingMessage}
|
|
26
|
+
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
27
|
+
const pageHeadingMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
28
|
+
const pageHeadingInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
21
29
|
|
|
22
30
|
const reportMessageBareToSHR = (tagName, visibleExample) => {
|
|
23
31
|
const matcher = tagName.match(bareTagRegex)
|
|
@@ -38,8 +46,12 @@ const searchBubbleUp = (node) => {
|
|
|
38
46
|
return node
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
if (
|
|
50
|
+
// Headingコンポーネントの拡張なので対象外
|
|
51
|
+
node.type === 'VariableDeclarator' && node.parent.parent?.type === 'Program' && node.id.name.match(declaratorHeadingRegex) ||
|
|
52
|
+
// ModelessDialogのheaderにHeadingを設定している場合も対象外
|
|
53
|
+
node.type === 'JSXAttribute' && node.name.name === 'header' && node.parent.name.name.match(modelessDialogRegex)
|
|
54
|
+
) {
|
|
43
55
|
return null
|
|
44
56
|
}
|
|
45
57
|
|
|
@@ -52,6 +64,7 @@ module.exports = {
|
|
|
52
64
|
schema: [],
|
|
53
65
|
},
|
|
54
66
|
create(context) {
|
|
67
|
+
let h1s = []
|
|
55
68
|
let sections = []
|
|
56
69
|
let { VariableDeclarator, ...formatter } = generateTagFormatter({ context, EXPECTED_NAMES })
|
|
57
70
|
|
|
@@ -86,31 +99,42 @@ module.exports = {
|
|
|
86
99
|
node,
|
|
87
100
|
message,
|
|
88
101
|
})
|
|
89
|
-
|
|
102
|
+
// Headingに明示的にtag属性が設定されており、それらが span or legend の場合はHeading扱いしない
|
|
103
|
+
} else if (
|
|
104
|
+
elementName.match(headingRegex) &&
|
|
105
|
+
!noHeadingTagNames.includes(node.attributes.find((a) => a.name?.name == 'tag')?.value.value)
|
|
106
|
+
) {
|
|
90
107
|
const result = searchBubbleUp(node.parent)
|
|
91
108
|
|
|
92
109
|
if (result) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// HINT: 最初の1つ目は通知しない()
|
|
96
|
-
if (!saved) {
|
|
97
|
-
sections.push([result, node])
|
|
98
|
-
} else {
|
|
99
|
-
// HINT: 同じファイルで同じSectioningContent or トップノードを持つ場合
|
|
100
|
-
const [section, unreport] = saved
|
|
101
|
-
const targets = unreport ? [unreport, node] : [node]
|
|
102
|
-
|
|
103
|
-
saved[1] = undefined
|
|
110
|
+
if (elementName.match(/PageHeading$/)) {
|
|
111
|
+
h1s.push(node)
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
if (h1s.length > 1) {
|
|
106
114
|
context.report({
|
|
107
|
-
node
|
|
108
|
-
message:
|
|
109
|
-
section.type === 'Program'
|
|
110
|
-
? rootMessage
|
|
111
|
-
: commonMessage,
|
|
115
|
+
node,
|
|
116
|
+
message: pageHeadingMessage,
|
|
112
117
|
})
|
|
118
|
+
} else if (result.type !== 'Program') {
|
|
119
|
+
context.report({
|
|
120
|
+
node,
|
|
121
|
+
message: pageHeadingInSectionMessage,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
} else if (result.type === 'Program') {
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
message: rootHeadingMessage,
|
|
113
128
|
})
|
|
129
|
+
} else {
|
|
130
|
+
if (sections.find((s) => s === result)) {
|
|
131
|
+
context.report({
|
|
132
|
+
node,
|
|
133
|
+
message: headingMessage,
|
|
134
|
+
})
|
|
135
|
+
} else {
|
|
136
|
+
sections.push(result)
|
|
137
|
+
}
|
|
114
138
|
}
|
|
115
139
|
}
|
|
116
140
|
}
|
|
@@ -12,13 +12,17 @@ const ruleTester = new RuleTester({
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const lowerMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
|
|
16
|
+
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
|
|
17
|
+
const message = `${lowerMessage}
|
|
18
|
+
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。`
|
|
19
|
+
const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。'
|
|
20
|
+
const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
|
|
17
21
|
|
|
18
22
|
ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
19
23
|
valid: [
|
|
20
24
|
{ code: `import styled from 'styled-components'` },
|
|
21
|
-
{ code: 'const
|
|
25
|
+
{ code: 'const HogePageHeading = styled.h1``' },
|
|
22
26
|
{ code: 'const HogeHeading = styled.h2``' },
|
|
23
27
|
{ code: 'const HogeHeading = styled.h3``' },
|
|
24
28
|
{ code: 'const HogeHeading = styled.h4``' },
|
|
@@ -30,18 +34,14 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
30
34
|
{ code: 'const FugaAside = styled(HogeAside)``' },
|
|
31
35
|
{ code: 'const FugaNav = styled(HogeNav)``' },
|
|
32
36
|
{ code: 'const FugaSection = styled(HogeSection)``' },
|
|
33
|
-
{ code: '<
|
|
34
|
-
{ code: '<
|
|
35
|
-
{ code: '<><Heading>hoge</Heading><Section><Heading>
|
|
36
|
-
{ code: '
|
|
37
|
-
{ code: '<><Heading>hoge</Heading><Article><Heading>hoge</Heading></Article></>' },
|
|
38
|
-
{ code: '<><Heading>hoge</Heading><Nav><Heading>hoge</Heading></Nav></>' },
|
|
39
|
-
{ code: '<><Heading>hoge</Heading><SectioningFragment><Heading>hoge</Heading></SectioningFragment></>' },
|
|
40
|
-
{ code: 'const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>;const FugaHeading = () => <AbcHeading anyArg={abc}>hoge</AbcHeading>' },
|
|
37
|
+
{ code: '<PageHeading>hoge</PageHeading>' },
|
|
38
|
+
{ code: '<Section><Heading>hoge</Heading></Section>' },
|
|
39
|
+
{ code: '<><Section><Heading>hoge</Heading></Section><Section><Heading>fuga</Heading></Section></>' },
|
|
40
|
+
{ code: 'const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>' },
|
|
41
41
|
],
|
|
42
42
|
invalid: [
|
|
43
43
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
44
|
-
{ code: 'const Hoge = styled.h1``', errors: [ { message: `Hogeを正規表現 "/
|
|
44
|
+
{ code: 'const Hoge = styled.h1``', errors: [ { message: `Hogeを正規表現 "/PageHeading$/" がmatchする名称に変更してください` } ] },
|
|
45
45
|
{ code: 'const Hoge = styled.h2``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] },
|
|
46
46
|
{ code: 'const Hoge = styled.h3``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] },
|
|
47
47
|
{ code: 'const Hoge = styled.h4``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] },
|
|
@@ -57,12 +57,11 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
57
57
|
{ code: 'const StyledAside = styled.aside``', errors: [ { message: `"aside"を利用せず、smarthr-ui/Asideを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.aside" -> "styled(Aside)")` } ] },
|
|
58
58
|
{ code: 'const StyledNav = styled.nav``', errors: [ { message: `"nav"を利用せず、smarthr-ui/Navを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.nav" -> "styled(Nav)")` } ] },
|
|
59
59
|
{ code: 'const StyledSection = styled.section``', errors: [ { message: `"section"を利用せず、smarthr-ui/Sectionを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.section" -> "styled(Section)")` } ] },
|
|
60
|
-
{ code: '<><
|
|
61
|
-
{ code: '
|
|
62
|
-
{ code: '
|
|
63
|
-
{ code: '<
|
|
64
|
-
{ code: '<
|
|
65
|
-
{ code: '<
|
|
66
|
-
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>;const Fuga = () => <AbcHeading anyArg={abc}>hoge</AbcHeading>', errors: [ { message: rootMessage }, { message: rootMessage } ] },
|
|
60
|
+
{ code: '<><PageHeading>hoge</PageHeading><PageHeading>fuga</PageHeading></>', errors: [ { message: pageMessage } ] },
|
|
61
|
+
{ code: '<Heading>hoge</Heading>', errors: [ { message } ] },
|
|
62
|
+
{ code: '<><Heading>hoge</Heading><Heading>fuga</Heading></>', errors: [ { message }, { message } ] },
|
|
63
|
+
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>', errors: [ { message } ] },
|
|
64
|
+
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
|
|
65
|
+
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
|
|
67
66
|
],
|
|
68
67
|
});
|