eslint-plugin-smarthr 1.7.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,14 @@
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
+
5
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)
6
14
 
7
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -26,7 +26,7 @@
26
26
  "json5": "^2.2.3"
27
27
  },
28
28
  "devDependencies": {
29
- "typescript-eslint": "^8.33.0"
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": "12babfba8f3ff1aa9bc9d159136ef15ffce24fe0"
40
+ "gitHead": "baf2b489f2397ac050c0c699e186b8356a8775cb"
41
41
  }
@@ -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,
@@ -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
  }
@@ -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>" にするなど
@@ -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
  })