eslint-plugin-smarthr
Advanced tools
Comparing version 0.3.9 to 0.3.10
@@ -5,2 +5,11 @@ # Changelog | ||
### [0.3.10](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.9...v0.3.10) (2023-09-20) | ||
### Features | ||
* a11y-heading-in-sectioning-contentでSectioningContentと予想される名前だがそれらの拡張ではないコンポーネントはエラーとする ([#77](https://github.com/kufu/eslint-plugin-smarthr/issues/77)) ([f7248d5](https://github.com/kufu/eslint-plugin-smarthr/commit/f7248d597cb06ba0bab1d3f0d51956efefd04aac)) | ||
* a11y-xxx-has-yyy-attributeにcheckTypeオプションを追加 ([#81](https://github.com/kufu/eslint-plugin-smarthr/issues/81)) ([94a511a](https://github.com/kufu/eslint-plugin-smarthr/commit/94a511a412e62431282eb1980941862313dfb777)) | ||
* a11y系ruleにstyled-componentsで既存のコンポーネントなどを拡張する際、誤検知が発生しそうな名称が設定されている場合はエラーにする機能を追加 ([#80](https://github.com/kufu/eslint-plugin-smarthr/issues/80)) ([727ff3f](https://github.com/kufu/eslint-plugin-smarthr/commit/727ff3fc6116fca017f8c3a3e62af569b76863da)) | ||
### [0.3.9](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.8...v0.3.9) (2023-09-04) | ||
@@ -7,0 +16,0 @@ |
@@ -6,5 +6,10 @@ const STYLED_COMPONENTS_METHOD = 'styled' | ||
const generateTagFormatter = ({ context, EXPECTED_NAMES }) => { | ||
const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) => { | ||
const entriesesTagNames = Object.entries(EXPECTED_NAMES).map(([b, e]) => [ new RegExp(b), new RegExp(e) ]) | ||
const entriesesUnTagNames = UNEXPECTED_NAMES ? Object.entries(UNEXPECTED_NAMES).map(([b, e]) => { | ||
const [ auctualE, messageTemplate ] = Array.isArray(e) ? e : [e, ''] | ||
return [ new RegExp(b), new RegExp(auctualE), messageTemplate ] | ||
}) : [] | ||
return { | ||
@@ -67,2 +72,24 @@ ImportDeclaration: (node) => { | ||
}) | ||
entriesesUnTagNames.forEach(([b, e, m]) => { | ||
const matcher = extended.match(e) | ||
if (matcher && !base.match(b)) { | ||
const expected = matcher[1] | ||
const isBareTag = base === base.toLowerCase() | ||
const sampleFixBase = `styled${isBareTag ? `.${base}` : `(${base})`}` | ||
context.report({ | ||
node, | ||
message: m ? m | ||
.replaceAll('{{extended}}', extended) | ||
.replaceAll('{{expected}}', expected) : `${extended} は ${b.toString()} にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- ${extended} の名称の末尾が"${expected}" という文字列ではない状態にしつつ、"${base}"を継承していることをわかる名称に変更してください | ||
- もしくは"${base}"を"${extended}"の継承元であることがわかるような${isBareTag ? '適切なタグや別コンポーネントに差し替えてください' : '名称に変更するか、適切な別コンポーネントに差し替えてください'} | ||
- 修正例1: const ${extended.replace(expected, '')}Xxxx = ${sampleFixBase} | ||
- 修正例2: const ${extended}Xxxx = ${sampleFixBase} | ||
- 修正例3: const ${extended} = styled(Xxxx${expected})` | ||
}) | ||
} | ||
}) | ||
} | ||
@@ -69,0 +96,0 @@ }, |
{ | ||
"name": "eslint-plugin-smarthr", | ||
"version": "0.3.9", | ||
"version": "0.3.10", | ||
"author": "SmartHR", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -31,18 +31,31 @@ const JSON5 = require('json5') | ||
const UNEXPECTED_NAMES = { | ||
'(Anchor|^a)$': '(Anchor)$', | ||
'(Link|^a)$': '(Link)$', | ||
} | ||
const REGEX_TARGET = /(Anchor|Link|^a)$/ | ||
const check = (node) => { | ||
const result = baseCheck(node) | ||
const check = (node, checkType) => { | ||
const result = baseCheck(node, checkType) | ||
return result && ((OPTION.nextjs && !nextCheck(node)) || (OPTION.react_router && !reactRouterCheck(node))) ? null : result | ||
return result && ((OPTION.nextjs && !nextCheck(node, checkType)) || (OPTION.react_router && !reactRouterCheck(node))) ? null : result | ||
} | ||
const baseCheck = (node) => { | ||
const baseCheck = (node, checkType) => { | ||
const nodeName = node.name.name || '' | ||
return nodeName.match(REGEX_TARGET) && checkExistAttribute(node, findHrefAttribute) ? nodeName : false | ||
if ( | ||
nodeName.match(REGEX_TARGET) && | ||
checkExistAttribute(node, findHrefAttribute) && | ||
(checkType !== 'smart' || !node.attributes.some(findSpreadAttr)) | ||
) { | ||
return nodeName | ||
} | ||
return false | ||
} | ||
const nextCheck = (node) => { | ||
const nextCheck = (node, checkType) => { | ||
// HINT: next/link で `Link>a` という構造がありえるので直上のJSXElementを調べる | ||
const target = node.parent.parent.openingElement | ||
return target ? baseCheck(target) : false | ||
return target ? baseCheck(target, checkType) : false | ||
} | ||
@@ -61,2 +74,3 @@ const reactRouterCheck = (node) => checkExistAttribute(node, findToAttribute) | ||
const isNullTextHref = (attr) => attr.type === 'Literal' && (attr.value === '' || attr.value === '#') | ||
const findSpreadAttr = (a) => a.type === 'JSXSpreadAttribute' | ||
@@ -74,3 +88,11 @@ const findHrefAttribute = (a) => a.name?.name == 'href' | ||
const SCHEMA = [] | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
checkType: { type: 'string', enum: ['always', 'smart'], default: 'always' }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
@@ -83,6 +105,9 @@ module.exports = { | ||
create(context) { | ||
const option = context.options[0] || {} | ||
const checkType = option.checkType || 'always' | ||
return { | ||
...generateTagFormatter({ context, EXPECTED_NAMES }), | ||
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }), | ||
JSXOpeningElement: (node) => { | ||
const nodeName = check(node) | ||
const nodeName = check(node, checkType) | ||
@@ -89,0 +114,0 @@ if (nodeName) { |
@@ -9,2 +9,3 @@ # smarthr/a11y-anchor-has-href-attribute | ||
- 無効化されたリンクであることを表したい場合 `href={undefined}` を設定してください | ||
- checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます。 | ||
@@ -16,3 +17,6 @@ ## rules | ||
rules: { | ||
'smarthr/a11y-anchor-has-href-attribute': 'error', // 'warn', 'off' | ||
'smarthr/a11y-anchor-has-href-attribute': [ | ||
'error', // 'warn', 'off' | ||
// { checkType: 'always' } /* 'always' || 'smart' */ | ||
] | ||
}, | ||
@@ -29,2 +33,6 @@ } | ||
<XxxLink href>any</XxxLink> | ||
// checkType: 'always' | ||
<XxxAnchor {...args} /> | ||
<XxxLink {...args} any="any" /> | ||
``` | ||
@@ -44,2 +52,6 @@ | ||
<Link to={hoge}>any</Link> | ||
// checkType: 'smart' | ||
<XxxAnchor {...args} /> | ||
<XxxLink {...args} any="any" /> | ||
``` |
@@ -23,2 +23,8 @@ const { generateTagFormatter } = require('../../libs/format_styled_components') | ||
const UNEXPECTED_NAMES = { | ||
'(B|^b)utton$': '(Button)$', | ||
'(Anchor|^a)$': '(Anchor)$', | ||
'(Link|^a)$': '(Link)$', | ||
} | ||
const REGEX_NLSP = /^\s*\n+\s*$/ | ||
@@ -51,3 +57,3 @@ const REGEX_CLICKABLE_ELEMENT = /^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/ | ||
return { | ||
...generateTagFormatter({ context, EXPECTED_NAMES }), | ||
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }), | ||
JSXElement: (parentNode) => { | ||
@@ -54,0 +60,0 @@ // HINT: 閉じタグが存在しない === テキストノードが存在しない |
@@ -15,2 +15,26 @@ const { generateTagFormatter } = require('../../libs/format_styled_components') | ||
const unexpectedMessageTemplate = `{{extended}} は smarthr-ui/{{expected}} をextendすることを期待する名称になっています | ||
- childrenにHeadingを含まない場合、コンポーネントの名称から"{{expected}}"を取り除いてください | ||
- childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/{{expected}}をexendしてください | ||
- "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"{{expected}}"を設定し、そのコンポーネント内でsmarthr-ui/{{expected}}を利用してください` | ||
const UNEXPECTED_NAMES = { | ||
'(Heading|^h(1|2|3|4|5|6))$': '(Heading)$', | ||
'(A|^a)rticle$': [ | ||
'(Article)$', | ||
unexpectedMessageTemplate, | ||
], | ||
'(A|^a)side$': [ | ||
'(Aside)$', | ||
unexpectedMessageTemplate, | ||
], | ||
'(N|^n)av$': [ | ||
'(Nav)$', | ||
unexpectedMessageTemplate, | ||
], | ||
'(S|^s)ection$': [ | ||
'(Section)$', | ||
unexpectedMessageTemplate, | ||
], | ||
} | ||
const headingRegex = /((^h(1|2|3|4|5|6))|Heading)$/ | ||
@@ -32,2 +56,20 @@ const declaratorHeadingRegex = /Heading$/ | ||
const VariableDeclaratorBareToSHR = (context, node) => { | ||
if (!node.init) { | ||
return | ||
} | ||
const tag = node.init.tag || node.init | ||
if (tag.object?.name === 'styled') { | ||
const message = reportMessageBareToSHR(tag.property.name, true) | ||
if (message) { | ||
context.report({ | ||
node, | ||
message, | ||
}); | ||
} | ||
} | ||
} | ||
const reportMessageBareToSHR = (tagName, visibleExample) => { | ||
@@ -72,22 +114,7 @@ const matcher = tagName.match(bareTagRegex) | ||
let sections = [] | ||
let { VariableDeclarator, ...formatter } = generateTagFormatter({ context, EXPECTED_NAMES }) | ||
let { VariableDeclarator, ...formatter } = generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES, unexpectedMessageTemplate }) | ||
formatter.VariableDeclarator = (node) => { | ||
VariableDeclarator(node) | ||
if (!node.init) { | ||
return | ||
} | ||
const tag = node.init.tag || node.init | ||
if (tag.object?.name === 'styled') { | ||
const message = reportMessageBareToSHR(tag.property.name, true) | ||
if (message) { | ||
context.report({ | ||
node, | ||
message, | ||
}); | ||
} | ||
} | ||
VariableDeclaratorBareToSHR(context, node) | ||
} | ||
@@ -94,0 +121,0 @@ |
@@ -10,5 +10,12 @@ const { generateTagFormatter } = require('../../libs/format_styled_components') | ||
const UNEXPECTED_NAMES = { | ||
'(Img|^(img|svg))$': '(Img)$', | ||
'(Image|^(img|svg))$': '(Image)$', | ||
'(Icon|^(img|svg))$': '(Icon)$', | ||
} | ||
const REGEX_IMG = /(img|image)$/i // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする | ||
const findAltAttr = (a) => a.name?.name === 'alt' | ||
const findSpreadAttr = (a) => a.type === 'JSXSpreadAttribute' | ||
const isWithinSvgJsxElement = (node) => { | ||
@@ -32,10 +39,23 @@ if ( | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
checkType: { type: 'string', enum: ['always', 'smart'], default: 'always' }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
schema: [], | ||
schema: SCHEMA, | ||
}, | ||
create(context) { | ||
const option = context.options[0] || {} | ||
const checkType = option.checkType || 'always' | ||
return { | ||
...generateTagFormatter({ context, EXPECTED_NAMES }), | ||
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }), | ||
JSXOpeningElement: (node) => { | ||
@@ -51,3 +71,12 @@ if (node.name.name) { | ||
if (!alt) { | ||
if (matcher.input !== 'image' || !isWithinSvgJsxElement(node.parent)) { | ||
if ( | ||
( | ||
matcher.input !== 'image' || | ||
!isWithinSvgJsxElement(node.parent) | ||
) && | ||
( | ||
checkType !== 'smart' || | ||
!node.attributes.some(findSpreadAttr) | ||
) | ||
) { | ||
message = MESSAGE_NOT_EXIST_ALT | ||
@@ -71,2 +100,2 @@ } | ||
} | ||
module.exports.schema = [] | ||
module.exports.schema = SCHEMA |
# smarthr/a11y-image-has-alt-attribute | ||
- 画像やアイコンにalt属性を設定することを強制するルールです | ||
- checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます。 | ||
@@ -10,3 +11,6 @@ ## rules | ||
rules: { | ||
'smarthr/a11y-image-has-alt-attribute': 'error', // 'warn', 'off' | ||
'smarthr/a11y-image-has-alt-attribute': [ | ||
'error', // 'warn', 'off' | ||
// { checkType: 'always' } /* 'always' || 'smart' */ | ||
] | ||
}, | ||
@@ -30,2 +34,7 @@ } | ||
```jsx | ||
// checkType: 'always' | ||
<XxxImage {...args} /> | ||
<YyyIcon {...args} any="any" /> | ||
``` | ||
```jsx | ||
import styled from 'styled-components' | ||
@@ -52,2 +61,7 @@ | ||
```jsx | ||
// checkType: 'smart' | ||
<XxxImage {...args} /> | ||
<YyyIcon {...args} any="any" /> | ||
``` | ||
```jsx | ||
import styled from 'styled-components' | ||
@@ -54,0 +68,0 @@ |
@@ -17,4 +17,6 @@ const { generateTagFormatter } = require('../../libs/format_styled_components'); | ||
const INPUT_TAG_REGEX = /(i|I)nput$/ | ||
const RADIO_BUTTON_REGEX = /RadioButton$/ | ||
const findNameAttr = (a) => a?.name?.name === 'name' | ||
const findSpreadAttr = (a) => a.type === 'JSXSpreadAttribute' | ||
const findRadioInput = (a) => a.name?.name === 'type' && a.value.value === 'radio' | ||
@@ -31,3 +33,11 @@ | ||
const SCHEMA = [] | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
checkType: { type: 'string', enum: ['always', 'smart'], default: 'always' }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
@@ -40,2 +50,5 @@ module.exports = { | ||
create(context) { | ||
const option = context.options[0] || {} | ||
const checkType = option.checkType || 'always' | ||
return { | ||
@@ -50,10 +63,16 @@ ...generateTagFormatter({ context, EXPECTED_NAMES }), | ||
if (!nameAttr) { | ||
const isRadio = | ||
nodeName.match(/RadioButton$/) || | ||
(nodeName.match(INPUT_TAG_REGEX) && node.attributes.some(findRadioInput)); | ||
if ( | ||
node.attributes.length === 0 || | ||
checkType !== 'smart' || | ||
!node.attributes.some(findSpreadAttr) | ||
) { | ||
const isRadio = | ||
nodeName.match(RADIO_BUTTON_REGEX) || | ||
(nodeName.match(INPUT_TAG_REGEX) && node.attributes.some(findRadioInput)); | ||
context.report({ | ||
node, | ||
message: `${nodeName} ${isRadio ? MESSAGE_UNDEFINED_FOR_RADIO : MESSAGE_UNDEFINED_FOR_NOT_RADIO}`, | ||
}); | ||
context.report({ | ||
node, | ||
message: `${nodeName} ${isRadio ? MESSAGE_UNDEFINED_FOR_RADIO : MESSAGE_UNDEFINED_FOR_NOT_RADIO}`, | ||
}); | ||
} | ||
} else { | ||
@@ -60,0 +79,0 @@ const nameValue = nameAttr.value?.value || '' |
@@ -7,2 +7,3 @@ # smarthr/a11y-input-has-name-attribute | ||
- input[type="radio"] は name を適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。 | ||
- checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます。 | ||
@@ -14,3 +15,6 @@ ## rules | ||
rules: { | ||
'smarthr/a11y-input-has-name-attribute': 'error', // 'warn', 'off' | ||
'smarthr/a11y-input-has-name-attribute': [ | ||
'error', // 'warn', 'off' | ||
// { checkType: 'always' } /* 'always' || 'smart' */ | ||
] | ||
}, | ||
@@ -28,2 +32,6 @@ } | ||
<Select /> | ||
// checkType: 'always' | ||
<AnyInput {...args} /> | ||
<AnyInput {...args} any="any" /> | ||
``` | ||
@@ -48,2 +56,6 @@ | ||
<Select name="piyo" /> | ||
// checkType: 'smart' | ||
<AnyInput {...args} /> | ||
<AnyInput {...args} any="any" /> | ||
``` | ||
@@ -50,0 +62,0 @@ |
const path = require('path') | ||
const fs = require('fs') | ||
const { replacePaths, rootPath } = require('../../libs/common') | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
allowedImports: { | ||
type: 'object', | ||
patternProperties: { | ||
'.+': { | ||
type: 'object', | ||
patternProperties: { | ||
'.+': { | ||
type: ['boolean', 'array' ], | ||
items: { | ||
type: 'string', | ||
}, | ||
additionalProperties: false | ||
} | ||
} | ||
}, | ||
}, | ||
additionalProperties: true, | ||
}, | ||
ignores: { type: 'array', items: { type: 'string' }, default: [] }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
const entriedReplacePaths = Object.entries(replacePaths) | ||
const CWD = process.cwd() | ||
const REGEX_UNNECESSARY_SLASH = /(\/)+/g | ||
const REGEX_ROOT_PATH = new RegExp(`^${rootPath}/index\.`) | ||
const REGEX_INDEX_FILE = /\/index\.(ts|js)x?$/ | ||
const TARGET_EXTS = ['ts', 'tsx', 'js', 'jsx'] | ||
const calculateAbsoluteImportPath = (source) => { | ||
@@ -9,10 +45,10 @@ if (source[0] === '/') { | ||
return Object.entries(replacePaths).reduce((prev, [key, values]) => { | ||
return entriedReplacePaths.reduce((prev, [key, values]) => { | ||
if (source === prev) { | ||
const regexp = new RegExp(`^${key}(.+)$`) | ||
return values.reduce((p, v) => { | ||
if (prev === p) { | ||
const regexp = new RegExp(`^${key}(.+)$`) | ||
if (prev.match(regexp)) { | ||
return p.replace(regexp, `${path.resolve(`${process.cwd()}/${v}`)}/$1`) | ||
return p.replace(regexp, `${path.resolve(`${CWD}/${v}`)}/$1`) | ||
} | ||
@@ -29,10 +65,10 @@ } | ||
const calculateReplacedImportPath = (source) => { | ||
return Object.entries(replacePaths).reduce((prev, [key, values]) => { | ||
return entriedReplacePaths.reduce((prev, [key, values]) => { | ||
if (source === prev) { | ||
return values.reduce((p, v) => { | ||
if (prev === p) { | ||
const regexp = new RegExp(`^${path.resolve(`${process.cwd()}/${v}`)}(.+)$`) | ||
const regexp = new RegExp(`^${path.resolve(`${CWD}/${v}`)}(.+)$`) | ||
if (prev.match(regexp)) { | ||
return p.replace(regexp, `${key}/$1`).replace(/(\/)+/g, '/') | ||
return p.replace(regexp, `${key}/$1`).replace(REGEX_UNNECESSARY_SLASH, '/') | ||
} | ||
@@ -48,31 +84,6 @@ } | ||
} | ||
const TARGET_EXTS = ['ts', 'tsx', 'js', 'jsx'] | ||
const SCHEMA = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
allowedImports: { | ||
type: 'object', | ||
patternProperties: { | ||
'.+': { | ||
type: 'object', | ||
patternProperties: { | ||
'.+': { | ||
type: ['boolean', 'array' ], | ||
items: { | ||
type: 'string', | ||
}, | ||
additionalProperties: false | ||
} | ||
} | ||
}, | ||
}, | ||
additionalProperties: true, | ||
}, | ||
ignores: { type: 'array', items: { type: 'string' }, default: [] }, | ||
}, | ||
additionalProperties: false, | ||
} | ||
] | ||
const pickImportedName = (s) => s.imported?.name | ||
const findExistsSync = (p) => fs.existsSync(p) | ||
module.exports = { | ||
@@ -87,12 +98,9 @@ meta: { | ||
if ((option.ignores || []).some((i) => !!filename.match(new RegExp(i)))) { | ||
if (option.ignores && option.ignores.some((i) => !!filename.match(new RegExp(i)))) { | ||
return {} | ||
} | ||
const dir = (() => { | ||
const d = filename.split('/') | ||
d.pop() | ||
return d.join('/') | ||
})() | ||
let d = filename.split('/') | ||
d.pop() | ||
const dir = d.join('/') | ||
const targetPathRegexs = Object.keys(option?.allowedImports || {}) | ||
@@ -112,3 +120,3 @@ const targetAllowedImports = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex))) | ||
const allowedModules = allowedOption[targetModule] || true | ||
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`) | ||
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${CWD}/${targetModule}`) | ||
let sourceValue = node.source.value | ||
@@ -124,3 +132,2 @@ | ||
if (!Array.isArray(allowedModules)) { | ||
@@ -130,3 +137,3 @@ isDenyPath = true | ||
} else { | ||
deniedModules.push(node.specifiers.map((s) => s.imported?.name).filter(i => allowedModules.indexOf(i) == -1)) | ||
deniedModules.push(node.specifiers.map(pickImportedName).filter(i => allowedModules.indexOf(i) == -1)) | ||
} | ||
@@ -136,10 +143,9 @@ }) | ||
if (isDenyPath && deniedModules[0] === true) { | ||
if ( | ||
isDenyPath && deniedModules[0] === true || | ||
!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0 | ||
) { | ||
return | ||
} | ||
if (!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0) { | ||
return | ||
} | ||
let sourceValue = node.source.value | ||
@@ -178,3 +184,3 @@ | ||
barrel = TARGET_EXTS.map((e) => `${sourceValue}/index.${e}`).find((p) => fs.existsSync(p)) || barrel | ||
barrel = TARGET_EXTS.map((e) => `${sourceValue}/index.${e}`).find(findExistsSync) || barrel | ||
@@ -185,5 +191,5 @@ sources.pop() | ||
if (barrel && !barrel.match(new RegExp(`^${rootPath}/index\.`))) { | ||
if (barrel && !barrel.match(REGEX_ROOT_PATH)) { | ||
barrel = calculateReplacedImportPath(barrel) | ||
const noExt = barrel.replace(/\/index\.(ts|js)x?$/, '') | ||
const noExt = barrel.replace(REGEX_INDEX_FILE, '') | ||
deniedModules = [...new Set(deniedModules.flat())] | ||
@@ -190,0 +196,0 @@ |
@@ -32,20 +32,9 @@ const rule = require('../rules/a11y-anchor-has-href-attribute') | ||
{ code: 'const HogeLink = styled(Link)``' }, | ||
{ | ||
code: `<a href="hoge">ほげ</a>`, | ||
}, | ||
{ | ||
code: `<a href={hoge}>ほげ</a>`, | ||
}, | ||
{ | ||
code: `<a href={undefined}>ほげ</a>`, | ||
}, | ||
{ | ||
code: `<HogeAnchor href={hoge}>ほげ</HogeAnchor>`, | ||
}, | ||
{ | ||
code: `<Link href="hoge">ほげ</Link>`, | ||
}, | ||
{ | ||
code: `<Link href="#fuga">ほげ</Link>`, | ||
}, | ||
{ code: `<a href="hoge">ほげ</a>` }, | ||
{ code: `<a href={hoge}>ほげ</a>` }, | ||
{ code: `<a href={undefined}>ほげ</a>` }, | ||
{ code: `<HogeAnchor href={hoge}>ほげ</HogeAnchor>` }, | ||
{ code: `<Link href="hoge">ほげ</Link>` }, | ||
{ code: `<Link href="#fuga">ほげ</Link>` }, | ||
{ code: '<AnyAnchor {...args1} />', options: [{ checkType: 'smart' }] }, | ||
], | ||
@@ -57,2 +46,14 @@ invalid: [ | ||
{ code: 'const Hoge = styled(Link)``', errors: [ { message: `Hogeを正規表現 "/Link$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const FugaAnchor = styled.div``', errors: [ { message: `FugaAnchor は /(Anchor|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- FugaAnchor の名称の末尾が"Anchor" という文字列ではない状態にしつつ、"div"を継承していることをわかる名称に変更してください | ||
- もしくは"div"を"FugaAnchor"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const FugaXxxx = styled.div | ||
- 修正例2: const FugaAnchorXxxx = styled.div | ||
- 修正例3: const FugaAnchor = styled(XxxxAnchor)` } ] }, | ||
{ code: 'const FugaLink = styled.p``', errors: [ { message: `FugaLink は /(Link|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- FugaLink の名称の末尾が"Link" という文字列ではない状態にしつつ、"p"を継承していることをわかる名称に変更してください | ||
- もしくは"p"を"FugaLink"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const FugaXxxx = styled.p | ||
- 修正例2: const FugaLinkXxxx = styled.p | ||
- 修正例3: const FugaLink = styled(XxxxLink)` } ] }, | ||
{ code: `<a></a>`, errors: [{ message: generateErrorText('a') }] }, | ||
@@ -70,3 +71,5 @@ { code: `<a>hoge</a>`, errors: [{ message: generateErrorText('a') }] }, | ||
{ code: `<HogeLink href={'#'}>hoge</HogeLink>`, errors: [{ message: generateErrorText('HogeLink') }] }, | ||
{ code: '<AnyAnchor {...args1} />', errors: [{ message: generateErrorText('AnyAnchor') }] }, | ||
{ code: '<AnyAnchor {...args1} />', options: [{ checkType: 'always' }], errors: [{ message: generateErrorText('AnyAnchor') }] }, | ||
] | ||
}) |
@@ -144,2 +144,20 @@ const rule = require('../rules/a11y-clickable-element-has-text') | ||
{ code: 'const Hoge = styled(HogeMessage)``', errors: [ { message: `Hogeを正規表現 "/Message$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const StyledButton = styled.div``', errors: [ { message: `StyledButton は /(B|^b)utton$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- StyledButton の名称の末尾が"Button" という文字列ではない状態にしつつ、"div"を継承していることをわかる名称に変更してください | ||
- もしくは"div"を"StyledButton"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const StyledXxxx = styled.div | ||
- 修正例2: const StyledButtonXxxx = styled.div | ||
- 修正例3: const StyledButton = styled(XxxxButton)` } ] }, | ||
{ code: 'const HogeAnchor = styled(Fuga)``', errors: [ { message: `HogeAnchor は /(Anchor|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- HogeAnchor の名称の末尾が"Anchor" という文字列ではない状態にしつつ、"Fuga"を継承していることをわかる名称に変更してください | ||
- もしくは"Fuga"を"HogeAnchor"の継承元であることがわかるような名称に変更するか、適切な別コンポーネントに差し替えてください | ||
- 修正例1: const HogeXxxx = styled(Fuga) | ||
- 修正例2: const HogeAnchorXxxx = styled(Fuga) | ||
- 修正例3: const HogeAnchor = styled(XxxxAnchor)` } ] }, | ||
{ code: 'const HogeLink = styled.p``', errors: [ { message: `HogeLink は /(Link|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- HogeLink の名称の末尾が"Link" という文字列ではない状態にしつつ、"p"を継承していることをわかる名称に変更してください | ||
- もしくは"p"を"HogeLink"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const HogeXxxx = styled.p | ||
- 修正例2: const HogeLinkXxxx = styled.p | ||
- 修正例3: const HogeLink = styled(XxxxLink)` } ] }, | ||
{ | ||
@@ -146,0 +164,0 @@ code: `<a><img src="hoge.jpg" /></a>`, |
@@ -61,2 +61,26 @@ const rule = require('../rules/a11y-heading-in-sectioning-content'); | ||
{ code: 'const StyledSection = styled.section``', errors: [ { message: `"section"を利用せず、smarthr-ui/Sectionを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.section" -> "styled(Section)")` } ] }, | ||
{ code: 'const StyledSection = styled.div``', errors: [ { message: `StyledSection は smarthr-ui/Section をextendすることを期待する名称になっています | ||
- childrenにHeadingを含まない場合、コンポーネントの名称から"Section"を取り除いてください | ||
- childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/Sectionをexendしてください | ||
- "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"Section"を設定し、そのコンポーネント内でsmarthr-ui/Sectionを利用してください` } ] }, | ||
{ code: 'const StyledArticle = styled(Hoge)``', errors: [ { message: `StyledArticle は smarthr-ui/Article をextendすることを期待する名称になっています | ||
- childrenにHeadingを含まない場合、コンポーネントの名称から"Article"を取り除いてください | ||
- childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/Articleをexendしてください | ||
- "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"Article"を設定し、そのコンポーネント内でsmarthr-ui/Articleを利用してください` } ] }, | ||
{ code: 'const StyledAside = styled(AsideXxxx)``', errors: [ { message: `StyledAside は smarthr-ui/Aside をextendすることを期待する名称になっています | ||
- childrenにHeadingを含まない場合、コンポーネントの名称から"Aside"を取り除いてください | ||
- childrenにHeadingを含み、アウトラインの範囲を指定するためのコンポーネントならば、smarthr-ui/Asideをexendしてください | ||
- "styled(Xxxx)" 形式の場合、拡張元であるXxxxコンポーネントの名称の末尾に"Aside"を設定し、そのコンポーネント内でsmarthr-ui/Asideを利用してください` } ] }, | ||
{ code: 'const StyledHeading = styled(Hoge)``', errors: [ { message: `StyledHeading は /(Heading|^h(1|2|3|4|5|6))$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- StyledHeading の名称の末尾が"Heading" という文字列ではない状態にしつつ、"Hoge"を継承していることをわかる名称に変更してください | ||
- もしくは"Hoge"を"StyledHeading"の継承元であることがわかるような名称に変更するか、適切な別コンポーネントに差し替えてください | ||
- 修正例1: const StyledXxxx = styled(Hoge) | ||
- 修正例2: const StyledHeadingXxxx = styled(Hoge) | ||
- 修正例3: const StyledHeading = styled(XxxxHeading)` } ] }, | ||
{ code: 'const StyledHeading = styled.div``', errors: [ { message: `StyledHeading は /(Heading|^h(1|2|3|4|5|6))$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- StyledHeading の名称の末尾が"Heading" という文字列ではない状態にしつつ、"div"を継承していることをわかる名称に変更してください | ||
- もしくは"div"を"StyledHeading"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const StyledXxxx = styled.div | ||
- 修正例2: const StyledHeadingXxxx = styled.div | ||
- 修正例3: const StyledHeading = styled(XxxxHeading)` } ] }, | ||
{ code: '<><PageHeading>hoge</PageHeading><PageHeading>fuga</PageHeading></>', errors: [ { message: pageMessage } ] }, | ||
@@ -63,0 +87,0 @@ { code: '<Heading>hoge</Heading>', errors: [ { message } ] }, |
@@ -35,3 +35,3 @@ const rule = require('../rules/a11y-image-has-alt-attribute') | ||
{ code: 'const HogeImage = styled(Image)``' }, | ||
{ code: 'const HogeIcon = styled(ICon)``' }, | ||
{ code: 'const HogeIcon = styled(Icon)``' }, | ||
{ code: '<img alt="hoge" />' }, | ||
@@ -42,2 +42,3 @@ { code: '<HogeImg alt="hoge" />' }, | ||
{ code: '<svg><image /></svg>' }, | ||
{ code: '<AnyImg {...hoge} />', options: [{ checkType: 'smart' }] }, | ||
], | ||
@@ -51,6 +52,26 @@ invalid: [ | ||
{ code: 'const Hoge = styled(Image)``', errors: [ { message: `Hogeを正規表現 "/Image$/" がmatchする名称に変更してください` } ] }, | ||
{ code: 'const StyledImage = styled.span``', errors: [ { message: `StyledImage は /(Image|^(img|svg))$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- StyledImage の名称の末尾が"Image" という文字列ではない状態にしつつ、"span"を継承していることをわかる名称に変更してください | ||
- もしくは"span"を"StyledImage"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください | ||
- 修正例1: const StyledXxxx = styled.span | ||
- 修正例2: const StyledImageXxxx = styled.span | ||
- 修正例3: const StyledImage = styled(XxxxImage)` } ] }, | ||
{ code: 'const StyledImg = styled(Hoge)``', errors: [ { message: `StyledImg は /(Img|^(img|svg))$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- StyledImg の名称の末尾が"Img" という文字列ではない状態にしつつ、"Hoge"を継承していることをわかる名称に変更してください | ||
- もしくは"Hoge"を"StyledImg"の継承元であることがわかるような名称に変更するか、適切な別コンポーネントに差し替えてください | ||
- 修正例1: const StyledXxxx = styled(Hoge) | ||
- 修正例2: const StyledImgXxxx = styled(Hoge) | ||
- 修正例3: const StyledImg = styled(XxxxImg)` } ] }, | ||
{ code: 'const FugaIcon = styled(Fuga)``', errors: [ { message: `FugaIcon は /(Icon|^(img|svg))$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています | ||
- FugaIcon の名称の末尾が"Icon" という文字列ではない状態にしつつ、"Fuga"を継承していることをわかる名称に変更してください | ||
- もしくは"Fuga"を"FugaIcon"の継承元であることがわかるような名称に変更するか、適切な別コンポーネントに差し替えてください | ||
- 修正例1: const FugaXxxx = styled(Fuga) | ||
- 修正例2: const FugaIconXxxx = styled(Fuga) | ||
- 修正例3: const FugaIcon = styled(XxxxIcon)` } ] }, | ||
{ code: '<img />', errors: [ { message: messageNotExistAlt } ] }, | ||
{ code: '<HogeImage alt="" />', errors: [ { message: messageNullAlt } ] }, | ||
{ code: '<hoge><image /></hoge>', errors: [ { message: messageNotExistAlt } ] }, | ||
{ code: '<AnyImg {...hoge} />', errors: [ { message: messageNotExistAlt } ] }, | ||
{ code: '<AnyImg {...hoge} />', options: [{ checkType: 'always' }], errors: [ { message: messageNotExistAlt } ] }, | ||
] | ||
}) |
@@ -40,2 +40,5 @@ const rule = require('../rules/a11y-input-has-name-attribute'); | ||
{ code: '<Select name="hoge[0][Fuga]" />' }, | ||
{ code: '<Input {...hoge} />', options: [{ checkType: 'smart' }] }, | ||
{ code: '<Input {...args1} {...args2} />', options: [{ checkType: 'smart' }] }, | ||
{ code: '<Input {...args} hoge="fuga" />', options: [{ checkType: 'smart' }] }, | ||
], | ||
@@ -60,3 +63,9 @@ invalid: [ | ||
{ code: '<select name="hoge[fuga][0][あいうえお]" />', errors: [ { message: 'select のname属性の値(hoge[fuga][0][あいうえお])はブラウザの自動補完が適切に行えない可能性があるため"/^[a-zA-Z0-9_\\[\\]]+$/"にmatchするフォーマットで命名してください' } ] }, | ||
{ code: '<Input {...hoge} />', errors: [ { message: `Input にname属性を指定してください | ||
- ブラウザの自動補完が有効化されるなどのメリットがあります | ||
- より多くのブラウザが自動補完を行える可能性を上げるため、\"/^[a-zA-Z0-9_\\[\\]]+$/\"にmatchするフォーマットで命名してください` } ] }, | ||
{ code: '<Input {...hoge} hoge="fuga" />', options: [{ checkType: 'always' }], errors: [ { message: `Input にname属性を指定してください | ||
- ブラウザの自動補完が有効化されるなどのメリットがあります | ||
- より多くのブラウザが自動補完を行える可能性を上げるため、\"/^[a-zA-Z0-9_\\[\\]]+$/\"にmatchするフォーマットで命名してください` } ] }, | ||
], | ||
}); |
@@ -30,7 +30,7 @@ const rule = require('../rules/best-practice-for-date') | ||
invalid: [ | ||
{ code: 'new Date("2022/12/31")', errors: [ { message: errorNewDate } ] }, | ||
{ code: 'new Date("2022/12/31")', errors: [ { message: errorNewDate } ], output: 'new Date(2022, 12 - 1, 31)' }, | ||
{ code: 'const arg = "2022/12/31"; new Date(arg)', errors: [ { message: errorNewDate } ] }, | ||
{ code: 'Date.parse("2022/12/31")', errors: [ { message: errorDateParse } ] }, | ||
{ code: 'Date.parse("2022/12/31")', errors: [ { message: errorDateParse } ], output: 'new Date(2022, 12 - 1, 31).getTime()' }, | ||
{ code: 'const arg = "2022/12/31"; Date.parse(arg)', errors: [ { message: errorDateParse } ] }, | ||
] | ||
}) |
226903
4009