Skip to content

Commit

Permalink
feat: a11y-numbered-text-within-olを追加する (#105)
Browse files Browse the repository at this point in the history
* feat: a11y-numbered-text-within-olを追加する

* chore: テキストノードと属性の組み合わせでも連番判定を行えるように修正
  • Loading branch information
AtsushiM authored Feb 19, 2024
1 parent 5f3b594 commit 167b92f
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [a11y-image-has-alt-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-image-has-alt-attribute)
- [a11y-input-has-name-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-has-name-attribute)
- [a11y-input-in-form-control](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-in-form-control)
- [a11y-numbered-text-within-ol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-numbered-text-within-ol)
- [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
- [a11y-prohibit-useless-sectioning-fragment](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-useless-sectioning-fragment)
- [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
Expand Down
82 changes: 82 additions & 0 deletions rules/a11y-numbered-text-within-ol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# smarthr/a11y-numbered-text-within-ol

- "1. hoge", "2. fuga" ... のように連番のテキストをもつコンポーネントはol要素でマークアップすることを促すルールです
- ol要素でマークアップすることで連番テキストをもつ要素同士の関係、順番に意味があることを適切に示すことが出来ます

## rules

```js
{
rules: {
'smarthr/a11y-numbered-text-within-ol': 'error', // 'warn', 'off',
},
}
```

## ❌ Incorrect

```jsx
// ol要素で囲まれていないためNG
<Any>1. hoge</Any>
<Any>2. fuga</Any>

// 属性でも同様にチェックする
<Any title="1. hoge" />
<Any title="2. fuga" />

// ol要素内で連番を設定しているとNG
<OrderedList>
<li>1. hoge</li>
<li>2. fuga</li>
</OrderedList>

// 同一のol要素で囲まれていないためNG
<OrderedList>
<li>hoge</li>
</OrderedList>
<OrderedList>
<li>fuga</li>
</OrderedList>>

```

## ✅ Correct

```jsx
<ol>
<li>hoge</li>
<li>fuga</li>
</ol>

<OrderedList>
<Any title="hoge" />
<Any title="fuga" />
</OrderedList>

// デフォルトの連番からフォーマット、スタイルを変更したい場合
// counter-reset + counter-increment で表現する
// 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
<OrderedList>
<li>
<NumberedHeading>hoge</NumberedHeading>
<Any />
</li>
<li>
<NumberedHeading>fuga</NumberedHeading>
<Any />
</li>
</OrderedList>

...

const OrderedList = styled.ol`
list-style: none; // デフォルトのstyleを消す
counter-reset: hoge; // カウンターの名称。わかりやすいものなら何でもOK
`
const NumberedHeading = styled(Heading)`
&::before {
counter-increment: hoge; // hogeカウンターを+1する
content: "No " counter(hoge) ": "; // 表示される連番のフォーマット
}
`
```
134 changes: 134 additions & 0 deletions rules/a11y-numbered-text-within-ol/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const { generateTagFormatter } = require('../../libs/format_styled_components')

const EXPECTED_NAMES = {
'(Ordered(.*)List|^ol)$': '(Ordered(.*)List)$',
'(S|s)elect$': '(Select)$',
}
const UNEXPECTED_NAMES = EXPECTED_NAMES

const NUMBERED_TEXT_REGEX = /^[\s\n]*(([1-9])([^1-9]{2})[^\s\n]*)/
const ORDERED_LIST_REGEX = /(Ordered(.*)List|^ol)$/
const SELECT_REGEX = /(S|s)elect$/

const searchOrderedList = (node) => {
if (node.type === 'JSXElement' && node.openingElement.name?.name) {
const name = node.openingElement.name.name

if (name.match(ORDERED_LIST_REGEX)) {
return node.openingElement
} else if (name.match(SELECT_REGEX)) {
// HINT: select要素の場合、optionのラベルに連番がついている場合がありえるのでignoreする
// 通常と処理を分けるためnullではなく0を返す
return 0
}
}

if (node.type === 'Program') {
return null
}

return searchOrderedList(node.parent)
}

const checkNumberedTextInOl = (result, node, context) => {
if (result) {
context.report({
node,
message: `${result.name.name} 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)`,
})
}
}

const renderTag = (node) => `\`${node.name.name}="${node.value.value}"\``
const renderNode = (node, matcher) => node.type === 'JSXText' ? `\`${matcher[1]}\`` : renderTag(node)

const SCHEMA = []

module.exports = {
meta: {
type: 'problem',
schema: SCHEMA,
},
create(context) {
let firstNumber = 0
let firstNumberedNode = null
let firstNumberedMatcher = null

function checker(node, matcher) {
if (matcher) {
const result = searchOrderedList(node)

if (result !== 0) {
checkNumberedTextInOl(result, node, context)

const nowNumber = matcher[2] * 1

if (firstNumberedNode && nowNumber !== firstNumber) {
if (nowNumber === firstNumber + 1) {
const resultFirst = searchOrderedList(firstNumberedNode)

if (!resultFirst) {
if (!result) {
context.report({
node: firstNumberedNode,
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
- ${renderNode(firstNumberedNode, firstNumberedMatcher)}${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
})
} else {
context.report({
node: firstNumberedNode,
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
- ${renderNode(firstNumberedNode, firstNumberedMatcher)}${renderNode(node, matcher)} を囲んでいるol要素内(<${result.name.name}>)に存在するように修正してください
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${result.name.name}>)に存在する必要があります`,
})
}
} else {
if (!result) {
context.report({
node,
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
- ${renderNode(node, matcher)}${renderNode(firstNumberedNode, firstNumberedMatcher)} を囲んでいるol要素内(<${resultFirst.name.name}>)に存在するように修正してください
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${resultFirst.name.name}>)に存在する必要があります`,
})

firstNumberedNode = null
} else if (resultFirst !== result) {
context.report({
node,
message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
- ${renderNode(firstNumberedNode, firstNumberedMatcher)}${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
})
}
}
}

firstNumber = nowNumber
firstNumberedNode = node
firstNumberedMatcher = matcher
} else if (!firstNumberedNode || nowNumber === firstNumber) {
firstNumber = nowNumber
firstNumberedNode = node
firstNumberedMatcher = matcher
}
}
}
}

return {
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
JSXAttribute: (node) => {
if (node.value?.value) {
checker(node, node.value.value.match(NUMBERED_TEXT_REGEX))
}
},
JSXText: (node) => {
checker(node, node.value.match(NUMBERED_TEXT_REGEX))
},
}
},
}
module.exports.schema = SCHEMA
Loading

0 comments on commit 167b92f

Please sign in to comment.