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.
|
|
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.
|
|
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": "
|
|
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.
|
|
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(
|
|
56
|
-
|
|
60
|
+
if (existValidChild || !c.value.replace(WHITESPACE_REGEX, '')) {
|
|
61
|
+
break
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
|
|
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
|
|
67
|
-
|
|
71
|
+
if (ALLOW_ELM_REGEX.test(c.openingElement.name.name)) {
|
|
72
|
+
break
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
|
|
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 (
|
|
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
|
})
|