eslint-plugin-smarthr 0.2.16 → 0.2.17

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 (29) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -0
  3. package/libs/format_styled_components.js +2 -8
  4. package/package.json +1 -1
  5. package/rules/a11y-clickable-element-has-text/index.js +2 -9
  6. package/rules/a11y-image-has-alt-attribute/index.js +3 -10
  7. package/rules/a11y-input-has-name-attribute/README.md +53 -0
  8. package/rules/a11y-input-has-name-attribute/index.js +64 -0
  9. package/rules/a11y-prohibit-input-placeholder/index.js +6 -22
  10. package/rules/a11y-trigger-has-button/index.js +2 -12
  11. package/rules/best-practice-for-date/index.js +2 -11
  12. package/rules/format-import-path/index.js +1 -7
  13. package/rules/format-translate-component/index.js +6 -15
  14. package/rules/jsx-start-with-spread-attributes/index.js +2 -8
  15. package/rules/no-import-other-domain/index.js +1 -7
  16. package/rules/prohibit-export-array-type/index.js +1 -7
  17. package/rules/prohibit-file-name/index.js +1 -7
  18. package/rules/prohibit-import/index.js +2 -8
  19. package/rules/prohibit-path-within-template-literal/index.js +1 -7
  20. package/rules/redundant-name/index.js +3 -23
  21. package/rules/require-barrel-import/index.js +1 -7
  22. package/rules/require-declaration/index.js +2 -11
  23. package/rules/require-export/index.js +1 -7
  24. package/rules/require-import/index.js +1 -7
  25. package/rules/trim-props/index.js +1 -7
  26. package/test/a11y-input-has-name-attribute.js +54 -0
  27. package/rules/a11y-radio-has-name-attribute/README.md +0 -58
  28. package/rules/a11y-radio-has-name-attribute/index.js +0 -49
  29. package/test/a11y-radio-has-name-attribute.js +0 -36
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.2.17](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.15...v0.2.17) (2023-01-12)
6
+
7
+
8
+ ### Features
9
+
10
+ * a11y-radio-has-name-attributeをinput, select, textareaに対してname属性を必須にするルール a11y-input-has-name-attribute に変更する ([#50](https://github.com/kufu/eslint-plugin-smarthr/issues/50)) ([f278bf8](https://github.com/kufu/eslint-plugin-smarthr/commit/f278bf8f094dab8b01b492ca24444f5ab3812b09))
11
+ * ラジオボタンにnameを強制するルールを追加 ([97792cf](https://github.com/kufu/eslint-plugin-smarthr/commit/97792cf276e3400cb6e01a01ef4cf104de5db190))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * no-import-other-domain、require-barrel-import でignoresオプションを指定するとエラーが発生する問題を修正する ([#49](https://github.com/kufu/eslint-plugin-smarthr/issues/49)) ([8526236](https://github.com/kufu/eslint-plugin-smarthr/commit/85262365d105c1afa4fd53662825c4c51cb84531))
17
+ * prohibit-path-within-template-literalのpathオブジェクト名の解析の際、エラーになるパターンがあったため修正する ([#48](https://github.com/kufu/eslint-plugin-smarthr/issues/48)) ([715e485](https://github.com/kufu/eslint-plugin-smarthr/commit/715e485f3679e4ca2eb9675b67b8f452f9707096))
18
+
5
19
  ### [0.2.16](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.15...v0.2.16) (2022-12-21)
6
20
 
7
21
 
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  - [a11y-clickable-element-has-text](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-clickable-element-has-text)
4
4
  - [a11y-image-has-alt-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-image-has-alt-attribute)
5
+ - [a11y-input-has-name-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-has-name-attribute)
5
6
  - [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
6
7
  - [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
7
8
  - [best-practice-for-date](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-date)
@@ -37,10 +37,7 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES }) => ({
37
37
  if (invalidNameNode) {
38
38
  context.report({
39
39
  node: invalidNameNode,
40
- messageId: 'format-styled-components',
41
- data: {
42
- message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`",
43
- },
40
+ message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`",
44
41
  });
45
42
  }
46
43
  },
@@ -58,10 +55,7 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES }) => ({
58
55
  if (!extended.match(extendedregex)) {
59
56
  context.report({
60
57
  node: node.parent,
61
- messageId: 'format-styled-components',
62
- data: {
63
- message: `${extended}を正規表現 "${extendedregex.toString()}" がmatchする名称に変更してください`,
64
- },
58
+ message: `${extended}を正規表現 "${extendedregex.toString()}" がmatchする名称に変更してください`,
65
59
  });
66
60
  }
67
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -25,10 +25,6 @@ const filterFalsyJSXText = (cs) => cs.filter((c) => (
25
25
  module.exports = {
26
26
  meta: {
27
27
  type: 'problem',
28
- messages: {
29
- 'format-styled-components': '{{ message }}',
30
- 'a11y-clickable-element-has-text': '{{ message }}',
31
- },
32
28
  schema: SCHEMA,
33
29
  },
34
30
  create(context) {
@@ -89,7 +85,7 @@ module.exports = {
89
85
 
90
86
  return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
91
87
  }, null)
92
-
88
+
93
89
  if (result || (existRole && existAriaLabel)) {
94
90
  return true
95
91
  }
@@ -107,10 +103,7 @@ module.exports = {
107
103
  if (!child) {
108
104
  context.report({
109
105
  node,
110
- messageId: 'a11y-clickable-element-has-text',
111
- data: {
112
- message: 'a, button要素にはテキストを設定してください。要素内にアイコン、画像のみを設置する場合はSmartHR UIのvisuallyHiddenText、通常のHTML要素にはaltなどの代替テキスト用属性を指定してください',
113
- },
106
+ message: 'a, button要素にはテキストを設定してください。要素内にアイコン、画像のみを設置する場合はSmartHR UIのvisuallyHiddenText、通常のHTML要素にはaltなどの代替テキスト用属性を指定してください',
114
107
  });
115
108
  }
116
109
  },
@@ -9,7 +9,7 @@ const EXPECTED_NAMES = {
9
9
 
10
10
  const isWithinSvgJsxElement = (node) => {
11
11
  if (
12
- node.type === 'JSXElement' &&
12
+ node.type === 'JSXElement' &&
13
13
  node.openingElement.name?.name === 'svg'
14
14
  ) {
15
15
  return true
@@ -25,10 +25,6 @@ const isWithinSvgJsxElement = (node) => {
25
25
  module.exports = {
26
26
  meta: {
27
27
  type: 'problem',
28
- messages: {
29
- 'format-styled-components': '{{ message }}',
30
- 'a11y-image-has-alt-attribute': '{{ message }}',
31
- },
32
28
  schema: [],
33
29
  },
34
30
  create(context) {
@@ -36,7 +32,7 @@ module.exports = {
36
32
  ...generateTagFormatter({ context, EXPECTED_NAMES }),
37
33
  JSXOpeningElement: (node) => {
38
34
  const matcher = (node.name.name || '').match(/(img|image)$/i) // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
39
- if (matcher) {
35
+ if (matcher) {
40
36
  const alt = node.attributes.find((a) => a.name?.name === 'alt')
41
37
 
42
38
  let message = ''
@@ -52,10 +48,7 @@ module.exports = {
52
48
  if (message) {
53
49
  context.report({
54
50
  node,
55
- messageId: 'a11y-image-has-alt-attribute',
56
- data: {
57
- message,
58
- },
51
+ message,
59
52
  });
60
53
  }
61
54
  }
@@ -0,0 +1,53 @@
1
+ # smarthr/a11y-input-has-name-attribute
2
+
3
+ - input, textarea, select に name 属性を設定することを強制するルールです。
4
+ - input は name を設定することでブラウザの補完機能が有効になる可能性が高まります。
5
+ - 補完機能はブラウザによって異なるため、補完される可能性が上がるよう、name には半角英数の小文字・大文字と一部記号(`_ , [, ]`)のみ利用可能です。
6
+ - input[type="radio"] は name を適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。
7
+
8
+ ## rules
9
+
10
+ ```js
11
+ {
12
+ rules: {
13
+ 'smarthr/a11y-input-has-name-attribute': 'error', // 'warn', 'off'
14
+ },
15
+ }
16
+ ```
17
+
18
+ ## ❌ Incorrect
19
+
20
+ ```jsx
21
+ <RadioButton />
22
+ <Input type="radio" />
23
+ <input type="text" />
24
+ <Textarea />
25
+ <Select />
26
+ ```
27
+
28
+
29
+ ```jsx
30
+ import styled from 'styled-components';
31
+
32
+ const StyledHoge = styled.input``;
33
+ const StyledFuga = styled(Input)``;
34
+ const StyledPiyo = styled(RadioButton)``;
35
+ ```
36
+
37
+ ## ✅ Correct
38
+
39
+ ```jsx
40
+ <RadioButton name="hoge" />
41
+ <Input type="radio" name="fuga" />
42
+ <input type="text" name="any" />
43
+ <Textarea name="some" />
44
+ <Select name="piyo" />
45
+ ```
46
+
47
+ ```jsx
48
+ import styled from 'styled-components';
49
+
50
+ const StyledInput = styled.input``;
51
+ const StyledInput = styled(Input)``;
52
+ const StyledRadioButton = styled(RadioButton)``;
53
+ ```
@@ -0,0 +1,64 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components');
2
+
3
+ const EXPECTED_NAMES = {
4
+ 'RadioButton$': 'RadioButton$',
5
+ '(i|I)nput$': 'Input$',
6
+ '(t|T)extarea$': 'Textarea$',
7
+ '(s|S)elect$': 'Select$',
8
+ }
9
+ const TARGET_TAG_NAME_REGEX = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
10
+ const INPUT_NAME_REGEX = /^[a-zA-Z0-9_\[\]]+$/
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: 'problem',
15
+ messages: {
16
+ 'format-styled-components': '{{ message }}',
17
+ 'a11y-input-has-name-attribute': '{{ message }}',
18
+ },
19
+ schema: [],
20
+ },
21
+ create(context) {
22
+ return {
23
+ ...generateTagFormatter({ context, EXPECTED_NAMES }),
24
+ JSXOpeningElement: (node) => {
25
+ const nodeName = node.name.name || '';
26
+
27
+ if (!nodeName.match(TARGET_TAG_NAME_REGEX)) {
28
+ return
29
+ }
30
+
31
+ const nameAttr = node.attributes.find((a) => a?.name?.name === 'name')
32
+
33
+ if (!nameAttr) {
34
+ const isRadio =
35
+ nodeName.match(/RadioButton$/) ||
36
+ (nodeName.match(/(i|I)nput$/) && node.attributes.some(
37
+ (a) => a.name?.name === 'type' && a.value.value === 'radio'
38
+ ));
39
+
40
+ context.report({
41
+ node,
42
+ messageId: 'a11y-input-has-name-attribute',
43
+ data: {
44
+ message: `${nodeName} にname属性を指定してください。適切に指定することで${isRadio ? 'グループが確立され、キーボード操作しやすくなる' : 'ブラウザの自動補完が有効化される'}などのメリットがあります。`,
45
+ },
46
+ });
47
+ } else {
48
+ const nameValue = nameAttr.value?.value || ''
49
+
50
+ if (nameValue && !nameValue.match(INPUT_NAME_REGEX)) {
51
+ context.report({
52
+ node,
53
+ messageId: 'a11y-input-has-name-attribute',
54
+ data: {
55
+ message: `${nodeName} のname属性の値(${nameValue})はブラウザの自動補完が適切に行えない可能性があるため ${INPUT_NAME_REGEX.toString()} にmatchするフォーマットで命名してください。`,
56
+ },
57
+ });
58
+ }
59
+ }
60
+ },
61
+ };
62
+ },
63
+ };
64
+ module.exports.schema = [];
@@ -12,10 +12,6 @@ const EXPECTED_NAMES = {
12
12
  module.exports = {
13
13
  meta: {
14
14
  type: 'suggestion',
15
- messages: {
16
- 'format-styled-components': '{{ message }}',
17
- 'a11y-prohibit-input-placeholder': '{{ message }}',
18
- },
19
15
  schema: [],
20
16
  },
21
17
  create(context) {
@@ -41,10 +37,7 @@ module.exports = {
41
37
  if (!tooltipMessage) {
42
38
  context.report({
43
39
  node: placeholder,
44
- messageId: 'a11y-prohibit-input-placeholder',
45
- data: {
46
- message: `${name} にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<${name} tooltipMessage="ヒント" />', '<${name} tooltipMessage={hint} placeholder={hint} />')`,
47
- },
40
+ message: `${name} にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<${name} tooltipMessage="ヒント" />', '<${name} tooltipMessage={hint} placeholder={hint} />')`,
48
41
  })
49
42
  }
50
43
  } else if (name.match(/ComboBox$/)) {
@@ -53,10 +46,10 @@ module.exports = {
53
46
 
54
47
  node.attributes.forEach((a) => {
55
48
  switch(a.name?.name) {
56
- case 'defaultItem':
49
+ case 'defaultItem':
57
50
  defaultItem = a
58
51
  break
59
- case 'dropdownHelpMessage':
52
+ case 'dropdownHelpMessage':
60
53
  dropdownHelpMessage = a
61
54
  break
62
55
  }
@@ -65,30 +58,21 @@ module.exports = {
65
58
  if (defaultItem) {
66
59
  context.report({
67
60
  node: placeholder,
68
- messageId: 'a11y-prohibit-input-placeholder',
69
- data: {
70
- message: `${name} にはdefaultItemが設定されているため、placeholder属性を閲覧出来ません。削除してください。`,
71
- },
61
+ message: `${name} にはdefaultItemが設定されているため、placeholder属性を閲覧出来ません。削除してください。`,
72
62
  })
73
63
  } else if (!dropdownHelpMessage) {
74
64
  context.report({
75
65
  node: placeholder,
76
- messageId: 'a11y-prohibit-input-placeholder',
77
- data: {
78
- message: `${name} にはplaceholder属性は設定せず、以下のいずれか、もしくは組み合わせての対応を検討してください。
66
+ message: `${name} にはplaceholder属性は設定せず、以下のいずれか、もしくは組み合わせての対応を検討してください。
79
67
  - 選択肢をどんな値で絞り込めるかの説明をしたい場合は dropdownHelpMessage 属性に変更してください。
80
68
  - 空の値の説明のためにplaceholderを利用している場合は defaultItem 属性に変更してください。
81
69
  - 上記以外の説明を行いたい場合、ヒント用要素を設置してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
82
- },
83
70
  })
84
71
  }
85
72
  } else {
86
73
  context.report({
87
74
  node: placeholder,
88
- messageId: 'a11y-prohibit-input-placeholder',
89
- data: {
90
- message: `${name} にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
91
- },
75
+ message: `${name} にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
92
76
  })
93
77
  }
94
78
  }
@@ -18,10 +18,6 @@ const filterFalsyJSXText = (cs) => cs.filter((c) => (
18
18
  module.exports = {
19
19
  meta: {
20
20
  type: 'problem',
21
- messages: {
22
- 'format-styled-components': '{{ message }}',
23
- 'a11y-trigger-has-button': '{{ message }}',
24
- },
25
21
  schema: [],
26
22
  },
27
23
  create(context) {
@@ -52,10 +48,7 @@ module.exports = {
52
48
  if (children.length > 1) {
53
49
  context.report({
54
50
  node,
55
- messageId: 'a11y-trigger-has-button',
56
- data: {
57
- message: `${match[1]}Trigger の直下には複数のコンポーネントを設置することは出来ません。buttonコンポーネントが一つだけ設置されている状態にしてください`,
58
- },
51
+ message: `${match[1]}Trigger の直下には複数のコンポーネントを設置することは出来ません。buttonコンポーネントが一つだけ設置されている状態にしてください`,
59
52
  })
60
53
 
61
54
  return
@@ -74,10 +67,7 @@ module.exports = {
74
67
  ) {
75
68
  context.report({
76
69
  node: c,
77
- messageId: 'a11y-trigger-has-button',
78
- data: {
79
- message: `${match[1]}Trigger の直下にはbuttonコンポーネントのみ設置してください`,
80
- },
70
+ message: `${match[1]}Trigger の直下にはbuttonコンポーネントのみ設置してください`,
81
71
  })
82
72
  }
83
73
  })
@@ -1,9 +1,6 @@
1
1
  module.exports = {
2
2
  meta: {
3
3
  type: 'problem',
4
- messages: {
5
- 'best-practice-for-date': '{{ message }}',
6
- },
7
4
  schema: [],
8
5
  },
9
6
  create(context) {
@@ -15,10 +12,7 @@ module.exports = {
15
12
  ) {
16
13
  context.report({
17
14
  node,
18
- messageId: 'best-practice-for-date',
19
- data: {
20
- message: "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。",
21
- },
15
+ message: "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。",
22
16
  });
23
17
  }
24
18
  },
@@ -29,10 +23,7 @@ module.exports = {
29
23
  ) {
30
24
  context.report({
31
25
  node,
32
- messageId: 'best-practice-for-date',
33
- data: {
34
- message: 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください',
35
- },
26
+ message: 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください',
36
27
  });
37
28
  }
38
29
  },
@@ -82,9 +82,6 @@ const calculateRelativeImportPath = ({ importPath, filteredDirs, filteredPaths }
82
82
  module.exports = {
83
83
  meta: {
84
84
  type: 'suggestion',
85
- messages: {
86
- 'format-import-path': '{{ message }}',
87
- },
88
85
  fixable: 'code',
89
86
  schema: SCHEMA,
90
87
  },
@@ -122,10 +119,7 @@ module.exports = {
122
119
  if (importPath !== fixedImportPath) {
123
120
  context.report({
124
121
  node,
125
- messageId: 'format-import-path',
126
- data: {
127
- message: `${fixedImportPath} に修正してください`,
128
- },
122
+ message: `${fixedImportPath} に修正してください`,
129
123
  fix: (fixer) => fixer.replaceText(
130
124
  node,
131
125
  context.getSourceCode().getText(node).replace(new RegExp(`from '${importPath}'$`), `from '${fixedImportPath}'`)
@@ -16,9 +16,6 @@ const SCHEMA = [
16
16
  module.exports = {
17
17
  meta: {
18
18
  type: 'suggestion',
19
- messages: {
20
- 'format-translate-component': '{{ message }}',
21
- },
22
19
  schema: SCHEMA,
23
20
  },
24
21
  create(context) {
@@ -32,10 +29,7 @@ module.exports = {
32
29
  if (hit) {
33
30
  context.report({
34
31
  node,
35
- messageId: 'format-translate-component',
36
- data: {
37
- message: `${hit} 属性は使用せず、 ${componentPath || componentName} コンポーネントを利用してください`,
38
- },
32
+ message: `${hit} 属性は使用せず、 ${componentPath || componentName} コンポーネントを利用してください`,
39
33
  });
40
34
  }
41
35
  }
@@ -51,19 +45,19 @@ module.exports = {
51
45
 
52
46
  node.parent.children.forEach((c) => {
53
47
  switch (c.type) {
54
- case 'JSXText':
48
+ case 'JSXText':
55
49
  // HINT: 空白と改行のみの場合はテキストが存在する扱いにはしない
56
50
  if (c.value.replace(/(\s|\n)+/g, '')) {
57
51
  existValidChild = true
58
52
  }
59
53
 
60
54
  break
61
- case 'JSXExpressionContainer':
55
+ case 'JSXExpressionContainer':
62
56
  // TODO 変数がstringのみか判定できるなら対応したい
63
57
  existValidChild = true
64
58
 
65
59
  break
66
- case 'JSXElement':
60
+ case 'JSXElement':
67
61
  if (c.openingElement.name.name !== 'br') {
68
62
  existNotBrElement = true
69
63
  }
@@ -77,16 +71,13 @@ module.exports = {
77
71
  return `${componentName} 内では <br /> 以外のタグは使えません`
78
72
  } else if (!existValidChild && !node.attributes.some((a) => a.name.name === 'dangerouslySetInnerHTML')) {
79
73
  return `${componentName} 内には必ずテキストを設置してください`
80
- }
74
+ }
81
75
  })()
82
76
 
83
77
  if (message) {
84
78
  context.report({
85
79
  node,
86
- messageId: 'format-translate-component',
87
- data: {
88
- message,
89
- },
80
+ message,
90
81
  });
91
82
  }
92
83
  }
@@ -11,16 +11,13 @@ const SCHEMA = [
11
11
  module.exports = {
12
12
  meta: {
13
13
  type: 'problem',
14
- messages: {
15
- 'jsx-start-with-spread-attributes': '{{ message }}',
16
- },
17
14
  fixable: 'code',
18
15
  schema: SCHEMA,
19
16
  },
20
17
  create(context) {
21
18
  return {
22
19
  JSXSpreadAttribute: (node) => {
23
- // HINT: -2: 計算中 -1: 見つからなかった >= 0: 見つかった
20
+ // HINT: -2: 計算中 -1: 見つからなかった >= 0: 見つかった
24
21
  const insertIndex = node.parent.attributes.reduce((h, a, i) => {
25
22
  if (h === -2) {
26
23
  if (a === node) {
@@ -40,10 +37,7 @@ module.exports = {
40
37
 
41
38
  context.report({
42
39
  node,
43
- messageId: 'jsx-start-with-spread-attributes',
44
- data: {
45
- message: `"${attributeCode}" は意図しない上書きを防ぐため、spread attributesでない属性より先に記述してください`,
46
- },
40
+ message: `"${attributeCode}" は意図しない上書きを防ぐため、spread attributesでない属性より先に記述してください`,
47
41
  fix: option?.fix ? (fixer) => {
48
42
  const elementNode = node.parent
49
43
  const sortedAttributes = [...elementNode.attributes].reduce((p, a, i) => {
@@ -36,9 +36,6 @@ const SCHEMA = [
36
36
  module.exports = {
37
37
  meta: {
38
38
  type: 'suggestion',
39
- messages: {
40
- 'no-import-other-domain': '{{ message }}',
41
- },
42
39
  fixable: 'code',
43
40
  schema: SCHEMA,
44
41
  },
@@ -122,10 +119,7 @@ module.exports = {
122
119
 
123
120
  context.report({
124
121
  node,
125
- messageId: 'no-import-other-domain',
126
- data: {
127
- message: `別ドメインから ${importPath}${deniedModules.length ? ` の ${deniedModules.join(', ')}` : ''} がimportされています。`,
128
- },
122
+ message: `別ドメインから ${importPath}${deniedModules.length ? ` の ${deniedModules.join(', ')}` : ''} がimportされています。`,
129
123
  })
130
124
  }
131
125
  },
@@ -1,9 +1,6 @@
1
1
  module.exports = {
2
2
  meta: {
3
3
  type: 'suggestion',
4
- messages: {
5
- 'prohibit-export-array-type': '{{ message }}',
6
- },
7
4
  schema: [],
8
5
  },
9
6
  create(context) {
@@ -11,10 +8,7 @@ module.exports = {
11
8
  if (node.declaration?.typeAnnotation?.type === 'TSArrayType') {
12
9
  context.report({
13
10
  node,
14
- messageId: 'prohibit-export-array-type',
15
- data: {
16
- message: '利用する際、配列かどうかわかりにくいため、配列ではない状態でexportしてください',
17
- },
11
+ message: '利用する際、配列かどうかわかりにくいため、配列ではない状態でexportしてください',
18
12
  })
19
13
  }
20
14
  }
@@ -12,9 +12,6 @@ const SCHEMA = [{
12
12
  module.exports = {
13
13
  meta: {
14
14
  type: 'suggestion',
15
- messages: {
16
- 'prohibit-file-name': '{{ message }}',
17
- },
18
15
  schema: SCHEMA,
19
16
  },
20
17
  create(context) {
@@ -48,10 +45,7 @@ module.exports = {
48
45
  messages.forEach((message) => {
49
46
  context.report({
50
47
  node,
51
- messageId: 'prohibit-file-name',
52
- data: {
53
- message,
54
- },
48
+ message,
55
49
  })
56
50
  })
57
51
  },
@@ -35,9 +35,6 @@ const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof
35
35
  module.exports = {
36
36
  meta: {
37
37
  type: 'suggestion',
38
- messages: {
39
- 'prohibit_import': '{{ message }}',
40
- },
41
38
  schema: SCHEMA,
42
39
  },
43
40
  create(context) {
@@ -74,7 +71,7 @@ module.exports = {
74
71
  if (actualTarget !== sourceValue) {
75
72
  return
76
73
  }
77
-
74
+
78
75
  const useImported = (() => {
79
76
  if (!Array.isArray(imported)) {
80
77
  return !!imported
@@ -88,10 +85,7 @@ module.exports = {
88
85
  if (useImported) {
89
86
  context.report({
90
87
  node,
91
- messageId: 'prohibit_import',
92
- data: {
93
- message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
94
- },
88
+ message: reportMessage ? reportMessage.replaceAll('{{module}}', node.source.value).replaceAll('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
95
89
  });
96
90
  }
97
91
  })
@@ -29,9 +29,6 @@ const SCHEMA = [
29
29
  module.exports = {
30
30
  meta: {
31
31
  type: 'suggestion',
32
- messages: {
33
- 'prohibit-path-within-template-literal': '{{ message }}',
34
- },
35
32
  schema: SCHEMA,
36
33
  },
37
34
  create(context) {
@@ -46,10 +43,7 @@ module.exports = {
46
43
  if (name) {
47
44
  context.report({
48
45
  node: exp,
49
- messageId: 'prohibit-path-within-template-literal',
50
- data: {
51
- message: `${name}は \`\` で囲まないでください。queryStringを結合するなどのURL生成は ${name} 内で行います。 (例: ${name}({ query: { hoge: 'abc' } })`,
52
- },
46
+ message: `${name}は \`\` で囲まないでください。queryStringを結合するなどのURL生成は ${name} 内で行います。 (例: ${name}({ query: { hoge: 'abc' } })`,
53
47
  });
54
48
  }
55
49
  })
@@ -191,10 +191,7 @@ const handleReportBetterName = ({
191
191
  if (candidates.length > 0) {
192
192
  context.report({
193
193
  node,
194
- messageId: `${key}-name`,
195
- data: {
196
- message: generateMessage({ name, betterName: candidates.join(', ') }),
197
- },
194
+ message: generateMessage({ name, betterName: candidates.join(', ') }),
198
195
  });
199
196
  }
200
197
  }
@@ -226,10 +223,7 @@ const generateTypeRedundant = (args) => {
226
223
  SuffixedName = `${typeName}${suffix[0]}`
227
224
  report = {
228
225
  node,
229
- messageId: 'type-name/invalid-suffix',
230
- data: {
231
- message: `type ${typeName} の名称の末尾に ${suffix.join(', ')} ${suffix.length > 1 ? 'のいずれか' : ''}を追加してください`,
232
- },
226
+ message: `type ${typeName} の名称の末尾に ${suffix.join(', ')} ${suffix.length > 1 ? 'のいずれか' : ''}を追加してください`,
233
227
  }
234
228
  }
235
229
 
@@ -246,10 +240,7 @@ const generateTypeRedundant = (args) => {
246
240
  if (SuffixedName !== betterName) {
247
241
  report = {
248
242
  node,
249
- messageId: 'type-name',
250
- data: {
251
- message: `type ${typeName} の名称からパスで推測できる箇所を取り除いてしてください (例: ${betterName})`,
252
- },
243
+ message: `type ${typeName} の名称からパスで推測できる箇所を取り除いてしてください (例: ${betterName})`,
253
244
  }
254
245
  }
255
246
 
@@ -392,17 +383,6 @@ const generateMethodRedundant = (args) => {
392
383
  module.exports = {
393
384
  meta: {
394
385
  type: 'suggestion',
395
- messages: {
396
- 'file-name': ' {{ message }}',
397
- 'type-name': '{{ message }}',
398
- 'type-name/invalid-suffix': '{{ message }}',
399
- 'property-name': ' {{ message }}',
400
- 'function-name': ' {{ message }}',
401
- 'functionParams-name': ' {{ message }}',
402
- 'variable-name': ' {{ message }}',
403
- 'class-name': ' {{ message }}',
404
- 'method-name': ' {{ message }}',
405
- },
406
386
  schema: SCHEMA,
407
387
  },
408
388
  create(context) {
@@ -75,9 +75,6 @@ const SCHEMA = [
75
75
  module.exports = {
76
76
  meta: {
77
77
  type: 'suggestion',
78
- messages: {
79
- 'require-barrel-import': '{{ message }}',
80
- },
81
78
  schema: SCHEMA,
82
79
  },
83
80
  create(context) {
@@ -183,10 +180,7 @@ module.exports = {
183
180
 
184
181
  context.report({
185
182
  node,
186
- messageId: 'require-barrel-import',
187
- data: {
188
- message: deniedModules.length ? `${deniedModules.join(', ')} は ${noExt} からimportしてください` : `${noExt} からimportするか、${barrel} のbarrelファイルを削除して直接import可能にしてください`,
189
- },
183
+ message: deniedModules.length ? `${deniedModules.join(', ')} は ${noExt} からimportしてください` : `${noExt} からimportするか、${barrel} のbarrelファイルを削除して直接import可能にしてください`,
190
184
  });
191
185
  }
192
186
  },
@@ -43,9 +43,6 @@ const useRegex = (use) => {
43
43
  module.exports = {
44
44
  meta: {
45
45
  type: 'suggestion',
46
- messages: {
47
- 'require-declaration': '{{ message }}',
48
- },
49
46
  schema: SCHEMA,
50
47
  },
51
48
  create(context) {
@@ -102,10 +99,7 @@ module.exports = {
102
99
  if (!hit) {
103
100
  context.report({
104
101
  node,
105
- messageId: 'require-declaration',
106
- data: {
107
- message: localOption.reportMessage || `${localOption.type} ${requireDeclaration}が宣言されていません`,
108
- },
102
+ message: localOption.reportMessage || `${localOption.type} ${requireDeclaration}が宣言されていません`,
109
103
  })
110
104
  } else if (localOption.use) {
111
105
  const code = context.getSourceCode().getText(hit)
@@ -115,10 +109,7 @@ module.exports = {
115
109
  if (!code.match(useRegex(u)) && (!localOption.reportMessage || !reported)) {
116
110
  context.report({
117
111
  node: hit,
118
- messageId: 'require-declaration',
119
- data: {
120
- message: localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`,
121
- },
112
+ message: localOption.reportMessage || `${localOption.type} ${requireDeclaration} では ${u} を利用してください`,
122
113
  })
123
114
  reported = true
124
115
  }
@@ -23,9 +23,6 @@ const fetchEdgeDeclaration = (node) => {
23
23
  module.exports = {
24
24
  meta: {
25
25
  type: 'suggestion',
26
- messages: {
27
- 'require-export': '{{ message }}',
28
- },
29
26
  schema: SCHEMA,
30
27
  },
31
28
  create(context) {
@@ -76,10 +73,7 @@ module.exports = {
76
73
  if (notExistsExports.length) {
77
74
  context.report({
78
75
  node,
79
- messageId: 'require-export',
80
- data: {
81
- message: `${notExistsExports.join(', ')} をexportしてください`,
82
- },
76
+ message: `${notExistsExports.join(', ')} をexportしてください`,
83
77
  })
84
78
  }
85
79
  })
@@ -35,9 +35,6 @@ const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof
35
35
  module.exports = {
36
36
  meta: {
37
37
  type: 'suggestion',
38
- messages: {
39
- 'require_import': '{{ message }}',
40
- },
41
38
  schema: SCHEMA,
42
39
  },
43
40
  create(context) {
@@ -81,10 +78,7 @@ module.exports = {
81
78
  const reporter = (item) => {
82
79
  context.report({
83
80
  node,
84
- messageId: 'require_import',
85
- data: {
86
- message: reportMessage ? reportMessage.replaceAll('{{module}}', actualTarget).replaceAll('{{export}}', item) : defaultReportMessage(actualTarget, item)
87
- },
81
+ message: reportMessage ? reportMessage.replaceAll('{{module}}', actualTarget).replaceAll('{{export}}', item) : defaultReportMessage(actualTarget, item)
88
82
  })
89
83
  }
90
84
 
@@ -1,9 +1,6 @@
1
1
  module.exports = {
2
2
  meta: {
3
3
  type: 'suggestion',
4
- messages: {
5
- 'trim-props': '{{ message }}',
6
- },
7
4
  schema: [],
8
5
  fixable: 'whitespace',
9
6
  },
@@ -20,10 +17,7 @@ module.exports = {
20
17
  return context.report({
21
18
  node,
22
19
  loc: current.loc,
23
- messageId: 'trim-props',
24
- data: {
25
- message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください',
26
- },
20
+ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください',
27
21
  fix(fixer) {
28
22
  return fixer.replaceTextRange([attribute.range[0] + 1, attribute.range[1] - 1], props.trim())
29
23
  },
@@ -0,0 +1,54 @@
1
+ const rule = require('../rules/a11y-input-has-name-attribute');
2
+ const RuleTester = require('eslint').RuleTester;
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ ecmaVersion: 2018,
7
+ ecmaFeatures: {
8
+ experimentalObjectRestSpread: true,
9
+ jsx: true,
10
+ },
11
+ sourceType: 'module',
12
+ },
13
+ });
14
+
15
+ ruleTester.run('a11y-input-has-name-attribute', rule, {
16
+ valid: [
17
+ { code: `import styled from 'styled-components'` },
18
+ { code: `import styled, { css } from 'styled-components'` },
19
+ { code: `import { css } from 'styled-components'` },
20
+ { code: 'const HogeInput = styled.input``' },
21
+ { code: 'const HogeInput = styled(Input)``' },
22
+ { code: 'const HogeRadioButton = styled(RadioButton)``' },
23
+ { code: 'const HogeSelect = styled(Select)``' },
24
+ { code: 'const HogeSelect = styled.select``' },
25
+ { code: 'const HogeTextarea = styled(Textarea)``' },
26
+ { code: 'const HogeTextarea = styled.textarea``' },
27
+ { code: '<input type="radio" name="hoge" />' },
28
+ { code: '<HogeInput type="radio" name="hoge" />' },
29
+ { code: '<HogeRadioButton name="hoge" />' },
30
+ { code: '<textarea name="hoge" />' },
31
+ { code: '<HogeTextarea name="hoge" />' },
32
+ { code: '<select name="hoge" />' },
33
+ { code: '<Select name="hoge[0][Fuga]" />' },
34
+ ],
35
+ invalid: [
36
+ { code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
37
+ { code: 'const Hoge = styled.input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
38
+ { code: 'const Hoge = styled.Input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
39
+ { code: 'const Hoge = styled(RadioButton)``', errors: [ { message: `Hogeを正規表現 "/RadioButton$/" がmatchする名称に変更してください` } ] },
40
+ { code: '<input />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
41
+ { code: '<input type="date" />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
42
+ { code: '<Input type="checkbox" />', errors: [ { message: 'Input にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
43
+ { code: '<input type="radio" />', errors: [ { message: 'input にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] },
44
+ { code: '<HogeInput type="radio" />', errors: [ { message: 'HogeInput にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] },
45
+ { code: '<HogeInput type="text" />', errors: [ { message: 'HogeInput にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
46
+ { code: '<HogeRadioButton />', errors: [ { message: 'HogeRadioButton にname属性を指定してください。適切に指定することでグループが確立され、キーボード操作しやすくなるなどのメリットがあります。' } ] },
47
+ { code: '<select />', errors: [ { message: 'select にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
48
+ { code: '<HogeSelect />', errors: [ { message: 'HogeSelect にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
49
+ { code: '<textarea />', errors: [ { message: 'textarea にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
50
+ { code: '<HogeTextarea />', errors: [ { message: 'HogeTextarea にname属性を指定してください。適切に指定することでブラウザの自動補完が有効化されるなどのメリットがあります。' } ] },
51
+ { code: '<input type="radio" name="ほげ" />', errors: [ { message: 'input のname属性の値(ほげ)はブラウザの自動補完が適切に行えない可能性があるため /^[a-zA-Z0-9_\\[\\]]+$/ にmatchするフォーマットで命名してください。' } ] },
52
+ { code: '<select name="hoge[fuga][0][あいうえお]" />', errors: [ { message: 'select のname属性の値(hoge[fuga][0][あいうえお])はブラウザの自動補完が適切に行えない可能性があるため /^[a-zA-Z0-9_\\[\\]]+$/ にmatchするフォーマットで命名してください。' } ] },
53
+ ],
54
+ });
@@ -1,58 +0,0 @@
1
- # smarthr/a11y-radio-has-bane-attribute
2
-
3
- - ラジオボタンに name 属性を設定することを強制するルールです。
4
- - name を適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。
5
-
6
- ## rules
7
-
8
- ```js
9
- {
10
- rules: {
11
- 'smarthr/a11y-radio-has-name-attribute': 'error', // 'warn', 'off'
12
- },
13
- }
14
- ```
15
-
16
- ## ❌ Incorrect
17
-
18
- ```jsx
19
- <RadioButton />
20
- ```
21
-
22
- ```jsx
23
- <Input type="radio" />
24
- ```
25
-
26
- ```jsx
27
- <input type="radio" />
28
- ```
29
-
30
- ```jsx
31
- import styled from 'styled-components';
32
-
33
- const StyledHoge = styled.input``;
34
- const StyledFuga = styled(Input)``;
35
- const StyledPiyo = styled(RadioButton)``;
36
- ```
37
-
38
- ## ✅ Correct
39
-
40
- ```jsx
41
- <RadioButton name="hoge" />
42
- ```
43
-
44
- ```jsx
45
- <Input type="radio" name="hoge" />
46
- ```
47
-
48
- ```jsx
49
- <input type="radio" name="hoge" />
50
- ```
51
-
52
- ```jsx
53
- import styled from 'styled-components';
54
-
55
- const StyledInput = styled.input``;
56
- const StyledInput = styled(Input)``;
57
- const StyledRadioButton = styled(RadioButton)``;
58
- ```
@@ -1,49 +0,0 @@
1
- const { generateTagFormatter } = require('../../libs/format_styled_components');
2
-
3
- const EXPECTED_NAMES = {
4
- RadioButton$: 'RadioButton$',
5
- '(i|I)nput$': 'Input$',
6
- };
7
-
8
- module.exports = {
9
- meta: {
10
- type: 'problem',
11
- messages: {
12
- 'format-styled-components': '{{ message }}',
13
- 'a11y-radio-has-name-attribute': '{{ message }}',
14
- },
15
- schema: [],
16
- },
17
- create(context) {
18
- return {
19
- ...generateTagFormatter({ context, EXPECTED_NAMES }),
20
- JSXOpeningElement: (node) => {
21
- const name = node.name.name || '';
22
- const isRadio =
23
- (name.match(/(i|I)nput$/) &&
24
- node.attributes.some(
25
- (a) => a.name?.name === 'type' && a.value.value === 'radio'
26
- )) ||
27
- name.match(/RadioButton$/);
28
-
29
- if (!isRadio) return;
30
-
31
- const hasName = node.attributes.some(
32
- (attribute) => attribute?.name?.name === 'name'
33
- );
34
-
35
- if (!hasName) {
36
- context.report({
37
- node,
38
- messageId: 'a11y-radio-has-name-attribute',
39
- data: {
40
- message:
41
- 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。',
42
- },
43
- });
44
- }
45
- },
46
- };
47
- },
48
- };
49
- module.exports.schema = [];
@@ -1,36 +0,0 @@
1
- const rule = require('../rules/a11y-radio-has-name-attribute');
2
- const RuleTester = require('eslint').RuleTester;
3
-
4
- const ruleTester = new RuleTester({
5
- parserOptions: {
6
- ecmaVersion: 2018,
7
- ecmaFeatures: {
8
- experimentalObjectRestSpread: true,
9
- jsx: true,
10
- },
11
- sourceType: 'module',
12
- },
13
- });
14
-
15
- ruleTester.run('a11y-radio-has-name-attribute', rule, {
16
- valid: [
17
- { code: `import styled from 'styled-components'` },
18
- { code: `import styled, { css } from 'styled-components'` },
19
- { code: `import { css } from 'styled-components'` },
20
- { code: 'const HogeInput = styled.input``' },
21
- { code: 'const HogeInput = styled(Input)``' },
22
- { code: 'const HogeRadioButton = styled(RadioButton)``' },
23
- { code: '<input type="radio" name="hoge" />' },
24
- { code: '<HogeInput type="radio" name="hoge" />' },
25
- { code: '<HogeRadioButton name="hoge" />' },
26
- ],
27
- invalid: [
28
- { code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
29
- { code: 'const Hoge = styled.input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
30
- { code: 'const Hoge = styled.Input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
31
- { code: 'const Hoge = styled(RadioButton)``', errors: [ { message: `Hogeを正規表現 "/RadioButton$/" がmatchする名称に変更してください` } ] },
32
- { code: '<input type="radio" />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
33
- { code: '<HogeInput type="radio" />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
34
- { code: '<HogeRadioButton />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
35
- ],
36
- });