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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -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
- elementName.match(headingRegex) &&
135
- !noHeadingTagNames.includes(node.attributes.find((a) => a.name?.name == 'tag')?.value.value)
136
- ) {
137
- const result = searchBubbleUp(node.parent)
138
-
139
- if (result) {
140
- if (elementName.match(/PageHeading$/)) {
141
- h1s.push(node)
142
-
143
- if (h1s.length > 1) {
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: pageHeadingMessage,
166
+ message: rootHeadingMessage,
147
167
  })
148
- } else if (result.type !== 'Program') {
149
- context.report({
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
  ],