eslint-plugin-smarthr 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
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
+ ## [1.8.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.7.0...eslint-plugin-smarthr-v1.8.0) (2025-06-09)
6
+
7
+
8
+ ### Features
9
+
10
+ * best-practice-for-layoutsで <any>{hoge}</any> など子要素が変数のみの場合を許容する ([#668](https://github.com/kufu/tamatebako/issues/668)) ([7d03ff1](https://github.com/kufu/tamatebako/commit/7d03ff150833f673b9c2afe8e86adfdaee150d72))
11
+ * format-translate-component rule で RangeSeparator を許容する ([#667](https://github.com/kufu/tamatebako/issues/667)) ([951bee2](https://github.com/kufu/tamatebako/commit/951bee2f376ddf48c5e9a0b13eb50b2daeeec7f9))
12
+
13
+ ## [1.7.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.6.0...eslint-plugin-smarthr-v1.7.0) (2025-06-02)
14
+
15
+
16
+ ### Features
17
+
18
+ * a11y-help-link-with-support-href を追加 ([#646](https://github.com/kufu/tamatebako/issues/646)) ([662ca33](https://github.com/kufu/tamatebako/commit/662ca33120acac3fb9acdc7331d187fba9be86ef))
19
+ * best-practice-for-nested-attributes-array-index ruleを追加 ([#648](https://github.com/kufu/tamatebako/issues/648)) ([8774d84](https://github.com/kufu/tamatebako/commit/8774d84f46fcaff9360aa76cb3773f644d6d5529))
20
+
5
21
  ## [1.6.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.5.1...eslint-plugin-smarthr-v1.6.0) (2025-05-20)
6
22
 
7
23
 
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  - [a11y-delegate-element-has-role-presentation](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-delegate-element-has-role-presentation)
6
6
  - [a11y-form-control-in-form](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-form-control-in-form)
7
7
  - [a11y-heading-in-sectioning-content](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content)
8
+ - [a11y-help-link-with-support-href](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-help-link-with-support-href)
8
9
  - [a11y-image-has-alt-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-image-has-alt-attribute)
9
10
  - [a11y-input-has-name-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-input-has-name-attribute)
10
11
  - [a11y-input-in-form-control](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-input-in-form-control)
@@ -16,13 +17,15 @@
16
17
  - [a11y-replace-unreadable-symbol](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-replace-unreadable-symbol)
17
18
  - [a11y-required-layout-as-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-required-layout-as-attribute)
18
19
  - [a11y-trigger-has-button](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-trigger-has-button)
20
+ - [best-practice-for-async-current-target](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-async-current-target)
19
21
  - [best-practice-for-button-element](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-button-element)
20
22
  - [best-practice-for-data-test-attribute](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-data-test-attribute)
21
23
  - [best-practice-for-date](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-date)
22
24
  - [best-practice-for-layouts](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts)
25
+ - [best-practice-for-nested-attributes-array-index](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index)
23
26
  - [best-practice-for-remote-trigger-dialog](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-remote-trigger-dialog)
24
- - [best-practice-for-tailwind-variants](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants)
25
27
  - [best-practice-for-tailwind-prohibit-root-margin](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-prohibit-root-margin)
28
+ - [best-practice-for-tailwind-variants](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants)
26
29
  - [design-system-guideline-prohibit-double-icons](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-double-icons)
27
30
  - [format-import-path](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-import-path)
28
31
  - [format-translate-component](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-translate-component)
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
7
7
  "main": "index.js",
8
8
  "engines": {
9
- "node": ">=22.15.1"
9
+ "node": ">=22.16.0"
10
10
  },
11
11
  "scripts": {
12
12
  "test": "jest"
@@ -26,7 +26,7 @@
26
26
  "json5": "^2.2.3"
27
27
  },
28
28
  "devDependencies": {
29
- "typescript-eslint": "^8.32.1"
29
+ "typescript-eslint": "^8.33.1"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "eslint": "^9"
@@ -37,5 +37,5 @@
37
37
  "eslintplugin",
38
38
  "smarthr"
39
39
  ],
40
- "gitHead": "92e589c6bfc8e311b4c873f7e4c65b29eed61304"
40
+ "gitHead": "baf2b489f2397ac050c0c699e186b8356a8775cb"
41
41
  }
@@ -0,0 +1,34 @@
1
+ # smarthr/a11y-help-link-with-support-href
2
+
3
+ - [ヘルプページ](https://support.smarthr.jp/) へのリンクはsmarthr-ui/HelpLinkを使うことを促すルールです
4
+ - ヘルプページへのリンクは、通常のテキストリンクと設定するべき属性が異なるため、専用のコンポーネントが用意されています
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/a11y-help-link-with-support-href': [
12
+ 'error', // 'warn', 'off'
13
+ ]
14
+ },
15
+ }
16
+ ```
17
+
18
+ ## ❌ Incorrect
19
+
20
+ ```jsx
21
+ <TextLink href="https://support.smarthr.jp/xxxxx">any</TextLink>
22
+ <a href={`//support.smarthr.jp/${hoge}`}>any</a>
23
+ <AnchorButton href={supportURL}>any</a>
24
+ <AnyAnchor href={path.support.xxxx.yyyy} />
25
+ ```
26
+
27
+ ## ✅ Correct
28
+
29
+ ```jsx
30
+ <HelpLink href="https://support.smarthr.jp/xxxxx">any</HelpLink>
31
+ <HelpLink href={`//support.smarthr.jp/${hoge}`}>any</HelpLink>
32
+ <HelpLink href={supportURL}>any</HelpLink>
33
+ <HogeHelpLink href={path.support.xxxx.yyyy} />
34
+ ```
@@ -0,0 +1,82 @@
1
+ const SCHEMA = []
2
+
3
+ const SUPPORT_URL_PREFIX_REGEX = /(\/|\.)support\./
4
+ const SUPPORT_IDENTIFIER_REGEX = /(S|(^|_)s)upport(.*)(H(ref|REF)|U(rl|URL))$/
5
+ const PATH_OBJ_REGEX = /^((p|P)ath|PATH)\./
6
+ const SUPPORT_PATH_MEMBER_REGEX = /\.support\./
7
+
8
+ const ANCHER_LIKE_REGEX = /(Anchor(Button)?|Link|^a)$/
9
+ const HELP_LINK_REGEX = /HelpLink$/
10
+
11
+ const checkSupportURL = (node, context) => {
12
+ switch (node.type) {
13
+ case 'Literal': {
14
+ return SUPPORT_URL_PREFIX_REGEX.test(node.value)
15
+ }
16
+ case 'JSXExpressionContainer': {
17
+ return checkSupportURLExpression(node.expression, context)
18
+ }
19
+ }
20
+
21
+ return false
22
+ }
23
+
24
+ const checkSupportURLExpression = (node, context) => {
25
+ switch (node.type) {
26
+ case 'Literal': {
27
+ return SUPPORT_URL_PREFIX_REGEX.test(node.value)
28
+ }
29
+ case 'Identifier': {
30
+ return SUPPORT_IDENTIFIER_REGEX.test(node.name)
31
+ }
32
+ case 'CallExpression':
33
+ case 'ChainExpression':
34
+ case 'MemberExpression': {
35
+ const fullName = context.sourceCode.getText(node)
36
+
37
+ return PATH_OBJ_REGEX.test(fullName) && SUPPORT_PATH_MEMBER_REGEX.test(fullName)
38
+ }
39
+ case 'TemplateLiteral': {
40
+ const someChecker = (e) => checkSupportURLExpression(e, context)
41
+
42
+ return node.expressions.some(someChecker) || node.quasis.some(someChecker)
43
+ }
44
+ case 'TemplateElement': {
45
+ return node.value.raw && SUPPORT_URL_PREFIX_REGEX.test(node.value.raw)
46
+ }
47
+ case 'TSAsExpression': {
48
+ return checkSupportURLExpression(node.expression, context)
49
+ }
50
+ case 'LogicalExpression': {
51
+ return checkSupportURLExpression(node.left, context) || checkSupportURLExpression(node.right, context)
52
+ }
53
+ case 'ConditionalExpression': {
54
+ return checkSupportURLExpression(node.alternate, context) || checkSupportURLExpression(node.consequent, context)
55
+ }
56
+ }
57
+
58
+ return false
59
+ }
60
+
61
+ /**
62
+ * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
63
+ */
64
+ module.exports = {
65
+ meta: {
66
+ type: 'problem',
67
+ schema: SCHEMA,
68
+ },
69
+ create(context) {
70
+ return {
71
+ JSXAttribute: (node) => {
72
+ if (node.name.name === 'href' && ANCHER_LIKE_REGEX.test(node.parent.name.name) && !HELP_LINK_REGEX.test(node.parent.name.name) && checkSupportURL(node.value, context)) {
73
+ context.report({
74
+ node,
75
+ message: `ヘルプページ用のリンクは smarthr-ui/HelpLink コンポーネントを利用してください`,
76
+ });
77
+ }
78
+ },
79
+ }
80
+ },
81
+ }
82
+ module.exports.schema = SCHEMA
@@ -24,6 +24,11 @@ const searchChildren = (node) => {
24
24
  }
25
25
 
26
26
  switch(node.type) {
27
+ case 'MemberExpression':
28
+ return searchChildren(node.object)
29
+ // {hoge} や {hoge.fuga} の場合、許容する
30
+ case 'Identifier':
31
+ return false
27
32
  case 'JSXExpressionContainer':
28
33
  case 'ChainExpression':
29
34
  return searchChildren(node.expression)
@@ -101,7 +106,6 @@ module.exports = {
101
106
  return
102
107
  }
103
108
 
104
-
105
109
  if (searchChildren(children[0])) {
106
110
  context.report({
107
111
  node,
@@ -0,0 +1,91 @@
1
+ # smarthr/best-practice-for-nested-attributes-array-index
2
+
3
+ - 入力要素のname属性で、配列に当たる部分の連番を指定しない場合(例: a[xxx][][yyy])、配列内アイテムの属性が意図せず入れ替わってしまう場合がありえるため、常にindexを設定してすることを促すルールです
4
+ - 前述例のyyyに当たる値が配列内の別アイテムに紐づいてしまう場合があります
5
+
6
+ ## indexを設定しない場合に起こり得る問題の詳細
7
+
8
+ 配列に当たる部分の連番を指定しない場合(例: a[xxx][][yyy])、配列のアイテムの値はどこからどこまでが1つ目、ここから2つ目... というように区切る処理が自動的に行われます。
9
+ これらの区切りは `全く同じnameが出てきたら区切る` という処理が行われます。
10
+
11
+ ```js
12
+ a[xxx][][id]='1'
13
+ a[xxx][][value]='hoge'
14
+ a[xxx][][id]='2' // `a[xxx][][id]`が被ったのでここで別オブジェクトになる
15
+ a[xxx][][value]='fuga'
16
+
17
+ /* 送信される値の概念モデル
18
+ a: {
19
+ xxx: [
20
+ { id: 1, value: 'hoge' },
21
+ { id: 2, value: 'fuga' },
22
+ ]
23
+ }
24
+ */
25
+ ```
26
+
27
+ この挙動は、例えば以下のようなパターンで問題になります。
28
+
29
+ ```js
30
+ // 問題あるパターン
31
+ // 新規作成でa[xxx][][id]=undefinedなので非表示にしたと仮定
32
+ a[xxx][][value]='hoge'
33
+ a[xxx][][id]='2' // 本来↓のvalueと紐づくべき値が↑に紐づいてしまう
34
+ a[xxx][][value]='fuga' // `a[xxx][][value]`が被ったのでここで別オブジェクトになる
35
+
36
+ /* 送信される値の概念モデル
37
+ a: {
38
+ xxx: [
39
+ { id: 2, value: 'hoge' },
40
+ { id: nil, value: 'fuga' }, // fugaだったもののidがhogeに紐づいてしまう
41
+ ]
42
+ }
43
+ */
44
+ ```
45
+
46
+ この問題は正しくnameにindexを含めることで回避できます
47
+
48
+ ```js
49
+ a[xxx][0][value]='hoge'
50
+ a[xxx][1][id]='2'
51
+ a[xxx][1][value]='fuga'
52
+
53
+ /* 送信される値の概念モデル
54
+ a: {
55
+ xxx: [
56
+ { id: nil, value: 'hoge' },
57
+ { id: 2, value: 'fuga' },
58
+ ]
59
+ }
60
+ */
61
+ ```
62
+
63
+ ## rules
64
+
65
+ ```js
66
+ {
67
+ rules: {
68
+ 'smarthr/best-practice-for-nested-attributes-array-index': 'error', // 'warn', 'off'
69
+ },
70
+ }
71
+ ```
72
+
73
+ ## ❌ Incorrect
74
+
75
+ ```jsx
76
+ <Input name="a[xxxx][][yyy]" />
77
+ <Input name={"${any}[][xxx]"} />
78
+
79
+
80
+ // 文字列の変数などでも `[][` のような指定が出てくるのはほぼname属性に対する指定として
81
+ // 利用される可能性が高いためチェック対象です
82
+ const namePrefix = 'a[xxx][][yyy]'
83
+ ```
84
+
85
+ ## ✅ Correct
86
+
87
+ ```jsx
88
+ <Input name="a[xxxx][0][yyy]" />
89
+ <Input name={"${any}[${index}][xxx]"} />
90
+ const namePrefix = `a[xxx][${index}][yyy]`
91
+ ```
@@ -0,0 +1,35 @@
1
+ const SCHEMA = []
2
+
3
+ const NOINDEX_ARRAY_REGEX = /\[\]\[/
4
+
5
+ /**
6
+ * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
7
+ */
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ schema: SCHEMA,
12
+ },
13
+ create(context) {
14
+ const checker = (node, value) => {
15
+ if (NOINDEX_ARRAY_REGEX.test(value)) {
16
+ context.report({
17
+ node,
18
+ message: `入力要素のname属性に対して、配列に当たる部分の連番を指定しない場合(例: a[xxx][][yyy] )、配列内アイテムの属性が意図せず入れ替わってしまう場合がありえるため、常にindexを設定してください。
19
+ - 例のyyyに当たる値が配列内の別アイテムに紐づいてしまう場合があります。
20
+ - 詳しくは https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index を参照してください`,
21
+ })
22
+ }
23
+ }
24
+
25
+ return {
26
+ Literal: (node) => {
27
+ checker(node, node.value)
28
+ },
29
+ TemplateElement: (node) => {
30
+ checker(node, node.value.cooked)
31
+ },
32
+ }
33
+ },
34
+ }
35
+ module.exports.schema = SCHEMA
@@ -37,6 +37,7 @@ const EXPECTED_NAMES = {
37
37
  'FormControls$': 'FormControls$',
38
38
  'FormDialog$': 'FormDialog$',
39
39
  'FormGroup$': 'FormGroup$',
40
+ 'HelpLink$': 'HelpLink$',
40
41
  'Icon$': 'Icon$',
41
42
  'Image$': 'Image$',
42
43
  'Img$': 'Img$',
@@ -14,6 +14,10 @@ const SCHEMA = [
14
14
  ]
15
15
 
16
16
  const NOOP = () => {}
17
+ const findDangerouslySetInnerHTMLAttr = (a) => a.name.name === 'dangerouslySetInnerHTML'
18
+
19
+ const WHITESPACE_REGEX = /(\s|\n)+/g
20
+ const ALLOW_ELM_REGEX = /^(br|RangeSeparator)$/
17
21
 
18
22
  /**
19
23
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -46,43 +50,39 @@ module.exports = {
46
50
  // HINT: 翻訳コンポーネントはテキストとbrのみ許容する
47
51
  if (node.name.name === componentName) {
48
52
  let existValidChild = false
49
- let existNotBrElement = false
50
53
 
51
- node.parent.children.forEach((c) => {
54
+ for (let i = 0; i < node.parent.children.length; i++) {
55
+ const c = node.parent.children[i]
56
+
52
57
  switch (c.type) {
53
58
  case 'JSXText':
54
59
  // HINT: 空白と改行のみの場合はテキストが存在する扱いにはしない
55
- if (c.value.replace(/(\s|\n)+/g, '')) {
56
- existValidChild = true
60
+ if (existValidChild || !c.value.replace(WHITESPACE_REGEX, '')) {
61
+ break
57
62
  }
58
63
 
59
- break
64
+ existValidChild = true
60
65
  case 'JSXExpressionContainer':
61
66
  // TODO 変数がstringのみか判定できるなら対応したい
62
67
  existValidChild = true
63
68
 
64
69
  break
65
70
  case 'JSXElement':
66
- if (c.openingElement.name.name !== 'br') {
67
- existNotBrElement = true
71
+ if (ALLOW_ELM_REGEX.test(c.openingElement.name.name)) {
72
+ break
68
73
  }
69
74
 
70
- break
71
- }
72
- })
73
-
74
- const message = (() => {
75
- if (existNotBrElement) {
76
- return `${componentName} 内では <br /> 以外のタグは使えません`
77
- } else if (!existValidChild && !node.attributes.some((a) => a.name.name === 'dangerouslySetInnerHTML')) {
78
- return `${componentName} 内には必ずテキストを設置してください`
75
+ return context.report({
76
+ node,
77
+ message: `${componentName} 内では <br />, <RangeSeparator /> 以外のタグは使えません`,
78
+ });
79
79
  }
80
- })()
80
+ }
81
81
 
82
- if (message) {
82
+ if (!existValidChild && !node.attributes.some(findDangerouslySetInnerHTMLAttr)) {
83
83
  context.report({
84
84
  node,
85
- message,
85
+ message: `${componentName} 内には必ずテキストを設置してください`,
86
86
  });
87
87
  }
88
88
  }
@@ -0,0 +1,32 @@
1
+ const rule = require('../rules/a11y-help-link-with-support-href')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ parserOptions: {
7
+ ecmaFeatures: {
8
+ jsx: true,
9
+ },
10
+ },
11
+ },
12
+ })
13
+ const errorText = `ヘルプページ用のリンクは smarthr-ui/HelpLink コンポーネントを利用してください`
14
+
15
+ ruleTester.run('a11y-help-link-with-support-href', rule, {
16
+ valid: [
17
+ { code: `<HelpLink href="//support.hoge">any</HelpLink>` },
18
+ { code: `<Hoge href="//support.hoge">any</Hoge>` },
19
+ { code: `<HelpLink href="//support.hoge" />` },
20
+ { code: `<HelpLink href={"//support.hoge"} />` },
21
+ { code: `<HelpLink href={path.support.hoge} />` },
22
+ { code: `<HelpLink href={supportUrl} />` },
23
+ ],
24
+ invalid: [
25
+ { code: `<Anchor href="//support.hoge">any</Anchor>`, errors: [{ message: errorText }] },
26
+ { code: `<HogeLink href={path.support.hoge} />`, errors: [{ message: errorText }] },
27
+ { code: `<a href={supportUrl}>ほげ</a>`, errors: [{ message: errorText }] },
28
+ { code: `<HogeLink href={path.support.hoge?.fuga} />`, errors: [{ message: errorText }] },
29
+ { code: `<HogeAnchor href={a ? path.support.hoge?.fuga : null} />`, errors: [{ message: errorText }] },
30
+ { code: `<HogeLink href={a ? undefined : supportHogeHref} />`, errors: [{ message: errorText }] },
31
+ ]
32
+ })
@@ -55,10 +55,12 @@ ruleTester.run('best-practice-for-button-element', rule, {
55
55
  { code: `<HogeCluster justify="end" gap={0}>{a}</HogeCluster>` },
56
56
  { code: `<Stack align="flex-end">{a}</Stack>` },
57
57
  { code: `<HogeStack align="end">{a}</HogeStack>` },
58
+ { code: `<Cluster>{a}</Cluster>` },
59
+ { code: `<Cluster>{a.b}</Cluster>` },
60
+ { code: `<Cluster>{a ? b : c}</Cluster>` },
58
61
  ],
59
62
  invalid: [
60
63
  { code: `<Stack><Hoge /></Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
61
- { code: `<Stack>{a}</Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
62
64
  { code: `<AnyStack>{a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
63
65
  { code: `<AnyStack>{a && <><Hoge /></>}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
64
66
  { code: `<AnyStack>{a && a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
@@ -70,7 +72,6 @@ ruleTester.run('best-practice-for-button-element', rule, {
70
72
  { code: `<AnyStack>{a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
71
73
  { code: `<AnyStack>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
72
74
  { code: `<Cluster><Hoge /></Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
73
- { code: `<Cluster>{a}</Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
74
75
  { code: `<AnyCluster>{a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
75
76
  { code: `<AnyCluster>{a && <><Hoge /></>}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
76
77
  { code: `<AnyCluster>{a && a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
@@ -81,8 +82,6 @@ ruleTester.run('best-practice-for-button-element', rule, {
81
82
  { code: `<AnyCluster>{a ? a.b.hoge(action) : <Hoge />}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
82
83
  { code: `<AnyCluster>{a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
83
84
  { code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
84
- { code: `<HogeCluster justify="center">{a}</HogeCluster>`, errors: [ { message: 'HogeCluster は smarthr-ui/Cluster ではなく smarthr-ui/Center でマークアップしてください' } ] },
85
- { code: `<HogeStack align="center">{a}</HogeStack>`, errors: [ { message: 'HogeStack は smarthr-ui/Stack ではなく smarthr-ui/Center でマークアップしてください' } ] },
86
85
  { code: `<HogeStack gap={0}>{a}{b}</HogeStack>`, errors: [ { message: `HogeStack に "gap={0}" が指定されており、smarthr-ui/Stack の利用方法として誤っている可能性があります。以下の修正方法を検討してください。
87
86
  - 方法1: 子要素を一つにまとめられないか検討してください
88
87
  - 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
@@ -0,0 +1,31 @@
1
+ const rule = require('../rules/best-practice-for-nested-attributes-array-index')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ parserOptions: {
7
+ ecmaFeatures: {
8
+ jsx: true,
9
+ },
10
+ },
11
+ },
12
+ })
13
+
14
+ const ERROR_MESSAGE = `入力要素のname属性に対して、配列に当たる部分の連番を指定しない場合(例: a[xxx][][yyy] )、配列内アイテムの属性が意図せず入れ替わってしまう場合がありえるため、常にindexを設定してください。
15
+ - 例のyyyに当たる値が配列内の別アイテムに紐づいてしまう場合があります。
16
+ - 詳しくは https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index を参照してください`
17
+
18
+ ruleTester.run('best-practice-for-nested-attributes-array-index', rule, {
19
+ valid: [
20
+ { code: `<Input name="a[xxxx][0][yyy]" />` },
21
+ { code: '`<Input name="a[xxxx][${index}][yyy]" />`' },
22
+ { code: `const hoge = 'a[xxxx][0][id]'`},
23
+ { code: 'const hoge = `${prefix}[${index}][id]`'},
24
+ ],
25
+ invalid: [
26
+ { code: `<Input name="a[xxxx][][yyy]" />`, errors: [ { message: ERROR_MESSAGE } ] },
27
+ { code: '<Input name={`${any}[][yyy]`} />', errors: [ { message: ERROR_MESSAGE } ] },
28
+ { code: `const hoge = 'a[xxxx][][id]'`, errors: [ { message: ERROR_MESSAGE } ] },
29
+ { code: 'const hoge = `${prefix}[][id]`', errors: [ { message: ERROR_MESSAGE } ] },
30
+ ]
31
+ })
@@ -23,13 +23,14 @@ ruleTester.run('format-translate-component', rule, {
23
23
  { code: '<Any data-wovn-enable="true">ほげ</Any>', options },
24
24
  { code: '<Translate>ほげ</Translate>', options },
25
25
  { code: '<Translate>ほげ<br />ふが</Translate>', options },
26
+ { code: '<Translate>1<RangeSeparator />100</Translate>', options },
26
27
  { code: '<Translate>{any}</Translate>', options },
27
28
  { code: '<Translate dangerouslySetInnerHTML={{ __html: "ほげ" }} />', options },
28
29
  ],
29
30
  invalid: [
30
31
  { code: '<Any data-translate="true">ほげ</Any>', options, errors: [ { message: 'data-translate 属性は使用せず、 @/any/path/Translate コンポーネントを利用してください' } ] },
31
- { code: '<Translate><Any>ほげ</Any></Translate>', options, errors: [ { message: 'Translate 内では <br /> 以外のタグは使えません' } ] },
32
- { code: '<Translate><Any /></Translate>', options, errors: [ { message: 'Translate 内では <br /> 以外のタグは使えません' } ] },
32
+ { code: '<Translate><Any>ほげ</Any></Translate>', options, errors: [ { message: 'Translate 内では <br />, <RangeSeparator /> 以外のタグは使えません' } ] },
33
+ { code: '<Translate><Any /></Translate>', options, errors: [ { message: 'Translate 内では <br />, <RangeSeparator /> 以外のタグは使えません' } ] },
33
34
  { code: '<Translate></Translate>', options, errors: [ { message: 'Translate 内には必ずテキストを設置してください' } ] },
34
35
  ]
35
36
  })