eslint-plugin-smarthr
Advanced tools
Comparing version 0.3.3 to 0.3.4
@@ -5,2 +5,9 @@ # Changelog | ||
### [0.3.4](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.3...v0.3.4) (2023-07-18) | ||
### Features | ||
* 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)) | ||
### [0.3.3](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.2...v0.3.3) (2023-07-10) | ||
@@ -7,0 +14,0 @@ |
{ | ||
"name": "eslint-plugin-smarthr", | ||
"version": "0.3.3", | ||
"version": "0.3.4", | ||
"author": "SmartHR", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
const { generateTagFormatter } = require('../../libs/format_styled_components') | ||
const EXPECTED_NAMES = { | ||
'PageHeading$': 'PageHeading$', | ||
'Heading$': 'Heading$', | ||
'^h(1|2|3|4|5|6)$': 'Heading$', | ||
'^h1$': 'PageHeading$', | ||
'^h(|2|3|4|5|6)$': 'Heading$', | ||
'Article$': 'Article$', | ||
@@ -10,2 +12,3 @@ 'Aside$': 'Aside$', | ||
'Section$': 'Section$', | ||
'ModelessDialog$': 'ModelessDialog$', | ||
} | ||
@@ -17,8 +20,13 @@ | ||
const bareTagRegex = /^(article|aside|nav|section)$/ | ||
const messagePrefix = 'Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。' | ||
const messageSuffix = 'Sectioning Content(Article, Aside, Nav, Section)でHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。現在のマークアップの構造を変更したくない場合はSectioningFragmentコンポーネントを利用してください。' | ||
const modelessDialogRegex = /ModelessDialog$/ | ||
const commonMessage = `${messagePrefix}${messageSuffix}` | ||
const rootMessage = `${messagePrefix}コンポーネント全体に対するHeadingではない場合、${messageSuffix}コンポーネント全体に対するHeadingの場合、他のHeadingのアウトラインが明確に指定されればエラーにならなくなります。` | ||
const noHeadingTagNames = ['span', 'legend'] | ||
const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。 | ||
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。` | ||
const rootHeadingMessage = `${headingMessage} | ||
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。` | ||
const pageHeadingMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。' | ||
const pageHeadingInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。' | ||
const reportMessageBareToSHR = (tagName, visibleExample) => { | ||
@@ -43,4 +51,8 @@ const matcher = tagName.match(bareTagRegex) | ||
// Headingコンポーネントの拡張なので対象外 | ||
if (node.type === 'VariableDeclarator' && node.id.name.match(declaratorHeadingRegex)) { | ||
if ( | ||
// Headingコンポーネントの拡張なので対象外 | ||
node.type === 'VariableDeclarator' && node.parent.parent?.type === 'Program' && node.id.name.match(declaratorHeadingRegex) || | ||
// ModelessDialogのheaderにHeadingを設定している場合も対象外 | ||
node.type === 'JSXAttribute' && node.name.name === 'header' && node.parent.name.name.match(modelessDialogRegex) | ||
) { | ||
return null | ||
@@ -58,2 +70,3 @@ } | ||
create(context) { | ||
let h1s = [] | ||
let sections = [] | ||
@@ -93,27 +106,38 @@ let { VariableDeclarator, ...formatter } = generateTagFormatter({ context, EXPECTED_NAMES }) | ||
}) | ||
} else if (elementName.match(headingRegex)) { | ||
// Headingに明示的にtag属性が設定されており、それらが span or legend の場合はHeading扱いしない | ||
} else if ( | ||
elementName.match(headingRegex) && | ||
!noHeadingTagNames.includes(node.attributes.find((a) => a.name?.name == 'tag')?.value.value) | ||
) { | ||
const result = searchBubbleUp(node.parent) | ||
if (result) { | ||
const saved = sections.find((s) => s[0] === result) | ||
if (elementName.match(/PageHeading$/)) { | ||
h1s.push(node) | ||
// HINT: 最初の1つ目は通知しない() | ||
if (!saved) { | ||
sections.push([result, node]) | ||
if (h1s.length > 1) { | ||
context.report({ | ||
node, | ||
message: pageHeadingMessage, | ||
}) | ||
} else if (result.type !== 'Program') { | ||
context.report({ | ||
node, | ||
message: pageHeadingInSectionMessage, | ||
}) | ||
} | ||
} else if (result.type === 'Program') { | ||
context.report({ | ||
node, | ||
message: rootHeadingMessage, | ||
}) | ||
} else { | ||
// HINT: 同じファイルで同じSectioningContent or トップノードを持つ場合 | ||
const [section, unreport] = saved | ||
const targets = unreport ? [unreport, node] : [node] | ||
saved[1] = undefined | ||
targets.forEach((n) => { | ||
if (sections.find((s) => s === result)) { | ||
context.report({ | ||
node: n, | ||
message: | ||
section.type === 'Program' | ||
? rootMessage | ||
: commonMessage, | ||
node, | ||
message: headingMessage, | ||
}) | ||
}) | ||
} else { | ||
sections.push(result) | ||
} | ||
} | ||
@@ -120,0 +144,0 @@ } |
@@ -42,10 +42,15 @@ # smarthr/a11y-heading-in-sectioning-content | ||
## ✅ Correct | ||
```jsx | ||
<div> | ||
<Heading>hoge</Heading> // コンポーネント内にHeadingが一つのみの場合はOK | ||
</div> | ||
<> | ||
<PageHeading> // 同じファイル内に複数のPageHeadingが存在するとNG | ||
hoge | ||
</PageHeading> | ||
<PageHeading> | ||
fuga | ||
</PageHeading> | ||
</> | ||
``` | ||
## ✅ Correct | ||
```jsx | ||
@@ -61,12 +66,15 @@ <Section> | ||
```jsx | ||
<Section> | ||
<Heading> | ||
hoge | ||
</Heading> | ||
</Section> | ||
<StyledSection> | ||
<Heading> | ||
fuga | ||
</Heading> | ||
</StyledSection> | ||
<> | ||
<PageHeading>Page Name.</PageHeading> | ||
<Section> | ||
<Heading> | ||
hoge | ||
</Heading> | ||
</Section> | ||
<StyledSection> | ||
<Heading> | ||
fuga | ||
</Heading> | ||
</StyledSection> | ||
</> | ||
``` |
@@ -15,4 +15,8 @@ const rule = require('../rules/a11y-heading-in-sectioning-content'); | ||
const rootMessage = 'Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。コンポーネント全体に対するHeadingではない場合、Sectioning Content(Article, Aside, Nav, Section)でHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。現在のマークアップの構造を変更したくない場合はSectioningFragmentコンポーネントを利用してください。コンポーネント全体に対するHeadingの場合、他のHeadingのアウトラインが明確に指定されればエラーにならなくなります。' | ||
const message = 'Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。Sectioning Content(Article, Aside, Nav, Section)でHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。現在のマークアップの構造を変更したくない場合はSectioningFragmentコンポーネントを利用してください。' | ||
const lowerMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。 | ||
- smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。` | ||
const message = `${lowerMessage} | ||
- Headingをh1にしたい場合(機能名、ページ名などこのページ内でもっとも重要な見出しの場合)、smarthr-ui/PageHeadingを利用してください。その場合はSectionなどでアウトラインを示す必要はありません。` | ||
const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存在しています。PageHeadingはh1タグを出力するため最も重要な見出しにのみ利用してください。' | ||
const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。' | ||
@@ -22,3 +26,3 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, { | ||
{ code: `import styled from 'styled-components'` }, | ||
{ code: 'const HogeHeading = styled.h1``' }, | ||
{ code: 'const HogePageHeading = styled.h1``' }, | ||
{ code: 'const HogeHeading = styled.h2``' }, | ||
@@ -35,14 +39,10 @@ { code: 'const HogeHeading = styled.h3``' }, | ||
{ code: 'const FugaSection = styled(HogeSection)``' }, | ||
{ code: '<Heading>hoge</Heading>' }, | ||
{ code: '<HogeHeading>hoge</HogeHeading>' }, | ||
{ code: '<><Heading>hoge</Heading><Section><Heading>hoge</Heading></Section></>' }, | ||
{ code: '<><Heading>hoge</Heading><Aside><Heading>hoge</Heading></Aside></>' }, | ||
{ code: '<><Heading>hoge</Heading><Article><Heading>hoge</Heading></Article></>' }, | ||
{ code: '<><Heading>hoge</Heading><Nav><Heading>hoge</Heading></Nav></>' }, | ||
{ code: '<><Heading>hoge</Heading><SectioningFragment><Heading>hoge</Heading></SectioningFragment></>' }, | ||
{ code: 'const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>;const FugaHeading = () => <AbcHeading anyArg={abc}>hoge</AbcHeading>' }, | ||
{ code: '<PageHeading>hoge</PageHeading>' }, | ||
{ code: '<Section><Heading>hoge</Heading></Section>' }, | ||
{ code: '<><Section><Heading>hoge</Heading></Section><Section><Heading>fuga</Heading></Section></>' }, | ||
{ code: 'const HogeHeading = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>' }, | ||
], | ||
invalid: [ | ||
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] }, | ||
{ code: 'const Hoge = styled.h1``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const Hoge = styled.h1``', errors: [ { message: `Hogeを正規表現 "/PageHeading$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const Hoge = styled.h2``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] }, | ||
@@ -63,10 +63,9 @@ { code: 'const Hoge = styled.h3``', errors: [ { message: `Hogeを正規表現 "/Heading$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const StyledSection = styled.section``', errors: [ { message: `"section"を利用せず、smarthr-ui/Sectionを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.section" -> "styled(Section)")` } ] }, | ||
{ code: '<><Heading>hoge</Heading><Heading>hoge</Heading></>', errors: [ { message: rootMessage }, { message: rootMessage } ] }, | ||
{ code: '<><Heading>hoge</Heading><Section><Heading>hoge</Heading><Heading>hoge</Heading></Section></>', errors: [ { message }, { message } ] }, | ||
{ code: '<article>hoge</article>', errors: [ { message: `"article"を利用せず、smarthr-ui/Articleを拡張してください。Headingのレベルが自動計算されるようになります。` } ] }, | ||
{ code: '<aside>hoge</aside>', errors: [ { message: `"aside"を利用せず、smarthr-ui/Asideを拡張してください。Headingのレベルが自動計算されるようになります。` } ] }, | ||
{ code: '<nav>hoge</nav>', errors: [ { message: `"nav"を利用せず、smarthr-ui/Navを拡張してください。Headingのレベルが自動計算されるようになります。` } ] }, | ||
{ code: '<section>hoge</section>', errors: [ { message: `"section"を利用せず、smarthr-ui/Sectionを拡張してください。Headingのレベルが自動計算されるようになります。` } ] }, | ||
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>;const Fuga = () => <AbcHeading anyArg={abc}>hoge</AbcHeading>', errors: [ { message: rootMessage }, { message: rootMessage } ] }, | ||
{ code: '<><PageHeading>hoge</PageHeading><PageHeading>fuga</PageHeading></>', errors: [ { message: pageMessage } ] }, | ||
{ code: '<Heading>hoge</Heading>', errors: [ { message } ] }, | ||
{ code: '<><Heading>hoge</Heading><Heading>fuga</Heading></>', errors: [ { message }, { message } ] }, | ||
{ code: 'const Hoge = () => <FugaHeading anyArg={abc}>hoge</FugaHeading>', errors: [ { message } ] }, | ||
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] }, | ||
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] }, | ||
], | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
200133
3721