eslint-plugin-smarthr
Advanced tools
Comparing version 0.3.18 to 0.3.19
@@ -5,2 +5,9 @@ # Changelog | ||
### [0.3.19](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.18...v0.3.19) (2024-01-01) | ||
### Bug Fixes | ||
* a11y-delegate-element-has-role-presentation のインタラクティブな要素の判定を調整する ([#96](https://github.com/kufu/eslint-plugin-smarthr/issues/96)) ([63c9bda](https://github.com/kufu/eslint-plugin-smarthr/commit/63c9bda3b5d52baa3c1c2fd183ebee06d3a429d2)) | ||
### [0.3.18](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.17...v0.3.18) (2023-12-30) | ||
@@ -7,0 +14,0 @@ |
{ | ||
"name": "eslint-plugin-smarthr", | ||
"version": "0.3.18", | ||
"version": "0.3.19", | ||
"author": "SmartHR", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -41,3 +41,6 @@ const { generateTagFormatter } = require('../../libs/format_styled_components'); | ||
const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/ | ||
const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/ | ||
const INTERACTIVE_NODE_TYPE = ['JSXElement', 'JSXExpressionContainer', 'ConditionalExpression'] | ||
const messageNonInteractiveEventHandler = (nodeName, interactiveComponentRegex, onAttrs) => { | ||
@@ -51,3 +54,5 @@ const onAttrsText = onAttrs.join(', ') | ||
- 方法3: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください | ||
- 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください` | ||
- 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください | ||
- 'role="presentation"' を設定した要素はマークアップとしての意味がなくなるため、div・span などマークアップとしての意味を持たない要素に設定してください | ||
- 'role="presentation"' を設定する適切な要素が存在しない場合、div、またはspanでイベントが発生する要素を囲んだ上でrole属性を設定してください` | ||
} | ||
@@ -82,3 +87,28 @@ const messageRolePresentationNotHasInteractive = (nodeName, interactiveComponentRegex, onAttrs) => `${nodeName}に 'role="presentation"' が設定されているにも関わらず、子要素にinput、buttonやaなどのインタラクティブな要素が見つからないため、ブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。 | ||
const interactiveComponentRegex = new RegExp(`(${INTERACTIVE_COMPONENT_NAMES.join('|')}${options?.additionalInteractiveComponentRegex ? `|${options.additionalInteractiveComponentRegex.join('|')}` : ''})`) | ||
const isHasInteractive = (c) => { | ||
switch (c.type) { | ||
case 'JSXElement': { | ||
if ((c.openingElement.name.name || '').match(interactiveComponentRegex)) { | ||
return true | ||
} | ||
if (c.children.length > 0) { | ||
return !!c.children.find(isHasInteractive) | ||
} | ||
} | ||
case 'JSXExpressionContainer': | ||
case 'ConditionalExpression': { | ||
let expression = c | ||
if (c.expression) { | ||
expression = c.expression | ||
} | ||
return !![expression.right, expression.consequent, expression.alternate].find((ec) => INTERACTIVE_NODE_TYPE.includes(ec?.type) && isHasInteractive(ec)) | ||
} | ||
} | ||
return false | ||
} | ||
return { | ||
@@ -90,2 +120,3 @@ ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }), | ||
let onAttrs = [] | ||
let isMeanedRole = false | ||
let isRolePresentation = false | ||
@@ -98,4 +129,10 @@ | ||
onAttrs.push(aName) | ||
} else if (aName === 'role' && a.value?.value === 'presentation') { | ||
isRolePresentation = true | ||
} else if (aName === 'role') { | ||
const v = a.value?.value || '' | ||
if (v === 'presentation') { | ||
isMeanedRole = isRolePresentation = true | ||
} else if (v.match(MEANED_ROLE_REGEX)) { | ||
isMeanedRole = true | ||
} | ||
} | ||
@@ -107,27 +144,16 @@ }) | ||
if (!isRolePresentation) { | ||
context.report({ | ||
node, | ||
message: messageNonInteractiveEventHandler(nodeName, interactiveComponentRegex, onAttrs), | ||
}); | ||
} else { | ||
const isHasInteractive = (c) => { | ||
if (c.type === 'JSXElement') { | ||
if ((c.openingElement.name.name || '').match(interactiveComponentRegex)) { | ||
return true | ||
} | ||
if (c.children.length > 0) { | ||
return c.children.find(isHasInteractive) | ||
} | ||
} | ||
return false | ||
} | ||
if (!node.parent.children.find(isHasInteractive)) { | ||
// HINT: role="presentation"以外で意味があるroleが設定されている場合はエラーにしない | ||
// 基本的にsmarthr-uiでroleの設定などは巻き取る && そもそもroleを設定するよりタグを適切にマークアップすることが優先されるため | ||
// エラーなどには表示しない | ||
if (!isMeanedRole) { | ||
context.report({ | ||
node, | ||
message: messageRolePresentationNotHasInteractive(nodeName, interactiveComponentRegex, onAttrs) | ||
}) | ||
message: messageNonInteractiveEventHandler(nodeName, interactiveComponentRegex, onAttrs), | ||
}); | ||
} | ||
} else if (!node.parent.children.find(isHasInteractive)) { | ||
context.report({ | ||
node, | ||
message: messageRolePresentationNotHasInteractive(nodeName, interactiveComponentRegex, onAttrs) | ||
}) | ||
} | ||
@@ -134,0 +160,0 @@ } |
@@ -15,3 +15,3 @@ const rule = require('../rules/a11y-delegate-element-has-role-presentation'); | ||
const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|Pagination$|SideNav$|AccordionPanel$)/' | ||
const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$)/' | ||
const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => { | ||
@@ -25,3 +25,5 @@ const onAttrsText = onAttrs.join(', ') | ||
- 方法3: インタラクティブな親要素、もしくは子要素が存在しない場合、インタラクティブな要素を必ず持つようにマークアップを修正後、${onAttrsText}の設定要素を検討してください | ||
- 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください` | ||
- 方法4: インタラクティブな子要素から発生したイベントをキャッチすることが目的で${onAttrsText}を設定している場合、'role="presentation"' を設定してください | ||
- 'role="presentation"' を設定した要素はマークアップとしての意味がなくなるため、div・span などマークアップとしての意味を持たない要素に設定してください | ||
- 'role="presentation"' を設定する適切な要素が存在しない場合、div、またはspanでイベントが発生する要素を囲んだ上でrole属性を設定してください` | ||
} | ||
@@ -49,2 +51,8 @@ const messageRolePresentationNotHasInteractive = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => `${nodeName}に 'role="presentation"' が設定されているにも関わらず、子要素にinput、buttonやaなどのインタラクティブな要素が見つからないため、ブラウザが正しく解釈が行えず、ユーザーが利用することが出来ない場合があるため、以下のいずれかの対応をおこなってください。 | ||
{ code: '<Wrapper onClick={any} role="presentation"><any><Link /></any></Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any && <AnyButton />}</Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any || <AnyButton />}</Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any1 && (any2 || any3) && <AnyButton />}</Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any ? <AnyButton /> : null}</Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any1 ? (any2 ? <HogeLink /> : null) : null}</Wrapper>' }, | ||
{ code: '<Wrapper onClick={any} role="presentation">{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>' }, | ||
], | ||
@@ -59,5 +67,6 @@ invalid: [ | ||
{ code: '<Wrapper onClick={any}><Link /></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] }, | ||
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|Pagination$|SideNav$|AccordionPanel$|^Hoge$)/') } ] }, | ||
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|^Hoge$)/') } ] }, | ||
{ code: '<Wrapper onClick={any} onChange={anyany}><any><Link /></any></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick', 'onChange']) } ] }, | ||
{ code: '<Wrapper onClick={any}>{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] }, | ||
], | ||
}); |
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
263046
4351