eslint-plugin-smarthr 6.12.1 → 6.13.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.
@@ -118,7 +118,10 @@
118
118
  "Bash(for pr:*)",
119
119
  "Bash(npx lerna:*)",
120
120
  "Bash(npm login:*)",
121
- "Bash(curl -s https://raw.githubusercontent.com/kufu/smarthr-ui/main/CHANGELOG.md)"
121
+ "Bash(curl -s https://raw.githubusercontent.com/kufu/smarthr-ui/main/CHANGELOG.md)",
122
+ "Bash(npm pack:*)",
123
+ "Bash(npx prettier:*)",
124
+ "Bash(pnpm prerelease:*)"
122
125
  ]
123
126
  }
124
127
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
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
+ ## [6.13.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.12.1...eslint-plugin-smarthr-v6.13.0) (2026-04-20)
6
+
7
+
8
+ ### Features
9
+
10
+ * add autofixer for smarthr-ui v92 to v93 migration ([#1267](https://github.com/kufu/tamatebako/issues/1267)) ([cbff132](https://github.com/kufu/tamatebako/commit/cbff132559f93f4fdabc44a561ccc61afd4c8ed8))
11
+
5
12
  ## [6.12.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.12.0...eslint-plugin-smarthr-v6.12.1) (2026-04-19)
6
13
 
7
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "6.12.1",
3
+ "version": "6.13.0",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -34,5 +34,5 @@
34
34
  "eslintplugin",
35
35
  "smarthr"
36
36
  ],
37
- "gitHead": "3b8ac2fc343826a86b14d05ffc9400c417ce3814"
37
+ "gitHead": "75b2791f637c4769435be4f8cfe07cb2a2b5fa2d"
38
38
  }
@@ -17,11 +17,13 @@
17
17
 
18
18
  const v90ToV91 = require('./versions/v90-to-v91/index')
19
19
  const v91ToV92 = require('./versions/v91-to-v92/index')
20
+ const v92ToV93 = require('./versions/v92-to-v93/index')
20
21
 
21
22
  // サポートしているバージョン間の移行モジュール
22
23
  const VERSION_MODULES = {
23
24
  'v90-v91': v90ToV91,
24
25
  'v91-v92': v91ToV92,
26
+ 'v92-v93': v92ToV93,
25
27
  }
26
28
 
27
29
  module.exports = {
@@ -55,6 +57,7 @@ module.exports = {
55
57
  skippedVersion: 'v{{version}} の自動修正ルールが実装されていません。変更内容は https://github.com/kufu/smarthr-ui/releases から対応するversionの情報を確認してください',
56
58
  ...v90ToV91.messages,
57
59
  ...v91ToV92.messages,
60
+ ...v92ToV93.messages,
58
61
  },
59
62
  },
60
63
  create(context) {
@@ -0,0 +1,130 @@
1
+ # smarthr-ui v92 → v93 移行ガイド
2
+
3
+ このドキュメントは、smarthr-ui v92からv93への移行に必要な変更をまとめたものです。
4
+
5
+ ## 対応する破壊的変更
6
+
7
+ ### 1. DropZone: decorators属性の削除
8
+
9
+ v93では、`DropZone`コンポーネントから`decorators`属性が削除されました。ファイル選択ボタンのラベルは、`selectButtonLabel`属性として独立しました。
10
+
11
+ #### 変更内容
12
+
13
+ **削除された属性:**
14
+ - `decorators?: DecoratorsType<'selectButtonLabel'>`
15
+
16
+ **追加された属性:**
17
+ - `selectButtonLabel?: string`
18
+ - ファイル選択ボタンのラベル
19
+ - 省略時はIntlProviderの翻訳が適用される(デフォルト: 「ファイルを選択」)
20
+
21
+ #### 移行方法
22
+
23
+ **Before (v92):**
24
+ ```tsx
25
+ <DropZone decorators={{ selectButtonLabel: () => 'Choose File' }} />
26
+ ```
27
+
28
+ **After (v93):**
29
+ ```tsx
30
+ <DropZone selectButtonLabel="Choose File" />
31
+ ```
32
+
33
+ **省略も可能(IntlProviderの翻訳を使用):**
34
+ ```tsx
35
+ <DropZone />
36
+ ```
37
+
38
+ #### 自動修正可能なパターン
39
+
40
+ 以下のパターンは、ESLintの`--fix`オプションで自動的に修正されます:
41
+
42
+ ```tsx
43
+ // 文字列リテラル
44
+ <DropZone decorators={{ selectButtonLabel: () => 'Choose' }} />
45
+ → <DropZone selectButtonLabel="Choose" />
46
+
47
+ // 変数参照
48
+ <DropZone decorators={{ selectButtonLabel: () => buttonLabel }} />
49
+ → <DropZone selectButtonLabel={buttonLabel} />
50
+
51
+ // 関数呼び出し
52
+ <DropZone decorators={{ selectButtonLabel: () => getLabel() }} />
53
+ → <DropZone selectButtonLabel={getLabel()} />
54
+
55
+ // テンプレートリテラル
56
+ <DropZone decorators={{ selectButtonLabel: () => `Select ${fileType}` }} />
57
+ → <DropZone selectButtonLabel={`Select ${fileType}`} />
58
+
59
+ // decorators が空の場合
60
+ <DropZone decorators={{}} />
61
+ → <DropZone />
62
+ ```
63
+
64
+ #### 手動対応が必要なパターン
65
+
66
+ 以下のパターンは自動修正できません。手動で修正してください:
67
+
68
+ ```tsx
69
+ // ❌ 引数を使用している場合
70
+ <DropZone decorators={{ selectButtonLabel: (defaultLabel) => customLabel || defaultLabel }} />
71
+
72
+ // ❌ BlockStatement(return文を使用)
73
+ <DropZone decorators={{ selectButtonLabel: () => { return 'Choose' } }} />
74
+
75
+ // ❌ spread syntax
76
+ <DropZone decorators={{ ...baseDecorators, selectButtonLabel: () => 'Choose' }} />
77
+
78
+ // ❌ selectButtonLabel以外のキーがある場合
79
+ <DropZone decorators={{ selectButtonLabel: () => 'Choose', otherKey: () => 'value' }} />
80
+ ```
81
+
82
+ #### escape hatch className の変更
83
+
84
+ v93では、DropZoneコンポーネントのescape hatch用className(`smarthr-ui-DropZone`等)に変更はありません。
85
+
86
+ ## ESLintルールの使用方法
87
+
88
+ ### .eslintrc.js の設定
89
+
90
+ ```javascript
91
+ module.exports = {
92
+ rules: {
93
+ 'smarthr/autofixer-smarthr-ui-migration': ['error', { from: '92', to: '93' }],
94
+ },
95
+ }
96
+ ```
97
+
98
+ ### 自動修正の実行
99
+
100
+ ```bash
101
+ # エラーを確認
102
+ pnpm run lint
103
+
104
+ # 自動修正を実行
105
+ pnpm run lint --fix
106
+ ```
107
+
108
+ ### smarthrUiAlias オプション
109
+
110
+ プロジェクト固有のsmarthr-ui aliasパスを使用している場合は、`smarthrUiAlias`オプションを指定してください。
111
+
112
+ ```javascript
113
+ module.exports = {
114
+ rules: {
115
+ 'smarthr/autofixer-smarthr-ui-migration': [
116
+ 'error',
117
+ {
118
+ from: '92',
119
+ to: '93',
120
+ smarthrUiAlias: '@/components/parts/smarthr-ui', // プロジェクト固有のalias
121
+ },
122
+ ],
123
+ },
124
+ }
125
+ ```
126
+
127
+ ## 参考リンク
128
+
129
+ - [smarthr-ui v93.0.0 リリースノート](https://github.com/kufu/smarthr-ui/releases/tag/smarthr-ui-v93.0.0)
130
+ - [PR #6236: DropZone の decorators 属性削除](https://github.com/kufu/smarthr-ui/pull/6236)
@@ -0,0 +1,287 @@
1
+ # v92-to-v93 実装の参考ポイント
2
+
3
+ このドキュメントは、v92→v93の移行ルール実装の構造と、新しいversionを追加する際の参考ポイントを説明します。
4
+
5
+ ## v92→v93 特有の実装パターン
6
+
7
+ ### 1. DropZone の decorators 属性削除と selectButtonLabel の移行
8
+
9
+ v93では DropZone コンポーネントから `decorators` 属性が削除されました。`selectButtonLabel` を新しい独立した属性に移行する必要があります。
10
+
11
+ #### 1-1. selectButtonLabel抽出のヘルパー関数
12
+
13
+ ```javascript
14
+ /**
15
+ * decorators属性からselectButtonLabelを抽出し、移行可能かチェック
16
+ *
17
+ * @param {Object} decoratorsNode - decorators属性のASTノード
18
+ * @param {Object} sourceCode - ソースコード
19
+ * @returns {Object} 解析結果
20
+ * - type: 'spread' | 'migratable' | 'not-migratable' | 'no-label' | 'invalid' | 'other-keys'
21
+ * - value?: string (migratableの場合)
22
+ * - isStringLiteral?: boolean (migratableの場合)
23
+ */
24
+ function extractSelectButtonLabel(decoratorsNode, sourceCode) {
25
+ // decorators={{ ... }} の形式か確認
26
+ if (!decoratorsNode.value || decoratorsNode.value.type !== 'JSXExpressionContainer') {
27
+ return { type: 'invalid' }
28
+ }
29
+
30
+ const expression = decoratorsNode.value.expression
31
+ if (expression.type !== 'ObjectExpression') {
32
+ return { type: 'invalid' }
33
+ }
34
+
35
+ // spread syntaxが含まれているかチェック
36
+ const hasSpread = expression.properties.some((prop) => prop.type === 'SpreadElement')
37
+ if (hasSpread) {
38
+ return { type: 'spread' }
39
+ }
40
+
41
+ // selectButtonLabelプロパティを探す
42
+ const selectButtonLabelProp = expression.properties.find(
43
+ (prop) => prop.type === 'Property' && prop.key.name === 'selectButtonLabel'
44
+ )
45
+
46
+ if (!selectButtonLabelProp) {
47
+ return { type: 'no-label' }
48
+ }
49
+
50
+ // selectButtonLabel以外のキーがある場合
51
+ if (expression.properties.length > 1) {
52
+ return { type: 'other-keys' }
53
+ }
54
+
55
+ const value = selectButtonLabelProp.value
56
+
57
+ // ArrowFunctionExpressionで、引数なし、returnなしのパターンのみ対応
58
+ if (
59
+ value.type !== 'ArrowFunctionExpression' ||
60
+ value.params.length > 0 ||
61
+ value.body.type === 'BlockStatement'
62
+ ) {
63
+ return { type: 'not-migratable' }
64
+ }
65
+
66
+ // bodyの式を抽出
67
+ const bodyExpression = value.body
68
+ const bodyText = sourceCode.getText(bodyExpression)
69
+
70
+ // 文字列リテラルの場合は値を抽出(クォートを除く)
71
+ const isStringLiteral = bodyExpression.type === 'Literal' && typeof bodyExpression.value === 'string'
72
+
73
+ return {
74
+ type: 'migratable',
75
+ value: isStringLiteral ? bodyExpression.value : bodyText,
76
+ isStringLiteral,
77
+ }
78
+ }
79
+ ```
80
+
81
+ **ポイント:**
82
+ - **spread syntax検出**: `SpreadElement` の存在をチェック
83
+ - **selectButtonLabelの有無**: プロパティが存在するかチェック
84
+ - **他のキーの存在**: selectButtonLabel以外のキーがないかチェック
85
+ - **自動移行可能性**: arrow function で引数なし、returnなし(`() => expression`)のみ対応
86
+ - **戻り値のtype分類**: spread / migratable / not-migratable / no-label / invalid / other-keys
87
+
88
+ #### 1-2. decorators属性のチェッカー(条件分岐)
89
+
90
+ ```javascript
91
+ 'JSXAttribute[name.name="decorators"]'(node) {
92
+ const componentName = node.parent.name.name
93
+
94
+ // DropZoneコンポーネントのみを対象
95
+ if (componentName !== 'DropZone') return
96
+
97
+ const result = extractSelectButtonLabel(node, sourceCode)
98
+
99
+ // spread syntaxがある場合 → エラーのみ(手動対応)
100
+ if (result.type === 'spread') {
101
+ context.report({
102
+ node,
103
+ messageId: 'migrateSelectButtonLabelManually',
104
+ data: { component: componentName, to: TARGET_VERSION },
105
+ // fixなし
106
+ })
107
+ return
108
+ }
109
+
110
+ // selectButtonLabel以外のキーがある場合 → エラーのみ(手動対応)
111
+ if (result.type === 'other-keys') {
112
+ context.report({
113
+ node,
114
+ messageId: 'migrateSelectButtonLabelManually',
115
+ data: { component: componentName, to: TARGET_VERSION },
116
+ // fixなし
117
+ })
118
+ return
119
+ }
120
+
121
+ // selectButtonLabelが自動移行可能な場合
122
+ if (result.type === 'migratable') {
123
+ context.report({
124
+ node,
125
+ messageId: 'removeDecorators',
126
+ data: { component: componentName, to: TARGET_VERSION },
127
+ fix(fixer) {
128
+ const fixes = []
129
+
130
+ // 1. selectButtonLabel属性を追加
131
+ const { value, isStringLiteral } = result
132
+ const selectButtonLabelAttr = isStringLiteral
133
+ ? ` selectButtonLabel="${value}"`
134
+ : ` selectButtonLabel={${value}}`
135
+ fixes.push(fixer.insertTextAfter(node.parent.name, selectButtonLabelAttr))
136
+
137
+ // 2. decorators属性を削除
138
+ const tokenBefore = sourceCode.getTokenBefore(node)
139
+ if (tokenBefore && tokenBefore.range[1] < node.range[0]) {
140
+ fixes.push(fixer.removeRange([tokenBefore.range[1], node.range[1]]))
141
+ } else {
142
+ fixes.push(fixer.remove(node))
143
+ }
144
+
145
+ return fixes
146
+ },
147
+ })
148
+ return
149
+ }
150
+
151
+ // selectButtonLabelが存在するが自動移行不可能な場合 → エラーのみ(手動対応)
152
+ if (result.type === 'not-migratable') {
153
+ context.report({
154
+ node,
155
+ messageId: 'migrateSelectButtonLabelManually',
156
+ data: { component: componentName, to: TARGET_VERSION },
157
+ // fixなし
158
+ })
159
+ return
160
+ }
161
+
162
+ // selectButtonLabelがない場合 → decoratorsを削除
163
+ if (result.type === 'no-label') {
164
+ context.report({
165
+ node,
166
+ messageId: 'removeDecorators',
167
+ data: { component: componentName, to: TARGET_VERSION },
168
+ fix(fixer) {
169
+ const tokenBefore = sourceCode.getTokenBefore(node)
170
+ if (tokenBefore && tokenBefore.range[1] < node.range[0]) {
171
+ return fixer.removeRange([tokenBefore.range[1], node.range[1]])
172
+ }
173
+ return fixer.remove(node)
174
+ },
175
+ })
176
+ return
177
+ }
178
+ }
179
+ ```
180
+
181
+ **処理フロー:**
182
+ 1. **spread syntax** → エラーのみ(`migrateSelectButtonLabelManually`)
183
+ 2. **selectButtonLabel以外のキーあり** → エラーのみ(`migrateSelectButtonLabelManually`)
184
+ 3. **selectButtonLabel自動移行可能** → selectButtonLabel追加 + decorators削除
185
+ 4. **selectButtonLabel自動移行不可能** → エラーのみ(`migrateSelectButtonLabelManually`)
186
+ 5. **selectButtonLabelなし** → decorators削除のみ
187
+
188
+ **メッセージID:**
189
+ - `removeDecorators`: decorators削除(自動修正あり)
190
+ - `migrateSelectButtonLabelManually`: selectButtonLabelの手動移行が必要(自動修正なし)
191
+
192
+ ## decorators属性のテストパターン(v92→v93特有)
193
+
194
+ #### パターン1: DropZone - selectButtonLabel自動移行
195
+
196
+ ```javascript
197
+ // 文字列リテラル
198
+ {
199
+ code: `<DropZone decorators={{ selectButtonLabel: () => 'Choose File' }} />`,
200
+ output: `<DropZone selectButtonLabel="Choose File" />`,
201
+ options: v92ToV93Options,
202
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
203
+ },
204
+
205
+ // 変数参照
206
+ {
207
+ code: `<DropZone decorators={{ selectButtonLabel: () => buttonLabel }} />`,
208
+ output: `<DropZone selectButtonLabel={buttonLabel} />`,
209
+ options: v92ToV93Options,
210
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
211
+ },
212
+
213
+ // 関数呼び出し
214
+ {
215
+ code: `<DropZone decorators={{ selectButtonLabel: () => getLabel() }} />`,
216
+ output: `<DropZone selectButtonLabel={getLabel()} />`,
217
+ options: v92ToV93Options,
218
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
219
+ },
220
+
221
+ // テンプレートリテラル
222
+ {
223
+ code: `<DropZone decorators={{ selectButtonLabel: () => \`Select \${fileType}\` }} />`,
224
+ output: `<DropZone selectButtonLabel={\`Select \${fileType}\`} />`,
225
+ options: v92ToV93Options,
226
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
227
+ },
228
+ ```
229
+
230
+ #### パターン2: DropZone - selectButtonLabelがない
231
+
232
+ ```javascript
233
+ // decorators削除のみ(selectButtonLabelなし)
234
+ {
235
+ code: `<DropZone decorators={{}} />`,
236
+ output: `<DropZone />`,
237
+ options: v92ToV93Options,
238
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
239
+ },
240
+ ```
241
+
242
+ #### パターン3: DropZone - 手動対応が必要(エラーのみ、outputなし)
243
+
244
+ ```javascript
245
+ // returnあり
246
+ {
247
+ code: `<DropZone decorators={{ selectButtonLabel: () => { return 'Choose' } }} />`,
248
+ options: v92ToV93Options,
249
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
250
+ },
251
+
252
+ // 引数あり
253
+ {
254
+ code: `<DropZone decorators={{ selectButtonLabel: (defaultLabel) => defaultLabel }} />`,
255
+ options: v92ToV93Options,
256
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
257
+ },
258
+
259
+ // spread syntax
260
+ {
261
+ code: `<DropZone decorators={{ ...baseDecorators, selectButtonLabel: () => 'Choose' }} />`,
262
+ options: v92ToV93Options,
263
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
264
+ },
265
+
266
+ // selectButtonLabel以外のキーがある
267
+ {
268
+ code: `<DropZone decorators={{ selectButtonLabel: () => 'Choose', otherKey: () => 'value' }} />`,
269
+ options: v92ToV93Options,
270
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
271
+ },
272
+ ```
273
+
274
+ **ポイント:**
275
+ - **outputあり**: 自動修正が行われる
276
+ - **outputなし**: エラーのみ(手動対応が必要)
277
+ - **messageId**: `removeDecorators`(自動修正)または `migrateSelectButtonLabelManually`(手動対応)
278
+
279
+ ## 最新versionへの参照(重要)
280
+
281
+ **このREFERENCE.mdを作成したら、DEVELOPER.mdの「参考にするファイル」セクションを必ず更新してください!**
282
+
283
+ 更新箇所:
284
+ - `rules/autofixer-smarthr-ui-migration/DEVELOPER.md` の56-65行目付近
285
+ - `v91-to-v92` → `v92-to-v93` に変更
286
+
287
+ これにより、次回以降のversion追加時に最新の実装パターンを参照できるようになります。
@@ -0,0 +1,198 @@
1
+ /**
2
+ * smarthr-ui v92 → v93 移行ルール
3
+ *
4
+ * v93での破壊的変更に対応する自動修正を提供します。
5
+ *
6
+ * 対応する破壊的変更:
7
+ * 1. DropZone の decorators 属性削除
8
+ * - decorators={{ selectButtonLabel: () => 'label' }} → selectButtonLabel="label"
9
+ * - decorators なしの場合、IntlProviderの翻訳が適用される
10
+ *
11
+ * 参考: https://github.com/kufu/smarthr-ui/releases/tag/smarthr-ui-v93.0.0
12
+ */
13
+
14
+ const { setupSmarthrUiAliasOptions } = require('../../helpers')
15
+
16
+ // ============================================================
17
+ // 定数定義
18
+ // ============================================================
19
+
20
+ // v93を示す定数(メッセージで使用)
21
+ const TARGET_VERSION = 'v93'
22
+
23
+ // ============================================================
24
+ // モジュールエクスポート
25
+ // ============================================================
26
+
27
+ module.exports = {
28
+ messages: {
29
+ removeDecorators: 'smarthr-ui {{to}} では {{component}} の decorators 属性は削除されました。selectButtonLabel 属性を使用してください',
30
+ migrateSelectButtonLabelManually: '{{component}} の decorators.selectButtonLabel を手動で移行してください。selectButtonLabel属性として独立しました。詳細: https://github.com/kufu/smarthr-ui/pull/6236',
31
+ },
32
+
33
+ createCheckers(context, sourceCode, options = {}) {
34
+ const { validSources, isAliasFile, filename } = setupSmarthrUiAliasOptions(context, options)
35
+
36
+ /**
37
+ * decorators属性からselectButtonLabelを抽出し、移行可能かチェック
38
+ *
39
+ * @param {Object} decoratorsNode - decorators属性のASTノード
40
+ * @returns {Object} 解析結果
41
+ * - type: 'spread' | 'migratable' | 'not-migratable' | 'no-label' | 'invalid' | 'other-keys'
42
+ * - value?: string (migratableの場合)
43
+ * - isStringLiteral?: boolean (migratableの場合)
44
+ */
45
+ function extractSelectButtonLabel(decoratorsNode) {
46
+ // decorators={{ ... }} の形式か確認
47
+ if (!decoratorsNode.value || decoratorsNode.value.type !== 'JSXExpressionContainer') {
48
+ return { type: 'invalid' }
49
+ }
50
+
51
+ const expression = decoratorsNode.value.expression
52
+ if (expression.type !== 'ObjectExpression') {
53
+ return { type: 'invalid' }
54
+ }
55
+
56
+ // spread syntaxが含まれているかチェック
57
+ const hasSpread = expression.properties.some((prop) => prop.type === 'SpreadElement')
58
+ if (hasSpread) {
59
+ return { type: 'spread' }
60
+ }
61
+
62
+ // selectButtonLabelプロパティを探す
63
+ const selectButtonLabelProp = expression.properties.find(
64
+ (prop) => prop.type === 'Property' && prop.key.name === 'selectButtonLabel'
65
+ )
66
+
67
+ if (!selectButtonLabelProp) {
68
+ return { type: 'no-label' }
69
+ }
70
+
71
+ // selectButtonLabel以外のキーがある場合
72
+ if (expression.properties.length > 1) {
73
+ return { type: 'other-keys' }
74
+ }
75
+
76
+ const value = selectButtonLabelProp.value
77
+
78
+ // ArrowFunctionExpressionで、引数なし、returnなしのパターンのみ対応
79
+ if (
80
+ value.type !== 'ArrowFunctionExpression' ||
81
+ value.params.length > 0 ||
82
+ value.body.type === 'BlockStatement'
83
+ ) {
84
+ return { type: 'not-migratable' }
85
+ }
86
+
87
+ // bodyの式を抽出
88
+ const bodyExpression = value.body
89
+ const bodyText = sourceCode.getText(bodyExpression)
90
+
91
+ // 文字列リテラルの場合は値を抽出(クォートを除く)
92
+ const isStringLiteral = bodyExpression.type === 'Literal' && typeof bodyExpression.value === 'string'
93
+
94
+ return {
95
+ type: 'migratable',
96
+ value: isStringLiteral ? bodyExpression.value : bodyText,
97
+ isStringLiteral,
98
+ }
99
+ }
100
+
101
+ const checkers = {
102
+ // ============================================================
103
+ // DropZone の decorators 属性削除
104
+ // ============================================================
105
+
106
+ 'JSXAttribute[name.name="decorators"]'(node) {
107
+ const componentName = node.parent.name.name
108
+
109
+ // DropZoneコンポーネントのみを対象
110
+ if (componentName !== 'DropZone') return
111
+
112
+ const result = extractSelectButtonLabel(node)
113
+
114
+ // spread syntaxがある場合 → エラーのみ(手動対応)
115
+ if (result.type === 'spread') {
116
+ context.report({
117
+ node,
118
+ messageId: 'migrateSelectButtonLabelManually',
119
+ data: { component: componentName, to: TARGET_VERSION },
120
+ })
121
+ return
122
+ }
123
+
124
+ // selectButtonLabel以外のキーがある場合 → エラーのみ(手動対応)
125
+ if (result.type === 'other-keys') {
126
+ context.report({
127
+ node,
128
+ messageId: 'migrateSelectButtonLabelManually',
129
+ data: { component: componentName, to: TARGET_VERSION },
130
+ })
131
+ return
132
+ }
133
+
134
+ // selectButtonLabelが自動移行可能な場合
135
+ if (result.type === 'migratable') {
136
+ context.report({
137
+ node,
138
+ messageId: 'removeDecorators',
139
+ data: { component: componentName, to: TARGET_VERSION },
140
+ fix(fixer) {
141
+ const fixes = []
142
+
143
+ // 1. selectButtonLabel属性を追加
144
+ const { value, isStringLiteral } = result
145
+ const selectButtonLabelAttr = isStringLiteral
146
+ ? ` selectButtonLabel="${value}"`
147
+ : ` selectButtonLabel={${value}}`
148
+ fixes.push(fixer.insertTextAfter(node.parent.name, selectButtonLabelAttr))
149
+
150
+ // 2. decorators属性を削除
151
+ const tokenBefore = sourceCode.getTokenBefore(node)
152
+ if (tokenBefore && tokenBefore.range[1] < node.range[0]) {
153
+ fixes.push(fixer.removeRange([tokenBefore.range[1], node.range[1]]))
154
+ } else {
155
+ fixes.push(fixer.remove(node))
156
+ }
157
+
158
+ return fixes
159
+ },
160
+ })
161
+ return
162
+ }
163
+
164
+ // selectButtonLabelが存在するが自動移行不可能な場合 → エラーのみ(手動対応)
165
+ if (result.type === 'not-migratable') {
166
+ context.report({
167
+ node,
168
+ messageId: 'migrateSelectButtonLabelManually',
169
+ data: { component: componentName, to: TARGET_VERSION },
170
+ })
171
+ return
172
+ }
173
+
174
+ // selectButtonLabelがない場合 → decoratorsを削除
175
+ if (result.type === 'no-label') {
176
+ context.report({
177
+ node,
178
+ messageId: 'removeDecorators',
179
+ data: { component: componentName, to: TARGET_VERSION },
180
+ fix(fixer) {
181
+ const tokenBefore = sourceCode.getTokenBefore(node)
182
+ if (tokenBefore && tokenBefore.range[1] < node.range[0]) {
183
+ return fixer.removeRange([tokenBefore.range[1], node.range[1]])
184
+ }
185
+ return fixer.remove(node)
186
+ },
187
+ })
188
+ return
189
+ }
190
+ },
191
+ }
192
+
193
+ // aliasファイルの場合、export変数名の置換は不要
194
+ // (DropZoneはリネームされないため)
195
+
196
+ return checkers
197
+ },
198
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * smarthr-ui v92 → v93 移行ルール テストケース
3
+ */
4
+
5
+ const v92ToV93Options = [{ from: '92', to: '93' }]
6
+
7
+ // ============================================================
8
+ // validテストケース(エラーにならないコード)
9
+ // ============================================================
10
+
11
+ const valid = [
12
+ // decorators属性なし
13
+ { code: `<DropZone />`, options: v92ToV93Options },
14
+ { code: `<DropZone selectButtonLabel="Choose File" />`, options: v92ToV93Options },
15
+
16
+ // DropZone以外のコンポーネント(decorators属性があってもエラーにしない)
17
+ { code: `<OtherComponent decorators={{ key: () => 'value' }} />`, options: v92ToV93Options },
18
+ ]
19
+
20
+ // ============================================================
21
+ // invalidテストケース(エラーになり、自動修正されるコード)
22
+ // ============================================================
23
+
24
+ const invalid = [
25
+ // ============================================================
26
+ // 1. DropZone - selectButtonLabel 自動移行
27
+ // ============================================================
28
+
29
+ // 文字列リテラル
30
+ {
31
+ code: `<DropZone decorators={{ selectButtonLabel: () => 'Choose File' }} />`,
32
+ output: `<DropZone selectButtonLabel="Choose File" />`,
33
+ options: v92ToV93Options,
34
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
35
+ },
36
+ {
37
+ code: `<DropZone decorators={{ selectButtonLabel: () => "選択" }} />`,
38
+ output: `<DropZone selectButtonLabel="選択" />`,
39
+ options: v92ToV93Options,
40
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
41
+ },
42
+
43
+ // 変数参照
44
+ {
45
+ code: `<DropZone decorators={{ selectButtonLabel: () => buttonLabel }} />`,
46
+ output: `<DropZone selectButtonLabel={buttonLabel} />`,
47
+ options: v92ToV93Options,
48
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
49
+ },
50
+
51
+ // 関数呼び出し
52
+ {
53
+ code: `<DropZone decorators={{ selectButtonLabel: () => getLabel() }} />`,
54
+ output: `<DropZone selectButtonLabel={getLabel()} />`,
55
+ options: v92ToV93Options,
56
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
57
+ },
58
+
59
+ // テンプレートリテラル
60
+ {
61
+ code: `<DropZone decorators={{ selectButtonLabel: () => \`Select \${fileType}\` }} />`,
62
+ output: `<DropZone selectButtonLabel={\`Select \${fileType}\`} />`,
63
+ options: v92ToV93Options,
64
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
65
+ },
66
+
67
+ // 三項演算子
68
+ {
69
+ code: `<DropZone decorators={{ selectButtonLabel: () => isJapanese ? '選択' : 'Choose' }} />`,
70
+ output: `<DropZone selectButtonLabel={isJapanese ? '選択' : 'Choose'} />`,
71
+ options: v92ToV93Options,
72
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
73
+ },
74
+
75
+ // 複数の属性がある場合
76
+ {
77
+ code: `<DropZone accept="image/*" decorators={{ selectButtonLabel: () => 'Choose' }} multiple />`,
78
+ output: `<DropZone selectButtonLabel="Choose" accept="image/*" multiple />`,
79
+ options: v92ToV93Options,
80
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
81
+ },
82
+
83
+ // ============================================================
84
+ // 2. DropZone - selectButtonLabel なし(decorators削除のみ)
85
+ // ============================================================
86
+
87
+ {
88
+ code: `<DropZone decorators={{}} />`,
89
+ output: `<DropZone />`,
90
+ options: v92ToV93Options,
91
+ errors: [{ messageId: 'removeDecorators', data: { component: 'DropZone', to: 'v93' } }],
92
+ },
93
+
94
+ // ============================================================
95
+ // 3. DropZone - 手動対応が必要(エラーのみ、outputなし)
96
+ // ============================================================
97
+
98
+ // returnあり(BlockStatement)
99
+ {
100
+ code: `<DropZone decorators={{ selectButtonLabel: () => { return 'Choose' } }} />`,
101
+ options: v92ToV93Options,
102
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
103
+ },
104
+
105
+ // 引数あり
106
+ {
107
+ code: `<DropZone decorators={{ selectButtonLabel: (defaultLabel) => defaultLabel }} />`,
108
+ options: v92ToV93Options,
109
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
110
+ },
111
+
112
+ // spread syntax
113
+ {
114
+ code: `<DropZone decorators={{ ...baseDecorators, selectButtonLabel: () => 'Choose' }} />`,
115
+ options: v92ToV93Options,
116
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
117
+ },
118
+ {
119
+ code: `<DropZone decorators={{ selectButtonLabel: () => 'Choose', ...rest }} />`,
120
+ options: v92ToV93Options,
121
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
122
+ },
123
+
124
+ // selectButtonLabel以外のキーがある
125
+ {
126
+ code: `<DropZone decorators={{ selectButtonLabel: () => 'Choose', otherKey: () => 'value' }} />`,
127
+ options: v92ToV93Options,
128
+ errors: [{ messageId: 'migrateSelectButtonLabelManually', data: { component: 'DropZone', to: 'v93' } }],
129
+ },
130
+ ]
131
+
132
+ module.exports = {
133
+ valid,
134
+ invalid,
135
+ }
@@ -2,6 +2,7 @@ const rule = require('../rules/autofixer-smarthr-ui-migration')
2
2
  const RuleTester = require('eslint').RuleTester
3
3
  const v90ToV91Tests = require('../rules/autofixer-smarthr-ui-migration/versions/v90-to-v91/test')
4
4
  const v91ToV92Tests = require('../rules/autofixer-smarthr-ui-migration/versions/v91-to-v92/test')
5
+ const v92ToV93Tests = require('../rules/autofixer-smarthr-ui-migration/versions/v92-to-v93/test')
5
6
 
6
7
  const ruleTester = new RuleTester({
7
8
  languageOptions: {
@@ -17,6 +18,7 @@ ruleTester.run('autofixer-smarthr-ui-migration', rule, {
17
18
  valid: [
18
19
  ...v90ToV91Tests.valid,
19
20
  ...v91ToV92Tests.valid,
21
+ ...v92ToV93Tests.valid,
20
22
  ],
21
23
 
22
24
  invalid: [
@@ -29,7 +31,7 @@ ruleTester.run('autofixer-smarthr-ui-migration', rule, {
29
31
  },
30
32
  {
31
33
  code: `import { ActionDialog } from 'smarthr-ui'`,
32
- options: [{ from: '92', to: '93' }],
34
+ options: [{ from: '93', to: '94' }],
33
35
  errors: [{ messageId: 'unsupportedVersion' }],
34
36
  },
35
37
 
@@ -37,12 +39,10 @@ ruleTester.run('autofixer-smarthr-ui-migration', rule, {
37
39
  // 複数バージョンスキップ
38
40
  // ============================================================
39
41
  {
40
- code: `import { RemoteTriggerActionDialog } from 'smarthr-ui'`,
41
- output: `import { ActionDialog } from 'smarthr-ui'`,
42
- options: [{ from: '91', to: '93' }],
42
+ code: `import { DropZone } from 'smarthr-ui'`,
43
+ options: [{ from: '92', to: '94' }],
43
44
  errors: [
44
- { messageId: 'skippedVersion', data: { version: 'v93' } },
45
- { messageId: 'renameRemoteTriggerDialog', data: { old: 'RemoteTriggerActionDialog', new: 'ActionDialog', to: 'v92' } },
45
+ { messageId: 'skippedVersion', data: { version: 'v94' } },
46
46
  ],
47
47
  },
48
48
  {
@@ -65,5 +65,6 @@ ruleTester.run('autofixer-smarthr-ui-migration', rule, {
65
65
  // ============================================================
66
66
  ...v90ToV91Tests.invalid,
67
67
  ...v91ToV92Tests.invalid,
68
+ ...v92ToV93Tests.invalid,
68
69
  ],
69
70
  })