eslint-plugin-smarthr 0.5.12 → 0.5.14

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.5.14](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.11...v0.5.14) (2024-07-02)
6
+
7
+
8
+ ### Features
9
+
10
+ * 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))
11
+ * ButtonやTextLinkコンポーネントにprefix, suffixの両属性を同時に設定できないルールを追加 ([1eb7568](https://github.com/kufu/eslint-plugin-smarthr/commit/1eb75680a1125391106994532b58cc0591853711))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * 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))
17
+
18
+ ### [0.5.13](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.12...v0.5.13) (2024-06-21)
19
+
20
+
21
+ ### Features
22
+
23
+ * ButtonやTextLinkコンポーネントにprefix, suffixの両属性を同時に設定できないルールを追加 ([1eb7568](https://github.com/kufu/eslint-plugin-smarthr/commit/1eb75680a1125391106994532b58cc0591853711))
24
+
5
25
  ### [0.5.12](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.11...v0.5.12) (2024-06-10)
6
26
 
7
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
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
 
@@ -0,0 +1,47 @@
1
+ # smarthr/design-system-guideline-prohibit-double-icons
2
+
3
+ - 要素の前後両方にアイコンの使用を禁止するルールです
4
+ - `Button` や `TextLink` において、`prefix` と `suffix` が同時に設定されている場合、エラーとなります。
5
+ - 基本的にアイコンのみが設定される前提のルールになっていますが、文字列などが設定されている場合もエラーとなります。
6
+ - どちらにもアイコンをつけられそうな場合は、アイコン付き(右)(サフィックス)を優先し、アイコン付き(左)(プレフィックス)には指定しないでください。
7
+
8
+ ## rules
9
+
10
+ ```js
11
+ {
12
+ rules: {
13
+ 'smarthr/design-system-guideline-prohibit-double-icons': [
14
+ 'error', // 'warn', 'off'
15
+ // { checkType: 'always' } /* 'always' || 'allow-spread-attributes' */
16
+ ]
17
+ },
18
+ }
19
+ ```
20
+
21
+ ## ❌ Incorrect
22
+
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} />
33
+ ```
34
+
35
+ ## ✅ Correct
36
+
37
+ ```jsx
38
+ <Button>hoge</Button>
39
+ <Button suffix={SUFFIX}>hoge</Button>
40
+ <Button prefix="PREFIX">hoge</Button>
41
+ <TextLink>hoge</TextLink>
42
+ <TextLink suffix="SUFFIX">hoge</TextLink>
43
+ <TextLink prefix={PREFIX}>hoge</TextLink>
44
+ <StyledButton>hoge</StyledButton>
45
+ <StyledLink>hoge</StyledLink>
46
+ <Input prefix={PREFIX} suffix={SUFFIX} />
47
+ ```
@@ -0,0 +1,58 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
+
3
+ const EXPECTED_NAMES = {
4
+ '(Button)$': '(Button)$',
5
+ '(Link)$': '(Link)$',
6
+ }
7
+
8
+ const UNEXPECTED_NAMES = EXPECTED_NAMES
9
+
10
+ const SCHEMA = []
11
+
12
+ const REGEX_PATTERN = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
13
+
14
+ /**
15
+ * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
16
+ */
17
+ module.exports = {
18
+ meta: {
19
+ type: 'problem',
20
+ schema: SCHEMA,
21
+ },
22
+ create(context) {
23
+ return {
24
+ ...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
25
+ JSXOpeningElement: (node) => {
26
+ const nodeName = node.name.name
27
+
28
+ if (REGEX_PATTERN.test(nodeName)) {
29
+ let prefix = null
30
+ let suffix = null
31
+
32
+ for (const attr of node.attributes) {
33
+ switch (attr.name.name) {
34
+ case 'prefix':
35
+ prefix = attr
36
+ break
37
+ case 'suffix':
38
+ suffix = attr
39
+ break
40
+ }
41
+
42
+ if(prefix && suffix) {
43
+ context.report({
44
+ node,
45
+ message: `${nodeName} には prefix と suffix は同時に設定できません。
46
+ - prefix または suffix のみを設定してください。
47
+ - どちらにもアイコンをつけられそうな場合は、アイコン付き(右)(サフィックス)を優先し、アイコン付き(左)(プレフィックス)には指定しないでください。
48
+ - 両方設定したい場合は、'eslint-disable-next-line' 等を利用して、このルールを無効化してください。`,
49
+ })
50
+ break
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ },
57
+ }
58
+ module.exports.schema = SCHEMA
@@ -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$|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$|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
  ],
@@ -0,0 +1,39 @@
1
+ const rule = require('../rules/design-system-guideline-prohibit-double-icons')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ ecmaVersion: 12,
7
+ ecmaFeatures: {
8
+ experimentalObjectRestSpread: true,
9
+ jsx: true,
10
+ },
11
+ sourceType: 'module',
12
+ },
13
+ })
14
+
15
+ const generateErrorText = (name) => `${name} には prefix と suffix は同時に設定できません。
16
+ - prefix または suffix のみを設定してください。
17
+ - どちらにもアイコンをつけられそうな場合は、アイコン付き(右)(サフィックス)を優先し、アイコン付き(左)(プレフィックス)には指定しないでください。
18
+ - 両方設定したい場合は、'eslint-disable-next-line' 等を利用して、このルールを無効化してください。`
19
+
20
+ ruleTester.run('design-system-guideline-prohibit-double-icons', rule, {
21
+ valid: [
22
+ { code: `<Button>hoge</Button>` },
23
+ { code: `<Button suffix={SUFFIX}>hoge</Button>` },
24
+ { code: `<Button prefix="PREFIX">hoge</Button>` },
25
+ { code: `<TextLink>hoge</TextLink>` },
26
+ { code: `<TextLink suffix="SUFFIX">hoge</TextLink>` },
27
+ { code: `<TextLink prefix={PREFIX}>hoge</TextLink>` },
28
+ { code: `<StyledButton>hoge</StyledButton>` },
29
+ { code: `<StyledLink>hoge</StyledLink>` },
30
+ { code: `<Input prefix={PREFIX} suffix={SUFFIX} />` },
31
+ ],
32
+ invalid: [
33
+ { code: `<Button suffix={SUFFIX} prefix={PREFIX}>hoge</Button>`, errors: [{message: generateErrorText('Button')}]},
34
+ { code: `<Button suffix prefix>hoge</Button>`, errors: [{message: generateErrorText('Button')}]},
35
+ { code: `<StyledButton suffix={undefined} prefix={null}>hoge</StyledButton>`, errors: [{message: generateErrorText('StyledButton')}]},
36
+ { code: `<Link prefix="PREFIX" suffix="SUFFIX">hoge</Link>`, errors: [{message: generateErrorText('Link')}]},
37
+ { code: `<StyledLink prefix="PREFIX" suffix="SUFFIX">hoge</StyledLink>`, errors: [{message: generateErrorText('StyledLink')}]},
38
+ ]
39
+ })