eslint-plugin-smarthr 0.4.0 → 0.4.2

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 CHANGED
@@ -2,6 +2,26 @@
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.4.2](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.4.1...v0.4.2) (2024-03-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * best-practice-for-button-elementを追加 ([#115](https://github.com/kufu/eslint-plugin-smarthr/issues/115)) ([c19ab10](https://github.com/kufu/eslint-plugin-smarthr/commit/c19ab1062d00aa000cfcf9b86535af21b47a0ead))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * a11y-numbered-text-within-ol でwidthの値が誤検知されてしまう場合に対応する ([#117](https://github.com/kufu/eslint-plugin-smarthr/issues/117)) ([3741d54](https://github.com/kufu/eslint-plugin-smarthr/commit/3741d5412f62ac04286e0626e37d2ca3c57c4f60))
16
+
17
+ ### [0.4.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.4.0...v0.4.1) (2024-02-21)
18
+
19
+
20
+ ### Features
21
+
22
+ * a11y-numbered-text-within-olを追加する ([#105](https://github.com/kufu/eslint-plugin-smarthr/issues/105)) ([167b92f](https://github.com/kufu/eslint-plugin-smarthr/commit/167b92f0f29db8ee9a446d25e09e04a7b11ce340))
23
+ * ComboBoxなどのinputAttributesでtitle属性が指定された場合、擬似的にラベルが付いていると判定するように修正 ([#113](https://github.com/kufu/eslint-plugin-smarthr/issues/113)) ([5f3b594](https://github.com/kufu/eslint-plugin-smarthr/commit/5f3b5943ec64a18d094a9c66627ad1db2bbabe08))
24
+
5
25
  ## [0.4.0](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.27...v0.4.0) (2024-02-05)
6
26
 
7
27
 
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - [a11y-image-has-alt-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-image-has-alt-attribute)
8
8
  - [a11y-input-has-name-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-has-name-attribute)
9
9
  - [a11y-input-in-form-control](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-in-form-control)
10
+ - [a11y-numbered-text-within-ol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-numbered-text-within-ol)
10
11
  - [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
11
12
  - [a11y-prohibit-useless-sectioning-fragment](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-useless-sectioning-fragment)
12
13
  - [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
@@ -18,6 +18,44 @@ const checkImportStyledComponents = (node, context) => {
18
18
  }
19
19
  }
20
20
 
21
+ const getStyledComponentBaseName = (node) => {
22
+ let base = null
23
+
24
+ if (!node.init) {
25
+ return base
26
+ }
27
+
28
+ const tag = node.init.tag || node.init
29
+
30
+ if (tag.object?.name === STYLED_COMPONENTS_METHOD) {
31
+ base = tag.property.name
32
+ } else if (tag.callee) {
33
+ const callee = tag.callee
34
+
35
+ switch (STYLED_COMPONENTS_METHOD) {
36
+ case callee.name: {
37
+ const arg = tag.arguments[0]
38
+ base = arg.name || arg.value
39
+ break
40
+ }
41
+ case callee.callee?.name: {
42
+ const arg = callee.arguments[0]
43
+ base = arg.name || arg.value
44
+ break
45
+ }
46
+ case callee.object?.name:
47
+ base = callee.property.name
48
+ break
49
+ case callee.object?.callee?.name:
50
+ const arg = callee.object.arguments[0]
51
+ base = arg.name || arg.value
52
+ break
53
+ }
54
+ }
55
+
56
+ return base
57
+ }
58
+
21
59
  const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) => {
22
60
  const entriesesTagNames = Object.entries(EXPECTED_NAMES).map(([b, e]) => [ new RegExp(b), new RegExp(e) ])
23
61
  const entriesesUnTagNames = UNEXPECTED_NAMES ? Object.entries(UNEXPECTED_NAMES).map(([b, e]) => {
@@ -52,39 +90,7 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) =>
52
90
  }
53
91
  },
54
92
  VariableDeclarator: (node) => {
55
- if (!node.init) {
56
- return
57
- }
58
-
59
- const tag = node.init.tag || node.init
60
-
61
- let base = null
62
-
63
- if (tag.object?.name === STYLED_COMPONENTS_METHOD) {
64
- base = tag.property.name
65
- } else if (tag.callee) {
66
- const callee = tag.callee
67
-
68
- switch (STYLED_COMPONENTS_METHOD) {
69
- case callee.name: {
70
- const arg = tag.arguments[0]
71
- base = arg.name || arg.value
72
- break
73
- }
74
- case callee.callee?.name: {
75
- const arg = callee.arguments[0]
76
- base = arg.name || arg.value
77
- break
78
- }
79
- case callee.object?.name:
80
- base = callee.property.name
81
- break
82
- case callee.object?.callee?.name:
83
- const arg = callee.object.arguments[0]
84
- base = arg.name || arg.value
85
- break
86
- }
87
- }
93
+ const base = getStyledComponentBaseName(node)
88
94
 
89
95
  if (base) {
90
96
  const extended = node.id.name
@@ -117,4 +123,4 @@ const generateTagFormatter = ({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }) =>
117
123
  }
118
124
  }
119
125
 
120
- module.exports = { generateTagFormatter }
126
+ module.exports = { generateTagFormatter, checkImportStyledComponents, getStyledComponentBaseName }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -6,7 +6,9 @@
6
6
  - URL遷移を行う場合、hrefが設定されていないとキーボード操作やコンテキストメニューからの遷移ができなくなります
7
7
  - これらの操作は href属性を参照します
8
8
  - 無効化されたリンクであることを表したい場合 `href={undefined}` を設定してください
9
- - checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます。
9
+ - checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます
10
+ - react-router-dom packageを利用している場合、a要素にto属性が指定されている場合、href属性が指定されているものとして許容します
11
+ - next/link コンポーネント直下のa要素にhref属性が指定されていないことを許容します
10
12
 
11
13
  ## rules
12
14
 
@@ -1,6 +1,11 @@
1
1
  # smarthr/a11y-clickable-element-has-text
2
2
 
3
- - ButtonやAnchor,Link コンポーネントにテキスト要素が設定されていない場合、アクセシビリティの問題が発生する可能性を防ぐルールです
3
+ - ButtonやAnchor,Link コンポーネントにテキスト要素が設定されていない場合、スクリーンリーダーで押したものが何だったのかわからない等の問題が発生する可能性を防ぐルールです
4
+ - a要素やbutton要素の中身にtextがあることを担保するルール
5
+ - 画像要素の場合は `visuallyHiddenText`や `alt`等代替テキストを設定する
6
+ - SVGの場合はrole="img" と aria-labelを設定する
7
+ - linkとかanchorのchildrenを含まない要素はチェックしない
8
+ - 例) `<YyyAnchor />`
4
9
 
5
10
  ## rules
6
11
 
@@ -38,7 +43,7 @@
38
43
  ```
39
44
 
40
45
  ```jsx
41
- <XxxAnchor>>
46
+ <XxxAnchor>
42
47
  <XxxTextYyyy />
43
48
  </XxxAnchor>
44
49
  ```
@@ -57,7 +62,7 @@
57
62
  </XxxLink>
58
63
  ```
59
64
  ```jsx
60
- <XxxAnchor>>
65
+ <XxxAnchor>
61
66
  <YyyIcon visuallyHiddenText="hoge" />
62
67
  </XxxAnchor>
63
68
  ```
@@ -68,11 +73,11 @@
68
73
  ```
69
74
 
70
75
  ```jsx
71
- <YyyAnchoor />
76
+ <YyyAnchor />
72
77
  ```
73
78
 
74
79
  ```jsx
75
- <XxxAnchor>>
80
+ <XxxAnchor>
76
81
  <XxxText />
77
82
  </XxxAnchor>
78
83
  ```
@@ -30,9 +30,9 @@ const REGEX_NLSP = /^\s*\n+\s*$/
30
30
  const REGEX_CLICKABLE_ELEMENT = /^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/
31
31
  const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
32
32
  const REGEX_TEXT_COMPONENT = /(Text|Message)$/
33
+ const REGEX_JSX_TYPE = /^(JSXText|JSXExpressionContainer)$/
33
34
 
34
- const HIT_TYPES_RECURSICVE_SEARCH = ['JSXText', 'JSXExpressionContainer']
35
- const HIT_TEXT_ATTRS = ['visuallyHiddenText', 'alt']
35
+ const HIT_TEXT_ATTR = 'alt'
36
36
 
37
37
  const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
38
38
  const checkFalsyJSXText = (c) => (
@@ -68,7 +68,7 @@ module.exports = {
68
68
  }
69
69
 
70
70
  const recursiveSearch = (c) => {
71
- if (HIT_TYPES_RECURSICVE_SEARCH.includes(c.type)) {
71
+ if (REGEX_JSX_TYPE.test(c.type)) {
72
72
  return true
73
73
  }
74
74
 
@@ -97,7 +97,7 @@ module.exports = {
97
97
 
98
98
  if (
99
99
  prev ||
100
- !HIT_TEXT_ATTRS.includes(a.name.name)
100
+ HIT_TEXT_ATTR !== a.name.name
101
101
  ) {
102
102
  return prev
103
103
  }
@@ -63,6 +63,7 @@ const IGNORE_INPUT_CHECK_PARENT_TYPE = /^(Program|ExportNamedDeclaration)$/
63
63
 
64
64
  const findRoleGroup = (a) => a.name?.name === 'role' && a.value.value === 'group'
65
65
  const findAsSectioning = (a) => a.name?.name.match(AS_REGEX) && a.value.value.match(BARE_SECTIONING_TAG_REGEX)
66
+ const findTitle = (i) => i.key.name === 'title'
66
67
 
67
68
  const SCHEMA = [
68
69
  {
@@ -121,6 +122,12 @@ module.exports = {
121
122
  case 'title':
122
123
  isPseudoLabel = true
123
124
  break
125
+ case 'inputAttributes': {
126
+ if (!isPseudoLabel && i.value.expression.type === 'ObjectExpression' && i.value.expression.properties.some(findTitle)) {
127
+ isPseudoLabel = true
128
+ }
129
+ break
130
+ }
124
131
  case 'type':
125
132
  switch (i.value.value) {
126
133
  case 'radio':
@@ -0,0 +1,82 @@
1
+ # smarthr/a11y-numbered-text-within-ol
2
+
3
+ - "1. hoge", "2. fuga" ... のように連番のテキストをもつコンポーネントはol要素でマークアップすることを促すルールです
4
+ - ol要素でマークアップすることで連番テキストをもつ要素同士の関係、順番に意味があることを適切に示すことが出来ます
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/a11y-numbered-text-within-ol': 'error', // 'warn', 'off',
12
+ },
13
+ }
14
+ ```
15
+
16
+ ## ❌ Incorrect
17
+
18
+ ```jsx
19
+ // ol要素で囲まれていないためNG
20
+ <Any>1. hoge</Any>
21
+ <Any>2. fuga</Any>
22
+
23
+ // 属性でも同様にチェックする
24
+ <Any title="1. hoge" />
25
+ <Any title="2. fuga" />
26
+
27
+ // ol要素内で連番を設定しているとNG
28
+ <OrderedList>
29
+ <li>1. hoge</li>
30
+ <li>2. fuga</li>
31
+ </OrderedList>
32
+
33
+ // 同一のol要素で囲まれていないためNG
34
+ <OrderedList>
35
+ <li>hoge</li>
36
+ </OrderedList>
37
+ <OrderedList>
38
+ <li>fuga</li>
39
+ </OrderedList>>
40
+
41
+ ```
42
+
43
+ ## ✅ Correct
44
+
45
+ ```jsx
46
+ <ol>
47
+ <li>hoge</li>
48
+ <li>fuga</li>
49
+ </ol>
50
+
51
+ <OrderedList>
52
+ <Any title="hoge" />
53
+ <Any title="fuga" />
54
+ </OrderedList>
55
+
56
+ // デフォルトの連番からフォーマット、スタイルを変更したい場合
57
+ // counter-reset + counter-increment で表現する
58
+ // 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
59
+ <OrderedList>
60
+ <li>
61
+ <NumberedHeading>hoge</NumberedHeading>
62
+ <Any />
63
+ </li>
64
+ <li>
65
+ <NumberedHeading>fuga</NumberedHeading>
66
+ <Any />
67
+ </li>
68
+ </OrderedList>
69
+
70
+ ...
71
+
72
+ const OrderedList = styled.ol`
73
+ list-style: none; // デフォルトのstyleを消す
74
+ counter-reset: hoge; // カウンターの名称。わかりやすいものなら何でもOK
75
+ `
76
+ const NumberedHeading = styled(Heading)`
77
+ &::before {
78
+ counter-increment: hoge; // hogeカウンターを+1する
79
+ content: "No " counter(hoge) ": "; // 表示される連番のフォーマット
80
+ }
81
+ `
82
+ ```
@@ -0,0 +1,141 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
+
3
+ const EXPECTED_NAMES = {
4
+ '(Ordered(.*)List|^ol)$': '(Ordered(.*)List)$',
5
+ '(S|s)elect$': '(Select)$',
6
+ }
7
+ const UNEXPECTED_NAMES = EXPECTED_NAMES
8
+
9
+ const NUMBERED_TEXT_REGEX = /^[\s\n]*(([0-9])([^0-9]{2})[^\s\n]*)/
10
+ const ORDERED_LIST_REGEX = /(Ordered(.*)List|^ol)$/
11
+ const SELECT_REGEX = /(S|s)elect$/
12
+ const IGNORE_ATTRIBUTE_REGEX = /((w|W)idth|(h|H)eight)$/
13
+ const AS_ATTRIBUTE_REGEX = /^(as|forwardedAs)$/
14
+
15
+ const findAsOlAttr = (a) => a.type === 'JSXAttribute' && AS_ATTRIBUTE_REGEX.test(a.name?.name) && a.value?.value === 'ol'
16
+
17
+ const searchOrderedList = (node) => {
18
+ if (node.type === 'JSXElement' && node.openingElement.name?.name) {
19
+ const name = node.openingElement.name.name
20
+
21
+ if (name.match(SELECT_REGEX)) {
22
+ // HINT: select要素の場合、optionのラベルに連番がついている場合がありえるのでignoreする
23
+ // 通常と処理を分けるためnullではなく0を返す
24
+ return 0
25
+ } else if (
26
+ name.match(ORDERED_LIST_REGEX) ||
27
+ node.openingElement.attributes.find(findAsOlAttr)
28
+ ) {
29
+ return node.openingElement
30
+ }
31
+ }
32
+
33
+ if (node.type === 'Program') {
34
+ return null
35
+ }
36
+
37
+ return searchOrderedList(node.parent)
38
+ }
39
+
40
+ const checkNumberedTextInOl = (result, node, context) => {
41
+ if (result) {
42
+ context.report({
43
+ node,
44
+ message: `${result.name.name} 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
45
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
46
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)`,
47
+ })
48
+ }
49
+ }
50
+
51
+ const renderTag = (node) => `\`${node.name.name}="${node.value.value}"\``
52
+ const renderNode = (node, matcher) => node.type === 'JSXText' ? `\`${matcher[1]}\`` : renderTag(node)
53
+
54
+ const SCHEMA = []
55
+
56
+ module.exports = {
57
+ meta: {
58
+ type: 'problem',
59
+ schema: SCHEMA,
60
+ },
61
+ create(context) {
62
+ let firstNumber = 0
63
+ let firstNumberedNode = null
64
+ let firstNumberedMatcher = null
65
+
66
+ function checker(node, matcher) {
67
+ if (matcher) {
68
+ const result = searchOrderedList(node)
69
+
70
+ if (result !== 0) {
71
+ checkNumberedTextInOl(result, node, context)
72
+
73
+ const nowNumber = matcher[2] * 1
74
+
75
+ if (firstNumberedNode && nowNumber !== firstNumber) {
76
+ if (nowNumber === firstNumber + 1) {
77
+ const resultFirst = searchOrderedList(firstNumberedNode)
78
+
79
+ if (!resultFirst) {
80
+ if (!result) {
81
+ context.report({
82
+ node: firstNumberedNode,
83
+ message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
84
+ - ${renderNode(firstNumberedNode, firstNumberedMatcher)} と ${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
85
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
86
+ })
87
+ } else {
88
+ context.report({
89
+ node: firstNumberedNode,
90
+ message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
91
+ - ${renderNode(firstNumberedNode, firstNumberedMatcher)} が ${renderNode(node, matcher)} を囲んでいるol要素内(<${result.name.name}>)に存在するように修正してください
92
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${result.name.name}>)に存在する必要があります`,
93
+ })
94
+ }
95
+ } else {
96
+ if (!result) {
97
+ context.report({
98
+ node,
99
+ message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
100
+ - ${renderNode(node, matcher)} が ${renderNode(firstNumberedNode, firstNumberedMatcher)} を囲んでいるol要素内(<${resultFirst.name.name}>)に存在するように修正してください
101
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${resultFirst.name.name}>)に存在する必要があります`,
102
+ })
103
+
104
+ firstNumberedNode = null
105
+ } else if (resultFirst !== result) {
106
+ context.report({
107
+ node,
108
+ message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
109
+ - ${renderNode(firstNumberedNode, firstNumberedMatcher)} と ${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
110
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
111
+ })
112
+ }
113
+ }
114
+ }
115
+
116
+ firstNumber = nowNumber
117
+ firstNumberedNode = node
118
+ firstNumberedMatcher = matcher
119
+ } else if (!firstNumberedNode || nowNumber === firstNumber) {
120
+ firstNumber = nowNumber
121
+ firstNumberedNode = node
122
+ firstNumberedMatcher = matcher
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ return {
129
+ ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
130
+ JSXAttribute: (node) => {
131
+ if (node.value?.value && !IGNORE_ATTRIBUTE_REGEX.test(node.name?.name)) {
132
+ checker(node, node.value.value.match(NUMBERED_TEXT_REGEX))
133
+ }
134
+ },
135
+ JSXText: (node) => {
136
+ checker(node, node.value.match(NUMBERED_TEXT_REGEX))
137
+ },
138
+ }
139
+ },
140
+ }
141
+ module.exports.schema = SCHEMA
@@ -0,0 +1,35 @@
1
+ # smarthr/best-practice-for-button-element
2
+
3
+ - button要素の利用を禁止し、smarthr/Button、もしくはsmarthr/UnstyledButtonの利用を促すルールです
4
+ - button要素のtype属性のデフォルトは 'submit' であり、これはform要素にbuttonを設置すると、clickでformをsubmitする状態になります
5
+ - 上記挙動は開発者が意図しづらいため、smarthr/Button, smarthr/UnstyledButtonはtype属性のデフォルトを 'button' に変更しています
6
+
7
+ ## rules
8
+
9
+ ```js
10
+ {
11
+ rules: {
12
+ 'smarthr/best-practice-for-button-element': 'error', // 'warn', 'off'
13
+ },
14
+ }
15
+ ```
16
+
17
+ ## ❌ Incorrect
18
+
19
+ ```js
20
+ <button>click</button>
21
+
22
+ const AnyButton = styled.button``
23
+ ```
24
+
25
+ ## ✅ Correct
26
+
27
+
28
+ ```js
29
+ <button type="button">click</button>
30
+ <button type="submit">click</button>
31
+ <Button>click</Button>
32
+ <AnyButton>click</AnyButton>
33
+
34
+ const AnyButton = styled(Button)``
35
+ ```
@@ -0,0 +1,43 @@
1
+ const { checkImportStyledComponents, getStyledComponentBaseName } = require('../../libs/format_styled_components')
2
+
3
+ const ERRORMESSAGE_SUFFIX = `
4
+ - button要素のtype属性のデフォルトは "submit" のため、button要素がformでラップされていると意図しないsubmitを引き起こす可能性があります
5
+ - smarthr-ui/Button, smarthr-ui/UnstyledButtonのtype属性のデフォルトは "button" になっているため、buttonから置き換えることをおすすめします`
6
+ const ERRORMESSAGE_REQUIRED_TYPE_ATTR = `button要素を利用する場合、type属性に "button" もしくは "submit" を指定してください${ERRORMESSAGE_SUFFIX}`
7
+ const ERRORMESSAGE_PROHIBIT_STYLED = `"styled.button" の直接利用をやめ、smarthr-ui/Button、もしくはsmarthr-ui/UnstyledButtonを利用してください${ERRORMESSAGE_SUFFIX}`
8
+
9
+ const findTypeAttr = (a) => a.type === 'JSXAttribute' && a.name.name === 'type'
10
+
11
+ const SCHEMA = []
12
+
13
+ module.exports = {
14
+ meta: {
15
+ type: 'problem',
16
+ fixable: 'code',
17
+ schema: SCHEMA,
18
+ },
19
+ create(context) {
20
+ return {
21
+ ImportDeclaration: (node) => {
22
+ checkImportStyledComponents(node, context)
23
+ },
24
+ JSXOpeningElement: (node) => {
25
+ if (node.name.name === 'button' && !node.attributes.find(findTypeAttr)) {
26
+ context.report({
27
+ node,
28
+ message: ERRORMESSAGE_REQUIRED_TYPE_ATTR,
29
+ });
30
+ }
31
+ },
32
+ VariableDeclarator: (node) => {
33
+ if (getStyledComponentBaseName(node) === 'button') {
34
+ context.report({
35
+ node,
36
+ message: ERRORMESSAGE_PROHIBIT_STYLED,
37
+ });
38
+ }
39
+ },
40
+ }
41
+ },
42
+ }
43
+ module.exports.schema = SCHEMA
@@ -92,12 +92,6 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
92
92
  {
93
93
  code: `<a><img src="hoge.jpg" alt="ほげ" /></a>`,
94
94
  },
95
- {
96
- code: `<a><Icon visuallyHiddenText="ほげ" /></a>`,
97
- },
98
- {
99
- code: `<a><AnyComponent><Icon visuallyHiddenText="ほげ" /></AnyComponent></a>`,
100
- },
101
95
  {
102
96
  code: `<a>{any}</a>`,
103
97
  },
@@ -194,10 +188,6 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
194
188
  code: `<a><img src="hoge.jpg" alt="" /></a>`,
195
189
  errors: [{ message: defaultErrorMessage }]
196
190
  },
197
- {
198
- code: `<a><AnyComponent><Icon visuallyHiddenText="" /></AnyComponent></a>`,
199
- errors: [{ message: defaultErrorMessage }]
200
- },
201
191
  {
202
192
  code: `<button><img src="hoge.jpg" /></button>`,
203
193
  errors: [{ message: defaultErrorMessage }]
@@ -214,10 +204,6 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
214
204
  code: `<button><img src="hoge.jpg" alt="" /></button>`,
215
205
  errors: [{ message: defaultErrorMessage }]
216
206
  },
217
- {
218
- code: `<button><AnyComponent><Icon visuallyHiddenText="" /></AnyComponent></button>`,
219
- errors: [{ message: defaultErrorMessage }]
220
- },
221
207
  {
222
208
  code: `<button><SmartHRLogoSuffix /></button>`,
223
209
  errors: [{ message: defaultErrorMessage }]
@@ -129,6 +129,8 @@ ruleTester.run('a11y-input-in-form-control', rule, {
129
129
  { code: '<HogeFieldset><HogeCheckBox /><HogeInput title="any" /></HogeFieldset>' },
130
130
  { code: '<FugaSection><HogeInput title="any" /></FugaSection>' },
131
131
  { code: '<HogeTextarea title="any" />' },
132
+ { code: '<HogeComboBox inputAttributes={{ title: "any" }} />' },
133
+ { code: '<HogeComboBox inputAttributes={{ title }} />' },
132
134
  { code: '<Fieldset><HogeRadioButtons /></Fieldset>' },
133
135
  { code: '<Fieldset><HogeRadioButtonPanels /></Fieldset>' },
134
136
  { code: '<Fieldset><HogeCheckBoxs /></Fieldset>' },
@@ -154,6 +156,7 @@ ruleTester.run('a11y-input-in-form-control', rule, {
154
156
  { code: '<HogeSelect />', errors: [ { message: noLabeledSelect('HogeSelect') } ] },
155
157
  { code: '<HogeInputFile />', errors: [ { message: noLabeledInput('HogeInputFile') } ] },
156
158
  { code: '<HogeComboBox />', errors: [ { message: noLabeledInput('HogeComboBox') } ] },
159
+ { code: '<HogeComboBox inputAttributes={{ any }} />', errors: [ { message: noLabeledInput('HogeComboBox') } ] },
157
160
  { code: '<HogeDatePicker />', errors: [ { message: noLabeledInput('HogeDatePicker') } ] },
158
161
  { code: '<HogeFormControl><Input type="checkbox" /><Input type="checkbox" /></HogeFormControl>', errors: [ { message: invalidPureCheckboxInFormControl('Input') } ] },
159
162
  { code: '<HogeFormControl><HogeCheckBox /><Input /></HogeFormControl>', errors: [ { message: invalidMultiInputsInFormControl() } ] },
@@ -0,0 +1,114 @@
1
+ const rule = require('../rules/a11y-numbered-text-within-ol')
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-numbered-text-within-ol', rule, {
16
+ valid: [
17
+ { code: `const HogeOrderedFugaList = styled.ol` },
18
+ { code: `const HogeOrderedFugaList = styled(HogeOrderedList)` },
19
+ { code: `<p>1: hoge</p>` },
20
+ { code: `<p title="2. hoge" />` },
21
+ { code: `<><p>2: hoge</p><p>1: hoge</p></>` },
22
+ { code: `<ol><li>abc</li><li>def</li></ol>` },
23
+ { code: `<OrderedList><li>abc</li><li>def</li></OrderedList>` },
24
+ { code: `<Hoge as="ol"><li>abc</li><li>def</li></Hoge>` },
25
+ { code: `<Hoge forwardedAs="ol"><li>abc</li><li>def</li></Hoge>` },
26
+ { code: `<Select><optgroup><option value="hoge">1. hoge</option><option value="fuga">2. fuga</option></optgroup></Select>` },
27
+ { code: `<><Hoge width="100%" /><Hoge height="200%" /><Hoge maxWidth="30px" /></>` },
28
+ ],
29
+ invalid: [
30
+ { code: `<><p>1: hoge</p><p>2: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
31
+ - \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
32
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
33
+ { code: `
34
+ <>
35
+ <div>
36
+ 1. abc
37
+ </div>
38
+ <p>
39
+ 2. def
40
+ </p>
41
+ </>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
42
+ - \`1. abc\` と \`2. def\` が同じol要素内に存在するように修正してください
43
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
44
+ { code: `<><p>2: hoge</p><p>1: hoge</p><p>2: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
45
+ - \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
46
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
47
+ { code: `<><p>1: hoge</p><ol><li>2: hoge</li></ol></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
48
+ - \`1: hoge\` が \`2: hoge\` を囲んでいるol要素内(<ol>)に存在するように修正してください
49
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
50
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
51
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` } ] },
52
+ { code: `<><p>1: hoge</p><p title="2. hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
53
+ - \`1: hoge\` と \`title="2. hoge"\` が同じol要素内に存在するように修正してください
54
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
55
+ { code: `<><ol><li>1: hoge</li></ol><p>2: hoge</p></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
56
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
57
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
58
+ - \`2: hoge\` が \`1: hoge\` を囲んでいるol要素内(<ol>)に存在するように修正してください
59
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` } ] },
60
+ { code: `<><ol><li>1: hoge</li></ol><ol><li>2: hoge</li></ol></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
61
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
62
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
63
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
64
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
65
+ - \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
66
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
67
+ { code: `<><Hoge any="1: hoge" /><Hoge any="2: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
68
+ - \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
69
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
70
+ { code: `<><Hoge any="2: hoge" /><Hoge any="1: hoge" /><Hoge any="2: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
71
+ - \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
72
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
73
+ { code: `<><Hoge any="1: hoge" /><ol><li><Hoge any="2: hoge" /></li></ol></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
74
+ - \`any="1: hoge"\` が \`any="2: hoge"\` を囲んでいるol要素内(<ol>)に存在するように修正してください
75
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
76
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
77
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` } ] },
78
+ { code: `<><ol><li><Hoge any="1: hoge" /></li></ol><Hoge any="2: hoge" /></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
79
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
80
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
81
+ - \`any="2: hoge"\` が \`any="1: hoge"\` を囲んでいるol要素内(<ol>)に存在するように修正してください
82
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` } ] },
83
+ { code: `<><ol><li><Hoge any="1: hoge" /></li></ol><ol><li><Hoge any="2: hoge" /></li></ol></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
84
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
85
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
86
+ - ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
87
+ - 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
88
+ - \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
89
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
90
+ { code: `<><Hoge any="1: hoge" /><Hoge any="2: hoge" /><Hoge any="3: hoge" /><Hoge any="4: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
91
+ - \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
92
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
93
+ - \`any="2: hoge"\` と \`any="3: hoge"\` が同じol要素内に存在するように修正してください
94
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
95
+ - \`any="3: hoge"\` と \`any="4: hoge"\` が同じol要素内に存在するように修正してください
96
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
97
+ { code: `<><Hoge any="1: hoge" /><Hoge any="3: hoge" /><Hoge any="4: hoge" /><Hoge any="6: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
98
+ - \`any="3: hoge"\` と \`any="4: hoge"\` が同じol要素内に存在するように修正してください
99
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
100
+ { code: `<><p>1: hoge</p><p>2: hoge</p><p>3: hoge</p><p>4: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
101
+ - \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
102
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
103
+ - \`2: hoge\` と \`3: hoge\` が同じol要素内に存在するように修正してください
104
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
105
+ - \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
106
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
107
+ { code: `<><p>1: hoge</p><p>3: hoge</p><p>4: hoge</p><p>6: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
108
+ - \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
109
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
110
+ { code: `<><p>3: hoge</p><select><option value="hoge">1: hoge</option></select><p>4: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
111
+ - \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
112
+ - 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
113
+ ]
114
+ })
@@ -0,0 +1,38 @@
1
+ const rule = require('../rules/best-practice-for-button-element')
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
+ const ERRORMESSAGE_REQUIRED_TYPE_ATTR = `button要素を利用する場合、type属性に "button" もしくは "submit" を指定してください
16
+ - button要素のtype属性のデフォルトは "submit" のため、button要素がformでラップされていると意図しないsubmitを引き起こす可能性があります
17
+ - smarthr-ui/Button, smarthr-ui/UnstyledButtonのtype属性のデフォルトは "button" になっているため、buttonから置き換えることをおすすめします`
18
+ const ERRORMESSAGE_PROHIBIT_STYLED = `"styled.button" の直接利用をやめ、smarthr-ui/Button、もしくはsmarthr-ui/UnstyledButtonを利用してください
19
+ - button要素のtype属性のデフォルトは "submit" のため、button要素がformでラップされていると意図しないsubmitを引き起こす可能性があります
20
+ - smarthr-ui/Button, smarthr-ui/UnstyledButtonのtype属性のデフォルトは "button" になっているため、buttonから置き換えることをおすすめします`
21
+
22
+ ruleTester.run('best-practice-for-button-element', rule, {
23
+ valid: [
24
+ { code: `import styled from 'styled-components'` },
25
+ { code: `import styled, { css } from 'styled-components'` },
26
+ { code: `<Button />` },
27
+ { code: `<Button>ほげ</Button>` },
28
+ { code: `<AnyButton>ほげ</AnyButton>` },
29
+ { code: `<button type="button">any</button>` },
30
+ { code: `<button type="submit">any</button>` },
31
+ { code: 'const HogeButton = styled(HogeButton)``' },
32
+ ],
33
+ invalid: [
34
+ { code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
35
+ { code: `<button>ほげ</button>`, errors: [ { message: ERRORMESSAGE_REQUIRED_TYPE_ATTR } ] },
36
+ { code: 'const HogeButton = styled.button``', errors: [ { message: ERRORMESSAGE_PROHIBIT_STYLED } ] },
37
+ ]
38
+ })