Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-smarthr

Package Overview
Dependencies
Maintainers
22
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-smarthr - npm Package Compare versions

Comparing version 0.3.6 to 0.3.7

7

CHANGELOG.md

@@ -5,2 +5,9 @@ # Changelog

### [0.3.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.6...v0.3.7) (2023-08-24)
### Features
* a11y-clickable-element-has-text のチェック時、リンク内部に名称の末尾がTextがつくコンポーネントがある場合、チェックを通過するように修正 ([#69](https://github.com/kufu/eslint-plugin-smarthr/issues/69)) ([182b5d5](https://github.com/kufu/eslint-plugin-smarthr/commit/182b5d5e52c1faee26011572c48271e4c03512e1))
### [0.3.6](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.5...v0.3.6) (2023-08-20)

@@ -7,0 +14,0 @@

2

package.json
{
"name": "eslint-plugin-smarthr",
"version": "0.3.6",
"version": "0.3.7",
"author": "SmartHR",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -18,9 +18,24 @@ const { generateTagFormatter } = require('../../libs/format_styled_components')

'Link$': 'Link$',
'Text$': 'Text$',
'Message$': 'Message$',
'^a$': '(Anchor|Link)$',
}
const filterFalsyJSXText = (cs) => cs.filter((c) => (
!(c.type === 'JSXText' && c.value.match(/^\s*\n+\s*$/))
))
const REGEX_NLSP = /^\s*\n+\s*$/
const REGEX_CLICKABLE_ELEMENT = /^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/
const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
const REGEX_TEXT_COMPONENT = /(Text|Message)$/
const HIT_TYPES_RECURSICVE_SEARCH = ['JSXText', 'JSXExpressionContainer']
const HIT_TEXT_ATTRS = ['visuallyHiddenText', 'alt']
const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
const checkFalsyJSXText = (c) => (
!(c.type === 'JSXText' && c.value.match(REGEX_NLSP))
)
const message = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください。
- 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
- クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`
module.exports = {

@@ -45,3 +60,3 @@ meta: {

if (!node.name.name || !node.name.name.match(/^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/)) {
if (!node.name.name || !node.name.name.match(REGEX_CLICKABLE_ELEMENT)) {
return

@@ -51,49 +66,47 @@ }

const recursiveSearch = (c) => {
if (['JSXText', 'JSXExpressionContainer'].includes(c.type)) {
if (HIT_TYPES_RECURSICVE_SEARCH.includes(c.type)) {
return true
}
if (c.type === 'JSXFragment') {
if (c.children && filterFalsyJSXText(c.children).some(recursiveSearch)) {
return true
switch (c.type) {
case 'JSXFragment': {
return c.children && filterFalsyJSXText(c.children).some(recursiveSearch)
}
case 'JSXElement': {
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
if (c.openingElement.name.name.match(REGEX_SMARTHR_LOGO)) {
return true
}
return false
}
const tagName = c.openingElement.name.name
if (c.type === 'JSXElement') {
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
if (c.openingElement.name.name.match(/SmartHRLogo$/)) {
return true
}
if (tagName.match(REGEX_TEXT_COMPONENT) || componentsWithText.includes(tagName)) {
return true
}
if (componentsWithText.includes(c.openingElement.name.name)) {
return true
}
// HINT: role & aria-label を同時に設定されている場合は許可
let existRole = false
let existAriaLabel = false
const result = c.openingElement.attributes.reduce((prev, a) => {
existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
// HINT: role & aria-label を同時に設定されている場合は許可
let existRole = false
let existAriaLabel = false
const result = c.openingElement.attributes.reduce((prev, a) => {
existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
if (
prev ||
!HIT_TEXT_ATTRS.includes(a.name.name)
) {
return prev
}
if (prev) {
return prev
}
return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
}, null)
if (!['visuallyHiddenText', 'alt'].includes(a.name.name)) {
return prev
if (
result ||
(existRole && existAriaLabel) ||
(c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
) {
return true
}
return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
}, null)
if (result || (existRole && existAriaLabel)) {
return true
}
if (c.children && filterFalsyJSXText(c.children).some(recursiveSearch)) {
return true
}
}

@@ -104,8 +117,6 @@

const child = filterFalsyJSXText(parentNode.children).find(recursiveSearch)
if (!child) {
if (!filterFalsyJSXText(parentNode.children).find(recursiveSearch)) {
context.report({
node,
message: 'a, button要素にはテキストを設定してください。要素内にアイコン、画像のみを設置する場合はSmartHR UIのvisuallyHiddenText、通常のHTML要素にはaltなどの代替テキスト用属性を指定してください',
message,
});

@@ -112,0 +123,0 @@ }

@@ -40,2 +40,8 @@ # smarthr/a11y-clickable-element-has-text

```jsx
<XxxAnchor>>
<XxxTextYyyy />
</XxxAnchor>
```
## ✅ Correct

@@ -70,2 +76,8 @@

```jsx
<XxxAnchor>>
<XxxText />
</XxxAnchor>
```
```jsx
/*

@@ -76,3 +88,3 @@ rules: {

{
componentsWithText: ['AnyComponent'],
componentsWithText: ['Hoge'],
},

@@ -84,4 +96,4 @@ ]

<XxxButton>
<AnyComponent />
<Hoge />
</XxxButton>
```

@@ -10,2 +10,5 @@ const { generateTagFormatter } = require('../../libs/format_styled_components')

const REGEX_IMG = /(img|image)$/i // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
const findAltAttr = (a) => a.name?.name === 'alt'
const isWithinSvgJsxElement = (node) => {

@@ -19,9 +22,12 @@ if (

if (!node.parent) {
return false
}
return isWithinSvgJsxElement(node.parent)
return node.parent ? isWithinSvgJsxElement(node.parent) : false
}
const MESSAGE_NOT_EXIST_ALT = `画像にはalt属性を指定してください。
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。`
const MESSAGE_NULL_ALT = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
module.exports = {

@@ -36,21 +42,24 @@ meta: {

JSXOpeningElement: (node) => {
const matcher = (node.name.name || '').match(/(img|image)$/i) // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
if (matcher) {
const alt = node.attributes.find((a) => a.name?.name === 'alt')
if (node.name.name) {
const matcher = node.name.name.match(REGEX_IMG)
let message = ''
if (matcher) {
const alt = node.attributes.find(findAltAttr)
if (!alt) {
if (matcher.input !== 'image' || !isWithinSvgJsxElement(node.parent)) {
message = '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。'
let message = ''
if (!alt) {
if (matcher.input !== 'image' || !isWithinSvgJsxElement(node.parent)) {
message = MESSAGE_NOT_EXIST_ALT
}
} else if (alt.value.value === '') {
message = MESSAGE_NULL_ALT
}
} else if (alt.value.value === '') {
message = '画像の情報をテキストにした代替テキスト(`alt`)を設定してください。装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。'
}
if (message) {
context.report({
node,
message,
});
if (message) {
context.report({
node,
message,
});
}
}

@@ -57,0 +66,0 @@ }

@@ -15,3 +15,5 @@ const rule = require('../rules/a11y-clickable-element-has-text')

const defaultErrorMessage = 'a, button要素にはテキストを設定してください。要素内にアイコン、画像のみを設置する場合はSmartHR UIのvisuallyHiddenText、通常のHTML要素にはaltなどの代替テキスト用属性を指定してください'
const defaultErrorMessage = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください。
- 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
- クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`

@@ -34,2 +36,4 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {

{ code: 'const HogeAnchor = styled(Anchor)(() => ``)' },
{ code: 'const FugaText = styled(HogeText)(() => ``)' },
{ code: 'const FugaMessage = styled(HogeMessage)(() => ``)' },
{

@@ -111,2 +115,11 @@ code: `<a>ほげ</a>`,

{
code: `<a><Text /></a>`,
},
{
code: `<a><HogeText /></a>`,
},
{
code: `<a><FormattedMessage /></a>`,
},
{
code: `<a><AnyComponent /></a>`,

@@ -132,2 +145,4 @@ options: [{

{ code: 'const Piyo = styled(Anchor)(() => ``)', errors: [ { message: `Piyoを正規表現 "/Anchor$/" がmatchする名称に変更してください` } ] },
{ code: 'const Hoge = styled(Text)``', errors: [ { message: `Hogeを正規表現 "/Text$/" がmatchする名称に変更してください` } ] },
{ code: 'const Hoge = styled(HogeMessage)``', errors: [ { message: `Hogeを正規表現 "/Message$/" がmatchする名称に変更してください` } ] },
{

@@ -182,2 +197,6 @@ code: `<a><img src="hoge.jpg" /></a>`,

{
code: `<a><TextWithHoge /></a>`,
errors: [{ message: defaultErrorMessage }]
},
{
code: `<a><AnyComponent /></a>`,

@@ -184,0 +203,0 @@ options: [{

@@ -15,2 +15,9 @@ const rule = require('../rules/a11y-image-has-alt-attribute')

const messageNotExistAlt = `画像にはalt属性を指定してください。
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。`
const messageNullAlt = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
ruleTester.run('a11y-image-has-alt-attribute', rule, {

@@ -43,6 +50,6 @@ valid: [

{ code: 'const Hoge = styled(Image)``', errors: [ { message: `Hogeを正規表現 "/Image$/" がmatchする名称に変更してください` } ] },
{ code: '<img />', errors: [ { message: '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。' } ] },
{ code: '<HogeImage alt="" />', errors: [ { message: '画像の情報をテキストにした代替テキスト(`alt`)を設定してください。装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。' } ] },
{ code: '<hoge><image /></hoge>', errors: [ { message: '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。' } ] },
{ code: '<img />', errors: [ { message: messageNotExistAlt } ] },
{ code: '<HogeImage alt="" />', errors: [ { message: messageNullAlt } ] },
{ code: '<hoge><image /></hoge>', errors: [ { message: messageNotExistAlt } ] },
]
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc