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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/libs/common_domain.js +5 -8
  3. package/libs/util.js +4 -0
  4. package/package.json +4 -4
  5. package/rules/a11y-anchor-has-href-attribute/index.js +31 -20
  6. package/rules/a11y-clickable-element-has-text/index.js +4 -25
  7. package/rules/a11y-delegate-element-has-role-presentation/index.js +34 -55
  8. package/rules/a11y-form-control-in-form/index.js +5 -31
  9. package/rules/a11y-heading-in-sectioning-content/index.js +7 -63
  10. package/rules/a11y-image-has-alt-attribute/index.js +0 -16
  11. package/rules/a11y-input-has-name-attribute/index.js +15 -33
  12. package/rules/a11y-input-in-form-control/README.md +5 -5
  13. package/rules/a11y-input-in-form-control/index.js +30 -76
  14. package/rules/a11y-numbered-text-within-ol/index.js +2 -11
  15. package/rules/a11y-prohibit-input-maxlength-attribute/index.js +8 -20
  16. package/rules/a11y-prohibit-input-placeholder/index.js +41 -57
  17. package/rules/a11y-prohibit-sectioning-content-in-form/index.js +5 -37
  18. package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +5 -23
  19. package/rules/a11y-required-layout-as-attribute/index.js +0 -25
  20. package/rules/a11y-trigger-has-button/index.js +10 -18
  21. package/rules/best-practice-for-data-test-attribute/index.js +3 -6
  22. package/rules/best-practice-for-layouts/index.js +2 -16
  23. package/rules/best-practice-for-remote-trigger-dialog/index.js +3 -11
  24. package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +38 -30
  25. package/rules/best-practice-for-tailwind-variants/index.js +10 -18
  26. package/rules/component-name/README.md +44 -0
  27. package/rules/component-name/index.js +139 -0
  28. package/rules/design-system-guideline-prohibit-double-icons/index.js +1 -11
  29. package/rules/format-import-path/index.js +14 -6
  30. package/rules/format-translate-component/index.js +3 -1
  31. package/rules/no-import-other-domain/index.js +8 -8
  32. package/rules/prohibit-file-name/index.js +1 -1
  33. package/rules/prohibit-import/index.js +21 -23
  34. package/rules/prohibit-path-within-template-literal/index.js +1 -1
  35. package/rules/require-barrel-import/index.js +6 -8
  36. package/rules/require-declaration/index.js +5 -3
  37. package/rules/require-export/index.js +34 -30
  38. package/rules/require-import/index.js +10 -10
  39. package/rules/trim-props/index.js +9 -8
  40. package/test/a11y-anchor-has-href-attribute.js +0 -29
  41. package/test/a11y-clickable-element-has-text.js +0 -66
  42. package/test/{a11y-delegate-element-has-role-presantation.js → a11y-delegate-element-has-role-presentation.js} +3 -2
  43. package/test/a11y-form-control-in-form.js +1 -1
  44. package/test/a11y-heading-in-sectioning-content.js +0 -82
  45. package/test/a11y-image-has-alt-attribute.js +0 -45
  46. package/test/a11y-input-has-name-attribute.js +0 -44
  47. package/test/a11y-input-in-form-control.js +11 -35
  48. package/test/a11y-prohhibit-input-placeholder.js +0 -45
  49. package/test/a11y-trigger-has-button.js +0 -42
  50. package/test/best-practice-for-remote-trigger-dialog.js +0 -6
  51. package/test/component-name.js +247 -0
  52. 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) => !!calcContext.filename.match(new RegExp(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?.allowedImports || {})
59
- const targetAllowedImports = targetPathRegexs.filter((regex) => !!calcContext.filename.match(new RegExp(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 && deniedModules[0] === true) {
94
- return
95
- }
96
-
97
- if (!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0) {
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) => !!context.filename.match(new RegExp(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) => !!context.filename.match(new RegExp(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(`${process.cwd()}/${targetModule}`)
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 !== sourceValue) {
74
- return
75
- }
71
+ if (actualTarget === sourceValue) {
72
+ let useImported = false
76
73
 
77
- const useImported = (() => {
78
74
  if (!Array.isArray(imported)) {
79
- return !!imported
80
- }
75
+ useImported = !!imported
76
+ } else {
77
+ const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
81
78
 
82
- const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
83
-
84
- return specifier ? specifier.imported.name : false
85
- })()
79
+ if (specifier) {
80
+ useImported = specifier.imported.name
81
+ }
82
+ }
86
83
 
87
- if (useImported) {
88
- context.report({
89
- node,
90
- message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
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 name.match(regex) ? chained : null
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
- if (prev.match(regexp)) {
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 (prev.match(regexp)) {
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) => !!context.filename.match(new RegExp(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?.allowedImports || {})
105
- const targetAllowedImports = targetPathRegexs.filter((regex) => !!context.filename.match(new RegExp(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) => !!context.filename.match(new RegExp(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.match(/Declaration$/)).map((d) => d.declaration || d)
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 (!code.match(useRegex(u)) && (!localOption.reportMessage || !reported)) {
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
- if (!declaration) {
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) => !!context.filename.match(new RegExp(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
- return false
52
- }
42
+ const exports = []
43
+
44
+ for (let i of node.body) {
45
+ if (i.type == 'ExportDefaultDeclaration') {
46
+ existDefault = true
53
47
 
54
- return i.type == 'ExportNamedDeclaration'
55
- })
56
- .map((i) => {
57
- const declaration = fetchEdgeDeclaration(i)
48
+ continue
49
+ }
58
50
 
59
- if (declaration.id) {
60
- return declaration.id.name
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
- return declaration
70
- })
71
- .flat(2)
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
- let notExistsExports = [...(!existDefault && option.includes('default') ? ['default'] : []), ...option.filter((o) => o !== 'default' && !exports.includes(o))]
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) => !!context.filename.match(new RegExp(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((item) => item.type === 'ImportDeclaration')
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 && !context.filename.match(new RegExp(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(`${process.cwd()}/${targetModule}`)
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.reduce((prev, current) => {
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 !== 'string') return prev
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
- return prev
30
- }, []),
30
+ })
31
31
  }
32
32
  },
33
33
  }
34
- module.exports.schema = []
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 defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|FilterDropdown$)/'
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'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|FilterDropdown$|^Hoge$)/') } ] },
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を表現するものである場合、親コンポーネント名を "((Fieldset)$|(Fieldsets)$|(FormGroup)$|(FormControl)$|(FormControls)$)" とマッチするものに変更してください`
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: [