eslint-plugin-smarthr 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/libs/common_domain.js +5 -8
- package/libs/util.js +4 -0
- package/package.json +4 -4
- package/rules/a11y-anchor-has-href-attribute/index.js +31 -20
- package/rules/a11y-clickable-element-has-text/index.js +4 -25
- package/rules/a11y-delegate-element-has-role-presentation/index.js +34 -55
- package/rules/a11y-form-control-in-form/index.js +5 -31
- package/rules/a11y-heading-in-sectioning-content/index.js +7 -63
- package/rules/a11y-image-has-alt-attribute/index.js +0 -16
- package/rules/a11y-input-has-name-attribute/index.js +15 -33
- package/rules/a11y-input-in-form-control/README.md +5 -5
- package/rules/a11y-input-in-form-control/index.js +30 -76
- package/rules/a11y-numbered-text-within-ol/index.js +2 -11
- package/rules/a11y-prohibit-input-maxlength-attribute/index.js +8 -20
- package/rules/a11y-prohibit-input-placeholder/index.js +41 -57
- package/rules/a11y-prohibit-sectioning-content-in-form/index.js +5 -37
- package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +5 -23
- package/rules/a11y-required-layout-as-attribute/index.js +0 -25
- package/rules/a11y-trigger-has-button/index.js +10 -18
- package/rules/best-practice-for-data-test-attribute/index.js +3 -6
- package/rules/best-practice-for-layouts/index.js +2 -16
- package/rules/best-practice-for-remote-trigger-dialog/index.js +3 -11
- package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +38 -30
- package/rules/best-practice-for-tailwind-variants/index.js +10 -18
- package/rules/component-name/README.md +44 -0
- package/rules/component-name/index.js +139 -0
- package/rules/design-system-guideline-prohibit-double-icons/index.js +1 -11
- package/rules/format-import-path/index.js +14 -6
- package/rules/format-translate-component/index.js +3 -1
- package/rules/no-import-other-domain/index.js +8 -8
- package/rules/prohibit-file-name/index.js +1 -1
- package/rules/prohibit-import/index.js +21 -23
- package/rules/prohibit-path-within-template-literal/index.js +1 -1
- package/rules/require-barrel-import/index.js +6 -8
- package/rules/require-declaration/index.js +5 -3
- package/rules/require-export/index.js +34 -30
- package/rules/require-import/index.js +10 -10
- package/rules/trim-props/index.js +9 -8
- package/test/a11y-anchor-has-href-attribute.js +0 -29
- package/test/a11y-clickable-element-has-text.js +0 -66
- package/test/{a11y-delegate-element-has-role-presantation.js → a11y-delegate-element-has-role-presentation.js} +3 -2
- package/test/a11y-form-control-in-form.js +1 -1
- package/test/a11y-heading-in-sectioning-content.js +0 -82
- package/test/a11y-image-has-alt-attribute.js +0 -45
- package/test/a11y-input-has-name-attribute.js +0 -44
- package/test/a11y-input-in-form-control.js +11 -35
- package/test/a11y-prohhibit-input-placeholder.js +0 -45
- package/test/a11y-trigger-has-button.js +0 -42
- package/test/best-practice-for-remote-trigger-dialog.js +0 -6
- package/test/component-name.js +247 -0
- package/test/prohibit-import.js +13 -13
|
@@ -13,6 +13,8 @@ const SCHEMA = [
|
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
+
const NOOP = () => {}
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
18
20
|
*/
|
|
@@ -23,7 +25,7 @@ module.exports = {
|
|
|
23
25
|
},
|
|
24
26
|
create(context) {
|
|
25
27
|
const { componentPath, componentName, prohibitAttributies } = context.options[0]
|
|
26
|
-
let JSXAttribute =
|
|
28
|
+
let JSXAttribute = NOOP
|
|
27
29
|
|
|
28
30
|
if (prohibitAttributies) {
|
|
29
31
|
JSXAttribute = (node) => {
|
|
@@ -46,7 +46,7 @@ module.exports = {
|
|
|
46
46
|
const calcContext = calculateDomainContext(context)
|
|
47
47
|
|
|
48
48
|
// 対象外ファイル
|
|
49
|
-
if (!calcContext.isTarget || calcContext.option.ignores && calcContext.option.ignores.some((i) =>
|
|
49
|
+
if (!calcContext.isTarget || calcContext.option.ignores && calcContext.option.ignores.some((i) => (new RegExp(i)).test(calcContext.filename))) {
|
|
50
50
|
return {}
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -55,8 +55,8 @@ module.exports = {
|
|
|
55
55
|
humanizeParentDir,
|
|
56
56
|
} = calcContext
|
|
57
57
|
|
|
58
|
-
const targetPathRegexs = Object.keys(option
|
|
59
|
-
const targetAllowedImports = targetPathRegexs.filter((regex) =>
|
|
58
|
+
const targetPathRegexs = option?.allowedImports ? Object.keys(option.allowedImports) : []
|
|
59
|
+
const targetAllowedImports = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(calcContext.filename))
|
|
60
60
|
|
|
61
61
|
return {
|
|
62
62
|
ImportDeclaration: (node) => {
|
|
@@ -90,11 +90,11 @@ module.exports = {
|
|
|
90
90
|
})
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
if (isDenyPath
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
93
|
+
if (isDenyPath) {
|
|
94
|
+
if (deniedModules[0] === true) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
} else if (deniedModules.length === 1 && deniedModules[0].length === 0) {
|
|
98
98
|
return
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -19,7 +19,7 @@ module.exports = {
|
|
|
19
19
|
},
|
|
20
20
|
create(context) {
|
|
21
21
|
const options = context.options[0]
|
|
22
|
-
const targetPaths = Object.keys(options).filter((regex) =>
|
|
22
|
+
const targetPaths = Object.keys(options).filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
if (targetPaths.length === 0) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const { getParentDir } = require('../../libs/util')
|
|
2
3
|
|
|
3
4
|
const SCHEMA = [{
|
|
4
5
|
type: 'object',
|
|
@@ -30,6 +31,8 @@ const SCHEMA = [{
|
|
|
30
31
|
additionalProperties: true,
|
|
31
32
|
}]
|
|
32
33
|
|
|
34
|
+
const CWD = process.cwd()
|
|
35
|
+
|
|
33
36
|
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください`
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -42,14 +45,9 @@ module.exports = {
|
|
|
42
45
|
},
|
|
43
46
|
create(context) {
|
|
44
47
|
const options = context.options[0]
|
|
45
|
-
const parentDir = (
|
|
46
|
-
const dir = context.filename.match(/^(.+?)\..+?$/)[1].split('/')
|
|
47
|
-
dir.pop()
|
|
48
|
-
|
|
49
|
-
return dir.join('/')
|
|
50
|
-
})()
|
|
48
|
+
const parentDir = getParentDir(context.filename)
|
|
51
49
|
const targetPathRegexs = Object.keys(options)
|
|
52
|
-
const targetProhibits = targetPathRegexs.filter((regex) =>
|
|
50
|
+
const targetProhibits = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
53
51
|
|
|
54
52
|
if (targetProhibits.length === 0) {
|
|
55
53
|
return {}
|
|
@@ -63,32 +61,32 @@ module.exports = {
|
|
|
63
61
|
|
|
64
62
|
targetModules.forEach((targetModule) => {
|
|
65
63
|
const { imported, reportMessage } = Object.assign({imported: true}, option[targetModule])
|
|
66
|
-
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${
|
|
64
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${CWD}/${targetModule}`)
|
|
67
65
|
let sourceValue = node.source.value
|
|
68
66
|
|
|
69
67
|
if (actualTarget[0] === '/') {
|
|
70
68
|
sourceValue = path.resolve(`${parentDir}/${sourceValue}`)
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
if (actualTarget
|
|
74
|
-
|
|
75
|
-
}
|
|
71
|
+
if (actualTarget === sourceValue) {
|
|
72
|
+
let useImported = false
|
|
76
73
|
|
|
77
|
-
const useImported = (() => {
|
|
78
74
|
if (!Array.isArray(imported)) {
|
|
79
|
-
|
|
80
|
-
}
|
|
75
|
+
useImported = !!imported
|
|
76
|
+
} else {
|
|
77
|
+
const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
if (specifier) {
|
|
80
|
+
useImported = specifier.imported.name
|
|
81
|
+
}
|
|
82
|
+
}
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
if (useImported) {
|
|
85
|
+
context.report({
|
|
86
|
+
node,
|
|
87
|
+
message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
92
90
|
}
|
|
93
91
|
})
|
|
94
92
|
})
|
|
@@ -13,7 +13,7 @@ const recursiveFetchName = (obj, chained = '') => {
|
|
|
13
13
|
const recursiveFetchRootNameIsPath = (obj, regex) => {
|
|
14
14
|
const [name, chained] = recursiveFetchName(obj, '')
|
|
15
15
|
|
|
16
|
-
return
|
|
16
|
+
return regex.test(name) ? chained : null
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const SCHEMA = [
|
|
@@ -47,10 +47,8 @@ const calculateAbsoluteImportPath = (source) => {
|
|
|
47
47
|
const regexp = new RegExp(`^${key}(.+)$`)
|
|
48
48
|
|
|
49
49
|
return values.reduce((p, v) => {
|
|
50
|
-
if (prev === p) {
|
|
51
|
-
|
|
52
|
-
return p.replace(regexp, `${path.resolve(`${CWD}/${v}`)}/$1`)
|
|
53
|
-
}
|
|
50
|
+
if (prev === p && regexp.test(regexp)) {
|
|
51
|
+
return p.replace(regexp, `${path.resolve(`${CWD}/${v}`)}/$1`)
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
return p
|
|
@@ -67,7 +65,7 @@ const calculateReplacedImportPath = (source) => {
|
|
|
67
65
|
if (prev === p) {
|
|
68
66
|
const regexp = new RegExp(`^${path.resolve(`${CWD}/${v}`)}(.+)$`)
|
|
69
67
|
|
|
70
|
-
if (
|
|
68
|
+
if (regexp.test(prev)) {
|
|
71
69
|
return p.replace(regexp, `${key}/$1`).replace(REGEX_UNNECESSARY_SLASH, '/')
|
|
72
70
|
}
|
|
73
71
|
}
|
|
@@ -94,15 +92,15 @@ module.exports = {
|
|
|
94
92
|
create(context) {
|
|
95
93
|
const option = context.options[0] || {}
|
|
96
94
|
|
|
97
|
-
if (option.ignores && option.ignores.some((i) =>
|
|
95
|
+
if (option.ignores && option.ignores.some((i) => (new RegExp(i)).test(context.filename))) {
|
|
98
96
|
return {}
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
let d = context.filename.split('/')
|
|
102
100
|
d.pop()
|
|
103
101
|
const dir = d.join('/')
|
|
104
|
-
const targetPathRegexs = Object.keys(option
|
|
105
|
-
const targetAllowedImports = targetPathRegexs.filter((regex) =>
|
|
102
|
+
const targetPathRegexs = option?.allowedImports ? Object.keys(option.allowedImports) || []
|
|
103
|
+
const targetAllowedImports = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
106
104
|
|
|
107
105
|
return {
|
|
108
106
|
ImportDeclaration: (node) => {
|
|
@@ -33,6 +33,8 @@ const SCHEMA = [
|
|
|
33
33
|
},
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
+
const DECLARATION_REGEX = /Declaration$/
|
|
37
|
+
|
|
36
38
|
const find = (type, ds, rd) => ds.find((d) => d.type === type && d.id.name === rd)
|
|
37
39
|
const codeSeparator = '[^a-zA-Z0-1_$]'
|
|
38
40
|
const useRegex = (use) => {
|
|
@@ -51,7 +53,7 @@ module.exports = {
|
|
|
51
53
|
create(context) {
|
|
52
54
|
const options = context.options[0]
|
|
53
55
|
const targetPathRegexs = Object.keys(options)
|
|
54
|
-
const targetRequires = targetPathRegexs.filter((regex) =>
|
|
56
|
+
const targetRequires = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
55
57
|
|
|
56
58
|
if (targetRequires.length === 0) {
|
|
57
59
|
return {}
|
|
@@ -59,7 +61,7 @@ module.exports = {
|
|
|
59
61
|
|
|
60
62
|
return {
|
|
61
63
|
Program: (node) => {
|
|
62
|
-
const declarations = node.body.filter((i) => i.type
|
|
64
|
+
const declarations = node.body.filter((i) => DECLARATION_REGEX.test(i.type)).map((d) => d.declaration || d)
|
|
63
65
|
|
|
64
66
|
if (declarations.length === 0) {
|
|
65
67
|
return
|
|
@@ -108,7 +110,7 @@ module.exports = {
|
|
|
108
110
|
let reported = false
|
|
109
111
|
|
|
110
112
|
localOption.use.forEach((u) => {
|
|
111
|
-
if (!
|
|
113
|
+
if (!useRegex(u).test(code) && (!localOption.reportMessage || !reported)) {
|
|
112
114
|
context.report({
|
|
113
115
|
node: hit,
|
|
114
116
|
message: localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`,
|
|
@@ -13,11 +13,7 @@ const SCHEMA = [{
|
|
|
13
13
|
const fetchEdgeDeclaration = (node) => {
|
|
14
14
|
const { declaration } = node
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
return node
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return fetchEdgeDeclaration(declaration)
|
|
16
|
+
return declaration ? fetchEdgeDeclaration(declaration) : node
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
/**
|
|
@@ -31,7 +27,7 @@ module.exports = {
|
|
|
31
27
|
create(context) {
|
|
32
28
|
const options = context.options[0]
|
|
33
29
|
const targetPathRegexs = Object.keys(options)
|
|
34
|
-
const targetRequires = targetPathRegexs.filter((regex) =>
|
|
30
|
+
const targetRequires = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
35
31
|
|
|
36
32
|
if (targetRequires.length === 0) {
|
|
37
33
|
return {}
|
|
@@ -42,35 +38,43 @@ module.exports = {
|
|
|
42
38
|
targetRequires.forEach((requireKey) => {
|
|
43
39
|
const option = options[requireKey]
|
|
44
40
|
let existDefault = false
|
|
45
|
-
const exports =
|
|
46
|
-
node.body
|
|
47
|
-
.filter((i) => {
|
|
48
|
-
if (i.type == 'ExportDefaultDeclaration') {
|
|
49
|
-
existDefault = true
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
const exports = []
|
|
43
|
+
|
|
44
|
+
for (let i of node.body) {
|
|
45
|
+
if (i.type == 'ExportDefaultDeclaration') {
|
|
46
|
+
existDefault = true
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.map((i) => {
|
|
57
|
-
const declaration = fetchEdgeDeclaration(i)
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
if (declaration.specifiers) {
|
|
63
|
-
return declaration.specifiers.map((s) => s.exported.name)
|
|
64
|
-
}
|
|
65
|
-
if (declaration.declarations) {
|
|
66
|
-
return declaration.declarations.map((d) => d.id.name || d.id.properties.map((p) => p.key.name))
|
|
67
|
-
}
|
|
51
|
+
if (i.type === 'ExportNamedDeclaration') {
|
|
52
|
+
const declaration = fetchEdgeDeclaration(i)
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
if (declaration.id) {
|
|
55
|
+
exports.push(declaration.id.name)
|
|
56
|
+
}
|
|
57
|
+
if (declaration.specifiers) {
|
|
58
|
+
declaration.specifiers.forEach((s) => {
|
|
59
|
+
exports.push(s.exported.name)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
if (declaration.declarations) {
|
|
63
|
+
declaration.declarations.forEach((d) => {
|
|
64
|
+
if (d.id.name) {
|
|
65
|
+
exports.push(d.id.name)
|
|
66
|
+
} else {
|
|
67
|
+
d.id.properties.forEach((p) => {
|
|
68
|
+
exports.push(p.key.name)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
const exportsRegex = new RegExp(`^(default|${exports.join('|')})$`)
|
|
77
|
+
const notExistsExports = (!existDefault && option.includes('default') ? ['default'] : []).concat(option.filter((o) => !exportsRegex.test(o)))
|
|
74
78
|
|
|
75
79
|
if (notExistsExports.length) {
|
|
76
80
|
context.report({
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const { getParentDir } = require('../../libs/util')
|
|
3
|
+
|
|
2
4
|
const SCHEMA = [{
|
|
3
5
|
type: 'object',
|
|
4
6
|
patternProperties: {
|
|
@@ -31,6 +33,7 @@ const SCHEMA = [{
|
|
|
31
33
|
}]
|
|
32
34
|
|
|
33
35
|
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} をimportしてください`
|
|
36
|
+
const filterImportDeclaration = (item) => item.type === 'ImportDeclaration'
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -43,21 +46,18 @@ module.exports = {
|
|
|
43
46
|
create(context) {
|
|
44
47
|
const options = context.options[0]
|
|
45
48
|
const targetPathRegexs = Object.keys(options)
|
|
46
|
-
const targetRequires = targetPathRegexs.filter((regex) =>
|
|
49
|
+
const targetRequires = targetPathRegexs.filter((regex) => (new RegExp(regex)).test(context.filename))
|
|
47
50
|
|
|
48
51
|
if (targetRequires.length === 0) {
|
|
49
52
|
return {}
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
const CWD = process.cwd()
|
|
56
|
+
|
|
52
57
|
return {
|
|
53
58
|
Program: (node) => {
|
|
54
|
-
const importDeclarations = node.body.filter(
|
|
55
|
-
const parentDir = (
|
|
56
|
-
const dir = context.filename.match(/^(.+?)\..+?$/)[1].split('/')
|
|
57
|
-
dir.pop()
|
|
58
|
-
|
|
59
|
-
return dir.join('/')
|
|
60
|
-
})()
|
|
59
|
+
const importDeclarations = node.body.filter(filterImportDeclaration)
|
|
60
|
+
const parentDir = getParentDir(context.filename)
|
|
61
61
|
|
|
62
62
|
targetRequires.forEach((requireKey) => {
|
|
63
63
|
const option = options[requireKey]
|
|
@@ -65,11 +65,11 @@ module.exports = {
|
|
|
65
65
|
Object.keys(option).forEach((targetModule) => {
|
|
66
66
|
const { imported, reportMessage, targetRegex } = Object.assign({imported: true}, option[targetModule])
|
|
67
67
|
|
|
68
|
-
if (targetRegex && !
|
|
68
|
+
if (targetRegex && !(new RegExp(targetRegex)).test(context.filename)) {
|
|
69
69
|
return
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${
|
|
72
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${CWD}/${targetModule}`)
|
|
73
73
|
const importDeclaration = importDeclarations.find(
|
|
74
74
|
actualTarget[0] !== '/' ? (
|
|
75
75
|
(id) => id.source.value === actualTarget
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
const SCHEMA = []
|
|
2
|
+
const EXTRA_SPACE_REGES = /(^\s+|\s+$)/
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
3
6
|
*/
|
|
4
7
|
module.exports = {
|
|
5
8
|
meta: {
|
|
6
9
|
type: 'suggestion',
|
|
7
|
-
schema:
|
|
10
|
+
schema: SCHEMA,
|
|
8
11
|
fixable: 'whitespace',
|
|
9
12
|
},
|
|
10
13
|
create(context) {
|
|
11
14
|
return {
|
|
12
15
|
JSXOpeningElement: (node) =>
|
|
13
|
-
node.attributes.
|
|
16
|
+
node.attributes.forEach((current) => {
|
|
14
17
|
const attribute = current.value?.type === 'JSXExpressionContainer' ? current.value.expression : current.value
|
|
15
18
|
const props = attribute?.value
|
|
16
19
|
|
|
17
|
-
if (typeof props
|
|
18
|
-
|
|
19
|
-
if (props.match(/(^\s+|\s+$)/)) {
|
|
20
|
+
if (typeof props === 'string' && EXTRA_SPACE_REGES.test(props)) {
|
|
20
21
|
return context.report({
|
|
21
22
|
node,
|
|
22
23
|
loc: current.loc,
|
|
@@ -26,9 +27,9 @@ module.exports = {
|
|
|
26
27
|
},
|
|
27
28
|
})
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
}, []),
|
|
30
|
+
})
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
module.exports.schema = SCHEMA
|
|
@@ -20,15 +20,6 @@ const generateErrorText = (name) => `${name} に href 属性を正しく設定
|
|
|
20
20
|
|
|
21
21
|
ruleTester.run('a11y-anchor-has-href-attribute', rule, {
|
|
22
22
|
valid: [
|
|
23
|
-
{ code: `import styled from 'styled-components'` },
|
|
24
|
-
{ code: `import styled, { css } from 'styled-components'` },
|
|
25
|
-
{ code: `import { css } from 'styled-components'` },
|
|
26
|
-
{ code: `import { HogeAnchor as FugaAnchor } from './hoge'` },
|
|
27
|
-
{ code: `import { Link as FugaLink } from './hoge'` },
|
|
28
|
-
{ code: 'const HogeAnchor = styled.a``' },
|
|
29
|
-
{ code: 'const HogeLink = styled.a``' },
|
|
30
|
-
{ code: 'const HogeAnchor = styled(Anchor)``' },
|
|
31
|
-
{ code: 'const HogeLink = styled(Link)``' },
|
|
32
23
|
{ code: `<a href="hoge">ほげ</a>` },
|
|
33
24
|
{ code: `<a href={hoge}>ほげ</a>` },
|
|
34
25
|
{ code: `<a href={undefined}>ほげ</a>` },
|
|
@@ -38,26 +29,6 @@ ruleTester.run('a11y-anchor-has-href-attribute', rule, {
|
|
|
38
29
|
{ code: '<AnyAnchor {...args1} />', options: [{ checkType: 'allow-spread-attributes' }] },
|
|
39
30
|
],
|
|
40
31
|
invalid: [
|
|
41
|
-
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
42
|
-
{ code: `import { Anchor as AnchorHoge } from './hoge'`, errors: [ { message: `AnchorHogeを正規表現 "/Anchor$/" がmatchする名称に変更してください。
|
|
43
|
-
- Anchorが型の場合、'import type { Anchor as AnchorHoge }' もしくは 'import { type Anchor as AnchorHoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
44
|
-
{ code: `import { HogeLink as HogeLinkFuga } from './hoge'`, errors: [ { message: `HogeLinkFugaを正規表現 "/Link$/" がmatchする名称に変更してください。
|
|
45
|
-
- HogeLinkが型の場合、'import type { HogeLink as HogeLinkFuga }' もしくは 'import { type HogeLink as HogeLinkFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
46
|
-
{ code: 'const Hoge = styled.a``', errors: [ { message: `Hogeを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください。` } ] },
|
|
47
|
-
{ code: 'const Hoge = styled(Anchor)``', errors: [ { message: `Hogeを正規表現 "/Anchor$/" がmatchする名称に変更してください。` } ] },
|
|
48
|
-
{ code: 'const Hoge = styled(Link)``', errors: [ { message: `Hogeを正規表現 "/Link$/" がmatchする名称に変更してください。` } ] },
|
|
49
|
-
{ code: 'const FugaAnchor = styled.div``', errors: [ { message: `FugaAnchor は /(Anchor|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています
|
|
50
|
-
- FugaAnchor の名称の末尾が"Anchor" という文字列ではない状態にしつつ、"div"を継承していることをわかる名称に変更してください
|
|
51
|
-
- もしくは"div"を"FugaAnchor"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください
|
|
52
|
-
- 修正例1: const FugaXxxx = styled.div
|
|
53
|
-
- 修正例2: const FugaAnchorXxxx = styled.div
|
|
54
|
-
- 修正例3: const FugaAnchor = styled(XxxxAnchor)` } ] },
|
|
55
|
-
{ code: 'const FugaLink = styled.p``', errors: [ { message: `FugaLink は /(Link|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています
|
|
56
|
-
- FugaLink の名称の末尾が"Link" という文字列ではない状態にしつつ、"p"を継承していることをわかる名称に変更してください
|
|
57
|
-
- もしくは"p"を"FugaLink"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください
|
|
58
|
-
- 修正例1: const FugaXxxx = styled.p
|
|
59
|
-
- 修正例2: const FugaLinkXxxx = styled.p
|
|
60
|
-
- 修正例3: const FugaLink = styled(XxxxLink)` } ] },
|
|
61
32
|
{ code: `<a></a>`, errors: [{ message: generateErrorText('a') }] },
|
|
62
33
|
{ code: `<a>hoge</a>`, errors: [{ message: generateErrorText('a') }] },
|
|
63
34
|
{ code: `<Anchor>hoge</Anchor>`, errors: [{ message: generateErrorText('Anchor') }] },
|
|
@@ -17,28 +17,6 @@ const defaultErrorMessage = `a, buttonなどのクリッカブルな要素内に
|
|
|
17
17
|
|
|
18
18
|
ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
19
19
|
valid: [
|
|
20
|
-
{ code: `import styled from 'styled-components'` },
|
|
21
|
-
{ code: `import styled, { css } from 'styled-components'` },
|
|
22
|
-
{ code: `import { css } from 'styled-components'` },
|
|
23
|
-
{ code: `import { SmartHRLogo as HogeSmartHRLogo } from './hoge'` },
|
|
24
|
-
{ code: `import { AbcButton as StyledAbcButton } from './hoge'` },
|
|
25
|
-
{ code: `import { HogeAnchor as FugaAnchor } from './hoge'` },
|
|
26
|
-
{ code: `import { Link as FugaLink } from './hoge'` },
|
|
27
|
-
{ code: `import { FugaText as HogeFugaText } from './hoge'` },
|
|
28
|
-
{ code: `import { FugaMessage as HogeFugaMessage } from './hoge'` },
|
|
29
|
-
{ code: 'const HogeAnchor = styled.a``' },
|
|
30
|
-
{ code: 'const HogeLink = styled.a``' },
|
|
31
|
-
{ code: 'const HogeButton = styled.button``' },
|
|
32
|
-
{ code: 'const HogeAnchor = styled(Anchor)``' },
|
|
33
|
-
{ code: 'const HogeLink = styled(Link)``' },
|
|
34
|
-
{ code: 'const HogeButton = styled(Button)``' },
|
|
35
|
-
{ code: 'const FugaAnchor = styled(HogeAnchor)``' },
|
|
36
|
-
{ code: 'const FugaSmartHRLogo = styled(SmartHRLogo)``' },
|
|
37
|
-
{ code: 'const HogeAnchor = styled.a(() => ``)' },
|
|
38
|
-
{ code: 'const HogeAnchor = styled("a")(() => ``)' },
|
|
39
|
-
{ code: 'const HogeAnchor = styled(Anchor)(() => ``)' },
|
|
40
|
-
{ code: 'const FugaText = styled(HogeText)(() => ``)' },
|
|
41
|
-
{ code: 'const FugaMessage = styled(HogeMessage)(() => ``)' },
|
|
42
20
|
{
|
|
43
21
|
code: `<a>ほげ</a>`,
|
|
44
22
|
},
|
|
@@ -128,50 +106,6 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
128
106
|
},
|
|
129
107
|
],
|
|
130
108
|
invalid: [
|
|
131
|
-
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
132
|
-
{ code: `import { SmartHRLogo as SmartHRLogoHoge } from './hoge'`, errors: [ { message: `SmartHRLogoHogeを正規表現 "/SmartHRLogo$/" がmatchする名称に変更してください。
|
|
133
|
-
- SmartHRLogoが型の場合、'import type { SmartHRLogo as SmartHRLogoHoge }' もしくは 'import { type SmartHRLogo as SmartHRLogoHoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
134
|
-
{ code: `import { AbcButton as AbcButtonFuga } from './hoge'`, errors: [ { message: `AbcButtonFugaを正規表現 "/Button$/" がmatchする名称に変更してください。
|
|
135
|
-
- AbcButtonが型の場合、'import type { AbcButton as AbcButtonFuga }' もしくは 'import { type AbcButton as AbcButtonFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
136
|
-
{ code: `import { Anchor as AnchorHoge } from './hoge'`, errors: [ { message: `AnchorHogeを正規表現 "/Anchor$/" がmatchする名称に変更してください。
|
|
137
|
-
- Anchorが型の場合、'import type { Anchor as AnchorHoge }' もしくは 'import { type Anchor as AnchorHoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
138
|
-
{ code: `import { HogeLink as HogeLinkFuga } from './hoge'`, errors: [ { message: `HogeLinkFugaを正規表現 "/Link$/" がmatchする名称に変更してください。
|
|
139
|
-
- HogeLinkが型の場合、'import type { HogeLink as HogeLinkFuga }' もしくは 'import { type HogeLink as HogeLinkFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
140
|
-
{ code: `import { FugaText as FugaTextFuga } from './hoge'`, errors: [ { message: `FugaTextFugaを正規表現 "/Text$/" がmatchする名称に変更してください。
|
|
141
|
-
- FugaTextが型の場合、'import type { FugaText as FugaTextFuga }' もしくは 'import { type FugaText as FugaTextFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
142
|
-
{ code: `import { FugaMessage as FugaMessageFuga } from './hoge'`, errors: [ { message: `FugaMessageFugaを正規表現 "/Message$/" がmatchする名称に変更してください。
|
|
143
|
-
- FugaMessageが型の場合、'import type { FugaMessage as FugaMessageFuga }' もしくは 'import { type FugaMessage as FugaMessageFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
144
|
-
{ code: 'const Hoge = styled.a``', errors: [ { message: `Hogeを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください。` } ] },
|
|
145
|
-
{ code: 'const Hoge = styled.button``', errors: [ { message: `Hogeを正規表現 "/Button$/" がmatchする名称に変更してください。` } ] },
|
|
146
|
-
{ code: 'const Hoge = styled(Anchor)``', errors: [ { message: `Hogeを正規表現 "/Anchor$/" がmatchする名称に変更してください。` } ] },
|
|
147
|
-
{ code: 'const Hoge = styled(Link)``', errors: [ { message: `Hogeを正規表現 "/Link$/" がmatchする名称に変更してください。` } ] },
|
|
148
|
-
{ code: 'const Hoge = styled(Button)``', errors: [ { message: `Hogeを正規表現 "/Button$/" がmatchする名称に変更してください。` } ] },
|
|
149
|
-
{ code: 'const Fuga = styled(HogeAnchor)``', errors: [ { message: `Fugaを正規表現 "/Anchor$/" がmatchする名称に変更してください。` } ] },
|
|
150
|
-
{ code: 'const Fuga = styled(SmartHRLogo)``', errors: [ { message: `Fugaを正規表現 "/SmartHRLogo$/" がmatchする名称に変更してください。` } ] },
|
|
151
|
-
{ code: 'const Piyo = styled.a(() => ``)', errors: [ { message: `Piyoを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください。` } ] },
|
|
152
|
-
{ code: 'const Piyo = styled("a")(() => ``)', errors: [ { message: `Piyoを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください。` } ] },
|
|
153
|
-
{ code: 'const Piyo = styled("a")``', errors: [ { message: `Piyoを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください。` } ] },
|
|
154
|
-
{ code: 'const Piyo = styled(Anchor)(() => ``)', errors: [ { message: `Piyoを正規表現 "/Anchor$/" がmatchする名称に変更してください。` } ] },
|
|
155
|
-
{ code: 'const Hoge = styled(Text)``', errors: [ { message: `Hogeを正規表現 "/Text$/" がmatchする名称に変更してください。` } ] },
|
|
156
|
-
{ code: 'const Hoge = styled(HogeMessage)``', errors: [ { message: `Hogeを正規表現 "/Message$/" がmatchする名称に変更してください。` } ] },
|
|
157
|
-
{ code: 'const StyledButton = styled.div``', errors: [ { message: `StyledButton は /(B|^b)utton$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています
|
|
158
|
-
- StyledButton の名称の末尾が"Button" という文字列ではない状態にしつつ、"div"を継承していることをわかる名称に変更してください
|
|
159
|
-
- もしくは"div"を"StyledButton"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください
|
|
160
|
-
- 修正例1: const StyledXxxx = styled.div
|
|
161
|
-
- 修正例2: const StyledButtonXxxx = styled.div
|
|
162
|
-
- 修正例3: const StyledButton = styled(XxxxButton)` } ] },
|
|
163
|
-
{ code: 'const HogeAnchor = styled(Fuga)``', errors: [ { message: `HogeAnchor は /(Anchor|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています
|
|
164
|
-
- HogeAnchor の名称の末尾が"Anchor" という文字列ではない状態にしつつ、"Fuga"を継承していることをわかる名称に変更してください
|
|
165
|
-
- もしくは"Fuga"を"HogeAnchor"の継承元であることがわかるような名称に変更するか、適切な別コンポーネントに差し替えてください
|
|
166
|
-
- 修正例1: const HogeXxxx = styled(Fuga)
|
|
167
|
-
- 修正例2: const HogeAnchorXxxx = styled(Fuga)
|
|
168
|
-
- 修正例3: const HogeAnchor = styled(XxxxAnchor)` } ] },
|
|
169
|
-
{ code: 'const HogeLink = styled.p``', errors: [ { message: `HogeLink は /(Link|^a)$/ にmatchする名前のコンポーネントを拡張することを期待している名称になっています
|
|
170
|
-
- HogeLink の名称の末尾が"Link" という文字列ではない状態にしつつ、"p"を継承していることをわかる名称に変更してください
|
|
171
|
-
- もしくは"p"を"HogeLink"の継承元であることがわかるような適切なタグや別コンポーネントに差し替えてください
|
|
172
|
-
- 修正例1: const HogeXxxx = styled.p
|
|
173
|
-
- 修正例2: const HogeLinkXxxx = styled.p
|
|
174
|
-
- 修正例3: const HogeLink = styled(XxxxLink)` } ] },
|
|
175
109
|
{
|
|
176
110
|
code: `<a><img src="hoge.jpg" /></a>`,
|
|
177
111
|
errors: [{ message: defaultErrorMessage }]
|
|
@@ -11,7 +11,8 @@ const ruleTester = new RuleTester({
|
|
|
11
11
|
},
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const defaultInteractiveMessage = '((B|b)utton|(Check|Combo)(B|b)ox|(Date(timeLocal)?|Time|Month|Wareki)Picker|(I|i)nput(File)?|(S|s)elect|(T|t)extarea|(ActionDialogWith|RemoteDialog)Trigger|AccordionPanel|^a|Anchor|Link|DropZone|Field(S|s)et|FilterDropdown|(F|f)orm(Control|Group|Dialog)?|Pagination|RadioButton(Panel)?|RemoteTrigger(.+)Dialog|RightFixedNote|SegmentedControl|SideNav|Switch|TabItem)$'
|
|
15
|
+
const defaultInteractiveRegex = `/(${defaultInteractiveMessage})/`
|
|
15
16
|
const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
|
|
16
17
|
const onAttrsText = onAttrs.join(', ')
|
|
17
18
|
|
|
@@ -66,7 +67,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
|
|
|
66
67
|
{ code: '<div onClick={any} onSubmit={any2} role="presentation"><Hoge /></div>', errors: [ { message: messageRolePresentationNotHasInteractive('div', ['onClick', 'onSubmit']) } ] },
|
|
67
68
|
{ code: '<div onClick={any}><Link /></div>', errors: [ { message: messageNonInteractiveEventHandler('div', ['onClick']) } ] },
|
|
68
69
|
{ code: '<Wrapper onClick={any}><Link /></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
|
|
69
|
-
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'],
|
|
70
|
+
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], `/(${defaultInteractiveMessage}|^Hoge$)/`) } ] },
|
|
70
71
|
{ code: '<Wrapper onClick={any} onChange={anyany}><any><Link /></any></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick', 'onChange']) } ] },
|
|
71
72
|
{ code: '<Wrapper onClick={any}>{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
|
|
72
73
|
],
|
|
@@ -14,7 +14,7 @@ const generateErrorText = (elementName) => `${elementName}をform要素で囲む
|
|
|
14
14
|
- form要素で囲むことでスクリーンリーダーに入力フォームであることが正しく伝わる、入力要素にfocusした状態でEnterを押せばsubmitできる、inputのpattern属性を利用できるなどのメリットがあります
|
|
15
15
|
- 以下のいずれかの方法で修正をおこなってください
|
|
16
16
|
- 方法1: form要素で ${elementName} を囲んでください。smarthr-ui/ActionDialog、もしくはsmarthr-ui/RemoteTriggerActionDialogを利用している場合、smarthr-ui/FormDialog、smarthr-ui/RemoteTriggerFormDialogに置き換えてください
|
|
17
|
-
- 方法2: ${elementName} がコンポーネント内の一要素であり、かつその親コンポーネントがFormControl、もしくはFieldsetを表現するものである場合、親コンポーネント名を "(
|
|
17
|
+
- 方法2: ${elementName} がコンポーネント内の一要素であり、かつその親コンポーネントがFormControl、もしくはFieldsetを表現するものである場合、親コンポーネント名を "/(Fieldset|Form(Group|Control))(s)?$/" とマッチするものに変更してください`
|
|
18
18
|
|
|
19
19
|
ruleTester.run('a11y-form-control-in-form', rule, {
|
|
20
20
|
valid: [
|