eslint-plugin-smarthr
Advanced tools
Comparing version 0.3.7 to 0.3.8
@@ -5,2 +5,10 @@ # Changelog | ||
### [0.3.8](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.7...v0.3.8) (2023-09-01) | ||
### Features | ||
* a11y-anchor-has-href-attribute の next, react-router-dom用オプションをpackage.jsonを解析して自動設定するように修正 ([#71](https://github.com/kufu/eslint-plugin-smarthr/issues/71)) ([8321433](https://github.com/kufu/eslint-plugin-smarthr/commit/832143385dd92bfd6fe45acd959038deea5cd1fe)) | ||
* a11y-anchor-has-href-attributeをhref="" や href="#" の場合、エラーとなるように修正 ([#75](https://github.com/kufu/eslint-plugin-smarthr/issues/75)) ([738ab65](https://github.com/kufu/eslint-plugin-smarthr/commit/738ab6598111dcf573a35d24f9d1baeda0506b4f)) | ||
### [0.3.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.6...v0.3.7) (2023-08-24) | ||
@@ -7,0 +15,0 @@ |
{ | ||
"name": "eslint-plugin-smarthr", | ||
"version": "0.3.7", | ||
"version": "0.3.8", | ||
"author": "SmartHR", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -0,3 +1,25 @@ | ||
const JSON5 = require('json5') | ||
const fs = require('fs') | ||
const { generateTagFormatter } = require('../../libs/format_styled_components') | ||
const OPTION = (() => { | ||
const file = `${process.cwd()}/package.json` | ||
if (!fs.existsSync(file)) { | ||
return {} | ||
} | ||
const json = JSON5.parse(fs.readFileSync(file)) | ||
const dependencies = [ | ||
...Object.keys(json.dependencies || {}), | ||
...Object.keys(json.devDependencies || {}), | ||
] | ||
return { | ||
nextjs: dependencies.includes('next'), | ||
react_router: dependencies.includes('react-router-dom'), | ||
} | ||
})() | ||
const EXPECTED_NAMES = { | ||
@@ -10,15 +32,6 @@ 'Anchor$': 'Anchor$', | ||
const REGEX_TARGET = /(Anchor|Link|^a)$/ | ||
const check = (node, option) => { | ||
let result = baseCheck(node) | ||
const check = (node) => { | ||
const result = baseCheck(node) | ||
if ( | ||
result && ( | ||
(option.nextjs && !nextCheck(node)) || | ||
(option.react_router && !reactRouterCheck(node)) | ||
) | ||
) { | ||
result = null | ||
} | ||
return result | ||
return result && ((OPTION.nextjs && !nextCheck(node)) || (OPTION.react_router && !reactRouterCheck(node))) ? null : result | ||
} | ||
@@ -28,11 +41,3 @@ const baseCheck = (node) => { | ||
if (nodeName.match(REGEX_TARGET)) { | ||
const href = node.attributes.find((a) => a.name?.name == 'href') | ||
if (!href || !href.value) { | ||
return nodeName | ||
} | ||
} | ||
return false | ||
return nodeName.match(REGEX_TARGET) && checkExistAttribute(node, findHrefAttribute) ? nodeName : false | ||
} | ||
@@ -43,25 +48,30 @@ const nextCheck = (node) => { | ||
if (target) { | ||
return baseCheck(target) | ||
} | ||
return false | ||
return target ? baseCheck(target) : false | ||
} | ||
const reactRouterCheck = (node) => { | ||
const href = node.attributes.find((a) => a.name?.name == 'to') | ||
const reactRouterCheck = (node) => checkExistAttribute(node, findToAttribute) | ||
return !href || !href.value | ||
const checkExistAttribute = (node, find) => { | ||
const attr = node.attributes.find(find)?.value | ||
return ( | ||
!attr || | ||
isNullTextHref(attr) || | ||
(attr.type === 'JSXExpressionContainer' && isNullTextHref(attr.expression)) | ||
) | ||
} | ||
const isNullTextHref = (attr) => attr.type === 'Literal' && (attr.value === '' || attr.value === '#') | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
nextjs: { type: 'boolean' }, | ||
react_router: { type: 'boolean' }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
const findHrefAttribute = (a) => a.name?.name == 'href' | ||
const findToAttribute = (a) => a.name?.name == 'to' | ||
const MESSAGE_SUFFIX = ` に href 属性を正しく設定してください | ||
- onClickなどでページ遷移する場合でもhref属性に遷移先のURIを設定してください | ||
- Cmd + clickなどのキーボードショートカットに対応出来ます | ||
- onClickなどの動作がURLの変更を行わない場合、button要素でマークアップすることを検討してください | ||
- href属性に空文字(""など)や '#' が設定されている場合、実質画面遷移を行わないため、同様にbutton要素でマークアップすることを検討してください | ||
- リンクが存在せず無効化されていることを表したい場合、href属性に undefined を設定してください | ||
- button要素のdisabled属性が設定された場合に相当します` | ||
const SCHEMA = [] | ||
module.exports = { | ||
@@ -73,8 +83,6 @@ meta: { | ||
create(context) { | ||
const option = context.options[0] || {} | ||
return { | ||
...generateTagFormatter({ context, EXPECTED_NAMES }), | ||
JSXOpeningElement: (node) => { | ||
const nodeName = check(node, option) | ||
const nodeName = check(node) | ||
@@ -84,6 +92,3 @@ if (nodeName) { | ||
node, | ||
message: `${nodeName} に href 属性を設定してください。 | ||
- onClickなどでページ遷移する場合、href属性に遷移先のURIを設定してください。Cmd + clickなどのキーボードショートカットに対応出来ます。 | ||
- onClickなどの動作がURLの変更を行わない場合、リンクではなくbuttonでマークアップすることを検討してください。 | ||
- リンクを無効化することを表したい場合、href属性に undefined を設定してください。`, | ||
message: `${nodeName}${MESSAGE_SUFFIX}`, | ||
}) | ||
@@ -90,0 +95,0 @@ } |
@@ -15,9 +15,3 @@ # smarthr/a11y-anchor-has-href-attribute | ||
rules: { | ||
'smarthr/a11y-anchor-has-href-attribute': [ | ||
'error', // 'warn', 'off' | ||
// { | ||
// nextjs: true, | ||
// react_router: true, | ||
// }, | ||
] | ||
'smarthr/a11y-anchor-has-href-attribute': 'error', // 'warn', 'off' | ||
}, | ||
@@ -43,7 +37,7 @@ } | ||
// nextjs: true | ||
// nextを利用している場合 | ||
<Link href={hoge}><a>any</a></Link> | ||
// react_router: true | ||
// react-router-domを利用している場合 | ||
<Link to={hoge}>any</Link> | ||
``` |
@@ -16,11 +16,22 @@ const { generateTagFormatter } = require('../../libs/format_styled_components'); | ||
const INPUT_NAME_REGEX = /^[a-zA-Z0-9_\[\]]+$/ | ||
const INPUT_TAG_REGEX = /(i|I)nput$/ | ||
const findNameAttr = (a) => a?.name?.name === 'name' | ||
const findRadioInput = (a) => a.name?.name === 'type' && a.value.value === 'radio' | ||
const MESSAGE_PART_FORMAT = `"${INPUT_NAME_REGEX.toString()}"にmatchするフォーマットで命名してください` | ||
const MESSAGE_UNDEFINED_NAME_PART = ` | ||
- ブラウザの自動補完が有効化されるなどのメリットがあります | ||
- より多くのブラウザが自動補完を行える可能性を上げるため、${MESSAGE_PART_FORMAT}` | ||
const MESSAGE_UNDEFINED_FOR_RADIO = `にグループとなる他のinput[radio]と同じname属性を指定してください | ||
- 適切に指定することで同じname属性を指定したinput[radio]とグループが確立され、適切なキーボード操作を行えるようになります${MESSAGE_UNDEFINED_NAME_PART}` | ||
const MESSAGE_UNDEFINED_FOR_NOT_RADIO = `にname属性を指定してください${MESSAGE_UNDEFINED_NAME_PART}` | ||
const MESSAGE_NAME_FORMAT_SUFFIX = `はブラウザの自動補完が適切に行えない可能性があるため${MESSAGE_PART_FORMAT}` | ||
const SCHEMA = [] | ||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
messages: { | ||
'format-styled-components': '{{ message }}', | ||
'a11y-input-has-name-attribute': '{{ message }}', | ||
}, | ||
schema: [], | ||
schema: SCHEMA, | ||
}, | ||
@@ -33,33 +44,23 @@ create(context) { | ||
if (!nodeName.match(TARGET_TAG_NAME_REGEX)) { | ||
return | ||
} | ||
if (nodeName.match(TARGET_TAG_NAME_REGEX)) { | ||
const nameAttr = node.attributes.find(findNameAttr) | ||
const nameAttr = node.attributes.find((a) => a?.name?.name === 'name') | ||
if (!nameAttr) { | ||
const isRadio = | ||
nodeName.match(/RadioButton$/) || | ||
(nodeName.match(INPUT_TAG_REGEX) && node.attributes.some(findRadioInput)); | ||
if (!nameAttr) { | ||
const isRadio = | ||
nodeName.match(/RadioButton$/) || | ||
(nodeName.match(/(i|I)nput$/) && node.attributes.some( | ||
(a) => a.name?.name === 'type' && a.value.value === 'radio' | ||
)); | ||
context.report({ | ||
node, | ||
messageId: 'a11y-input-has-name-attribute', | ||
data: { | ||
message: `${nodeName} にname属性を指定してください。適切に指定することで${isRadio ? 'グループが確立され、キーボード操作しやすくなる' : 'ブラウザの自動補完が有効化される'}などのメリットがあります。`, | ||
}, | ||
}); | ||
} else { | ||
const nameValue = nameAttr.value?.value || '' | ||
if (nameValue && !nameValue.match(INPUT_NAME_REGEX)) { | ||
context.report({ | ||
node, | ||
messageId: 'a11y-input-has-name-attribute', | ||
data: { | ||
message: `${nodeName} のname属性の値(${nameValue})はブラウザの自動補完が適切に行えない可能性があるため ${INPUT_NAME_REGEX.toString()} にmatchするフォーマットで命名してください。`, | ||
}, | ||
message: `${nodeName} ${isRadio ? MESSAGE_UNDEFINED_FOR_RADIO : MESSAGE_UNDEFINED_FOR_NOT_RADIO}`, | ||
}); | ||
} else { | ||
const nameValue = nameAttr.value?.value || '' | ||
if (nameValue && !nameValue.match(INPUT_NAME_REGEX)) { | ||
context.report({ | ||
node, | ||
message: `${nodeName} のname属性の値(${nameValue})${MESSAGE_NAME_FORMAT_SUFFIX}`, | ||
}); | ||
} | ||
} | ||
@@ -71,2 +72,2 @@ } | ||
}; | ||
module.exports.schema = []; | ||
module.exports.schema = SCHEMA; |
@@ -0,1 +1,8 @@ | ||
const MESSAGE_NEW_DATE = `'new Date(arg)' のように引数を一つだけ指定したDate instanceの生成は実行環境によって結果が異なるため、以下のいずれかの方法に変更してください | ||
- 'new Date(2022, 12 - 1, 31)' のように数値を個別に指定する | ||
- dayjsなど、日付系ライブラリを利用する (例: 'dayjs(arg).toDate()')` | ||
const MESSAGE_PARSE = `Date.parse は実行環境によって結果が異なるため、以下のいずれかの方法に変更してください | ||
- 'new Date(2022, 12 - 1, 31).getTime()' のように数値を個別に指定する | ||
- dayjsなど、日付系ライブラリを利用する (例: 'dayjs(arg).valueOf()')` | ||
module.exports = { | ||
@@ -15,3 +22,3 @@ meta: { | ||
node, | ||
message: "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。", | ||
message: MESSAGE_NEW_DATE, | ||
}); | ||
@@ -27,3 +34,3 @@ } | ||
node, | ||
message: 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください', | ||
message: MESSAGE_PARSE, | ||
}); | ||
@@ -30,0 +37,0 @@ } |
@@ -15,6 +15,9 @@ const rule = require('../rules/a11y-anchor-has-href-attribute') | ||
const generateErrorText = (name) => `${name} に href 属性を設定してください。 | ||
- onClickなどでページ遷移する場合、href属性に遷移先のURIを設定してください。Cmd + clickなどのキーボードショートカットに対応出来ます。 | ||
- onClickなどの動作がURLの変更を行わない場合、リンクではなくbuttonでマークアップすることを検討してください。 | ||
- リンクを無効化することを表したい場合、href属性に undefined を設定してください。` | ||
const generateErrorText = (name) => `${name} に href 属性を正しく設定してください | ||
- onClickなどでページ遷移する場合でもhref属性に遷移先のURIを設定してください | ||
- Cmd + clickなどのキーボードショートカットに対応出来ます | ||
- onClickなどの動作がURLの変更を行わない場合、button要素でマークアップすることを検討してください | ||
- href属性に空文字(""など)や '#' が設定されている場合、実質画面遷移を行わないため、同様にbutton要素でマークアップすることを検討してください | ||
- リンクが存在せず無効化されていることを表したい場合、href属性に undefined を設定してください | ||
- button要素のdisabled属性が設定された場合に相当します` | ||
@@ -46,9 +49,4 @@ ruleTester.run('a11y-anchor-has-href-attribute', rule, { | ||
{ | ||
code: `<Link href="hoge"><a>ほげ</a></Link>`, | ||
options: [{ nextjs: true }], | ||
code: `<Link href="#fuga">ほげ</Link>`, | ||
}, | ||
{ | ||
code: `<Link to="hoge">ほげ</Link>`, | ||
options: [{ react_router: true }], | ||
}, | ||
], | ||
@@ -66,5 +64,9 @@ invalid: [ | ||
{ code: `<HogeLink href="hoge"><a>hoge</a></HogeLink>`, errors: [{ message: generateErrorText('a') }] }, | ||
{ code: `<HogeLink><a>hoge</a></HogeLink>`, options: [{ nextjs: true }], errors: [{ message: generateErrorText('a') }] }, | ||
{ code: `<HogeLink to="hoge">hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: `<HogeLink href="">hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: `<HogeLink href={""}>hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: `<HogeLink href={''}>hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: `<HogeLink href="#">hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: `<HogeLink href={'#'}>hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
] | ||
}) |
@@ -15,2 +15,8 @@ const rule = require('../rules/a11y-input-has-name-attribute'); | ||
const MESSAGE_SUFFIX = ` | ||
- ブラウザの自動補完が有効化されるなどのメリットがあります | ||
- より多くのブラウザが自動補完を行える可能性を上げるため、\"/^[a-zA-Z0-9_\\[\\]]+$/\"にmatchするフォーマットで命名してください` | ||
const MESSAGE_RADIO_SUFFIX = ` | ||
- 適切に指定することで同じname属性を指定したinput[radio]とグループが確立され、適切なキーボード操作を行えるようになります${MESSAGE_SUFFIX}` | ||
ruleTester.run('a11y-input-has-name-attribute', rule, { | ||
@@ -41,16 +47,16 @@ valid: [ | ||
{ code: 'const Hoge = styled(RadioButton)``', errors: [ { message: `Hogeを正規表現 "/RadioButton$/" がmatchする名称に変更してください` } ] }, | ||
{ code: '<input />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<input type="date" />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<Input type="checkbox" />', errors: [ { message: 'Input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<input type="radio" />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] }, | ||
{ code: '<HogeInput type="radio" />', errors: [ { message: 'HogeInput にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] }, | ||
{ code: '<HogeInput type="text" />', errors: [ { message: 'HogeInput にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<HogeRadioButton />', errors: [ { message: 'HogeRadioButton にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] }, | ||
{ code: '<select />', errors: [ { message: 'select にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<HogeSelect />', errors: [ { message: 'HogeSelect にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<textarea />', errors: [ { message: 'textarea にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<HogeTextarea />', errors: [ { message: 'HogeTextarea にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] }, | ||
{ code: '<input type="radio" name="ほげ" />', errors: [ { message: 'input のname属性の値(ほげ)はブラウザの自動補完が適切に行えない可能性があるため /^[a-zA-Z0-9_\\[\\]]+$/ にmatchするフォーマットで命名してください。' } ] }, | ||
{ code: '<select name="hoge[fuga][0][あいうえお]" />', errors: [ { message: 'select のname属性の値(hoge[fuga][0][あいうえお])はブラウザの自動補完が適切に行えない可能性があるため /^[a-zA-Z0-9_\\[\\]]+$/ にmatchするフォーマットで命名してください。' } ] }, | ||
{ code: '<input />', errors: [ { message: `input にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<input type="date" />', errors: [ { message: `input にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<Input type="checkbox" />', errors: [ { message: `Input にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<input type="radio" />', errors: [ { message: `input にグループとなる他のinput[radio]と同じname属性を指定してください${MESSAGE_RADIO_SUFFIX}` } ] }, | ||
{ code: '<HogeInput type="radio" />', errors: [ { message: `HogeInput にグループとなる他のinput[radio]と同じname属性を指定してください${MESSAGE_RADIO_SUFFIX}` } ] }, | ||
{ code: '<HogeInput type="text" />', errors: [ { message: `HogeInput にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<HogeRadioButton />', errors: [ { message: `HogeRadioButton にグループとなる他のinput[radio]と同じname属性を指定してください${MESSAGE_RADIO_SUFFIX}` } ] }, | ||
{ code: '<select />', errors: [ { message: `select にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<HogeSelect />', errors: [ { message: `HogeSelect にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<textarea />', errors: [ { message: `textarea にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<HogeTextarea />', errors: [ { message: `HogeTextarea にname属性を指定してください${MESSAGE_SUFFIX}` } ] }, | ||
{ code: '<input type="radio" name="ほげ" />', errors: [ { message: 'input のname属性の値(ほげ)はブラウザの自動補完が適切に行えない可能性があるため"/^[a-zA-Z0-9_\\[\\]]+$/"にmatchするフォーマットで命名してください' } ] }, | ||
{ code: '<select name="hoge[fuga][0][あいうえお]" />', errors: [ { message: 'select のname属性の値(hoge[fuga][0][あいうえお])はブラウザの自動補完が適切に行えない可能性があるため"/^[a-zA-Z0-9_\\[\\]]+$/"にmatchするフォーマットで命名してください' } ] }, | ||
], | ||
}); |
@@ -15,4 +15,8 @@ const rule = require('../rules/best-practice-for-date') | ||
const errorNewDate = "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。" | ||
const errorDateParse = 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください' | ||
const errorNewDate = `'new Date(arg)' のように引数を一つだけ指定したDate instanceの生成は実行環境によって結果が異なるため、以下のいずれかの方法に変更してください | ||
- 'new Date(2022, 12 - 1, 31)' のように数値を個別に指定する | ||
- dayjsなど、日付系ライブラリを利用する (例: 'dayjs(arg).toDate()')` | ||
const errorDateParse = `Date.parse は実行環境によって結果が異なるため、以下のいずれかの方法に変更してください | ||
- 'new Date(2022, 12 - 1, 31).getTime()' のように数値を個別に指定する | ||
- dayjsなど、日付系ライブラリを利用する (例: 'dayjs(arg).valueOf()')` | ||
@@ -19,0 +23,0 @@ ruleTester.run('best-practice-for-date', rule, { |
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
207836
3790
8