eslint-plugin-smarthr 0.5.13 → 0.5.15

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,31 @@
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.5.15](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.14...v0.5.15) (2024-09-10)
6
+
7
+
8
+ ### Features
9
+
10
+ * a11y系ルールの対象にsmarthr-ui/TimePickerを追加する ([#146](https://github.com/kufu/eslint-plugin-smarthr/issues/146)) ([90d9bf4](https://github.com/kufu/eslint-plugin-smarthr/commit/90d9bf42d770026e7ebc9442096e677fab298841))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * README.md内のIncorrect examplesを修正 ([fe0f7d2](https://github.com/kufu/eslint-plugin-smarthr/commit/fe0f7d2b8bf9773a8ff7e962084093bc6441bcda))
16
+
17
+ ### [0.5.14](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.11...v0.5.14) (2024-07-02)
18
+
19
+
20
+ ### Features
21
+
22
+ * best-practice-for-data-test-attributeを追加 ([#141](https://github.com/kufu/eslint-plugin-smarthr/issues/141)) ([30a8f00](https://github.com/kufu/eslint-plugin-smarthr/commit/30a8f003e1cdb79c709390be0322703e74de284d))
23
+ * ButtonやTextLinkコンポーネントにprefix, suffixの両属性を同時に設定できないルールを追加 ([1eb7568](https://github.com/kufu/eslint-plugin-smarthr/commit/1eb75680a1125391106994532b58cc0591853711))
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * a11y-clickable-element-has-text でtext属性を持つコンポーネントが存在する場合、真となるように修正 ([#143](https://github.com/kufu/eslint-plugin-smarthr/issues/143)) ([46b7048](https://github.com/kufu/eslint-plugin-smarthr/commit/46b7048dc3bb5a29455e205fabdd584fa478d2f1))
29
+
5
30
  ### [0.5.13](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.12...v0.5.13) (2024-06-21)
6
31
 
7
32
 
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - [best-practice-for-date](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-date)
20
20
  - [best-practice-for-layouts](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-layouts)
21
21
  - [best-practice-for-remote-trigger-dialog](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-remote-trigger-dialog)
22
+ - [design-system-guideline-prohibit-double-icons](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/design-system-guideline-prohibit-double-icons)
22
23
  - [format-import-path](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/format-import-path)
23
24
  - [format-translate-component](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/format-translate-component)
24
25
  - [jsx-start-with-spread-attributes](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/jsx-start-with-spread-attributes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -32,8 +32,6 @@ const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
32
32
  const REGEX_TEXT_COMPONENT = /(Text|Message)$/
33
33
  const REGEX_JSX_TYPE = /^(JSXText|JSXExpressionContainer)$/
34
34
 
35
- const HIT_TEXT_ATTR = 'alt'
36
-
37
35
  const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
38
36
  const checkFalsyJSXText = (c) => (
39
37
  !(c.type === 'JSXText' && c.value.match(REGEX_NLSP))
@@ -91,30 +89,29 @@ module.exports = {
91
89
  return true
92
90
  }
93
91
 
94
- // HINT: role & aria-label を同時に設定されている場合は許可
92
+ // HINT: role & aria-label を同時に設定されている場合か、text属性が設定されている場合許可
95
93
  let existRole = false
96
94
  let existAriaLabel = false
97
95
  const result = c.openingElement.attributes.reduce((prev, a) => {
98
- existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
99
- existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
100
-
101
- if (
102
- prev ||
103
- HIT_TEXT_ATTR !== a.name.name
104
- ) {
105
- return prev
96
+ const n = a.name.name
97
+
98
+ if (prev || n === 'text') {
99
+ return true
100
+ }
101
+
102
+ const v = a.value?.value
103
+
104
+ existRole = existRole || (n === 'role' && v === 'img')
105
+ existAriaLabel = existAriaLabel || n === 'aria-label'
106
+
107
+ if (existRole && existAriaLabel) {
108
+ return true
106
109
  }
107
110
 
108
- return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
111
+ return n === 'alt' && (v || a.value.type === 'JSXExpressionContainer') || false
109
112
  }, null)
110
113
 
111
- if (
112
- result ||
113
- (existRole && existAriaLabel) ||
114
- (c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
115
- ) {
116
- return true
117
- }
114
+ return result || (c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
118
115
  }
119
116
  }
120
117
 
@@ -9,6 +9,7 @@ const EXPECTED_NAMES = {
9
9
  'Check(b|B)ox$': 'CheckBox$',
10
10
  'Combo(b|B)ox$': 'ComboBox$',
11
11
  'DatePicker$': 'DatePicker$',
12
+ 'TimePicker$': 'TimePicker$',
12
13
  'DropZone$': 'DropZone$',
13
14
  'Switch$': 'Switch$',
14
15
  'SegmentedControl$': 'SegmentedControl$',
@@ -10,6 +10,7 @@ const EXPECTED_NAMES = {
10
10
  'Check(b|B)ox$': 'CheckBox$',
11
11
  'Combo(b|B)ox$': 'ComboBox$',
12
12
  'DatePicker$': 'DatePicker$',
13
+ 'TimePicker$': 'TimePicker$',
13
14
  'DropZone$': 'DropZone$',
14
15
  }
15
16
  const TARGET_TAG_NAME_REGEX = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
@@ -16,6 +16,7 @@ const EXPECTED_INPUT_NAMES = {
16
16
  'InputFile$': '(InputFile)$',
17
17
  'Combo(b|B)ox$': '(ComboBox)$',
18
18
  'DatePicker$': '(DatePicker)$',
19
+ 'TimePicker$': '(TimePicker)$',
19
20
  ...EXPECTED_LABELED_INPUT_NAMES,
20
21
  }
21
22
 
@@ -7,7 +7,9 @@ const EXPECTED_NAMES = {
7
7
  'FieldSet$': 'FieldSet$',
8
8
  'ComboBox$': 'ComboBox$',
9
9
  'DatePicker$': 'DatePicker$',
10
+ 'TimePicker$': 'TimePicker$',
10
11
  }
12
+ const INPUT_TAG_REGEX = /((i|I)nput|(t|T)extarea|FieldSet|ComboBox|(Date|Time)Picker)$/
11
13
 
12
14
  /**
13
15
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -27,7 +29,7 @@ module.exports = {
27
29
  return
28
30
  }
29
31
 
30
- if (!name.match(/((i|I)nput|(t|T)extarea|FieldSet|ComboBox|DatePicker)$/)) {
32
+ if (!name.match(INPUT_TAG_REGEX)) {
31
33
  return
32
34
  }
33
35
 
@@ -21,15 +21,11 @@
21
21
  ## ❌ Incorrect
22
22
 
23
23
  ```jsx
24
- <Button>hoge</Button>
25
- <Button suffix={SUFFIX}>hoge</Button>
26
- <Button prefix="PREFIX">hoge</Button>
27
- <TextLink>hoge</TextLink>
28
- <TextLink suffix="SUFFIX">hoge</TextLink>
29
- <TextLink prefix={PREFIX}>hoge</TextLink>
30
- <StyledButton>hoge</StyledButton>
31
- <StyledLink>hoge</StyledLink>
32
- <Input prefix={PREFIX} suffix={SUFFIX} />
24
+ <Button suffix={SUFFIX} prefix={PREFIX}>hoge</Button>
25
+ <Button suffix prefix>hoge</Button>
26
+ <StyledButton suffix={undefined} prefix={null}>hoge</StyledButton>
27
+ <Link prefix="PREFIX" suffix="SUFFIX">hoge</Link>
28
+ <StyledLink prefix="PREFIX" suffix="SUFFIX">hoge</StyledLink>
33
29
  ```
34
30
 
35
31
  ## ✅ Correct
@@ -119,6 +119,9 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
119
119
  {
120
120
  code: `<a><FormattedMessage /></a>`,
121
121
  },
122
+ {
123
+ code: `<button><Hoge text="any" /></button>`,
124
+ },
122
125
  {
123
126
  code: `<a><AnyComponent /></a>`,
124
127
  options: [{
@@ -12,7 +12,7 @@ const ruleTester = new RuleTester({
12
12
  },
13
13
  });
14
14
 
15
- const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|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$)/'
15
+ const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|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$)/'
16
16
  const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
17
17
  const onAttrsText = onAttrs.join(', ')
18
18
 
@@ -67,7 +67,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
67
67
  { code: '<div onClick={any} onSubmit={any2} role="presentation"><Hoge /></div>', errors: [ { message: messageRolePresentationNotHasInteractive('div', ['onClick', 'onSubmit']) } ] },
68
68
  { code: '<div onClick={any}><Link /></div>', errors: [ { message: messageNonInteractiveEventHandler('div', ['onClick']) } ] },
69
69
  { code: '<Wrapper onClick={any}><Link /></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
70
- { 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$|DatePicker$|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$|^Hoge$)/') } ] },
70
+ { 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$|DatePicker$|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$)/') } ] },
71
71
  { code: '<Wrapper onClick={any} onChange={anyany}><any><Link /></any></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick', 'onChange']) } ] },
72
72
  { code: '<Wrapper onClick={any}>{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
73
73
  ],
@@ -15,13 +15,13 @@ const ruleTester = new RuleTester({
15
15
  const noLabeledInput = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
16
16
  - FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
17
17
  - ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
18
- - ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
18
+ - ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
19
19
  - 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を入力すれば良いのか" の説明を設定してください
20
20
  - 例: <${name} title="姓を全角カタカナのみで入力してください" />`
21
21
  const noLabeledSelect = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
22
22
  - FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
23
23
  - ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
24
- - ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
24
+ - ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
25
25
  - 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を選択すれば良いのか" の説明を設定してください
26
26
  - 例: <${name} title="検索対象を選択してください" />`
27
27
  const invalidPureCheckboxInFormControl = (name) => `HogeFormControl が ${name} を含んでいます。smarthr-ui/FormControl を smarthr-ui/Fieldset に変更し、正しくグルーピングされるように修正してください。
@@ -76,7 +76,7 @@ const invalidChildreninFormControl = (children) => `FormControl が、${children
76
76
  - FormControlではなく、smarthr-ui/Fieldset、もしくはsmarthr-ui/Section + smarthr-ui/Heading などでのマークアップを検討してください
77
77
  - 方法2: 親要素であるFormControlがsmarthr-ui/FormControlを拡張したコンポーネントではない場合、コンポーネント名を/(Form(Control|Group))$/と一致しない名称に変更してください`
78
78
  const requireMultiInputInFormControlWithRoleGroup = () => `HogeFormControl内に入力要素が2個以上存在しないため、'role=\"group\"'を削除してください。'role=\"group\"'は複数の入力要素を一つのグループとして扱うための属性です。
79
- - HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
79
+ - HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
80
80
 
81
81
  ruleTester.run('a11y-input-in-form-control', rule, {
82
82
  valid: [
@@ -41,6 +41,7 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
41
41
  { code: `<CustomComboBox />` },
42
42
  { code: `<SearchInput />` },
43
43
  { code: `<DatePicker />` },
44
+ { code: `<TimePicker />` },
44
45
  { code: `<CustomSearchInput tooltipMessage="hoge" />` },
45
46
  { code: `<CustomSearchInput tooltipMessage="hoge" placeholder="fuga" />` },
46
47
  { code: `<ComboBox defaultItem={items[0]} />` },