eslint-plugin-smarthr 6.10.4 → 6.12.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,32 @@
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.12.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.11.0...eslint-plugin-smarthr-v6.12.0) (2026-04-15)
6
+
7
+
8
+ ### Features
9
+
10
+ * **require-barrel-import:** additionalBarrelFileNamesオプション追加と同階層・子階層からのバレルimport禁止 ([#1244](https://github.com/kufu/tamatebako/issues/1244)) ([b38a143](https://github.com/kufu/tamatebako/commit/b38a1432a92bd3ee4e52f3a450177e59f65512f0))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **format-import-path:** dirCount関数でmatch結果がnullの場合のエラーを修正 ([#1246](https://github.com/kufu/tamatebako/issues/1246)) ([fc50fba](https://github.com/kufu/tamatebako/commit/fc50fbadc3da3b046a73bbab664c281fc7be847f))
16
+
17
+ ## [6.11.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.10.4...eslint-plugin-smarthr-v6.11.0) (2026-04-14)
18
+
19
+
20
+ ### Features
21
+
22
+ * **autofixer-smarthr-ui-migration:** v91→v92移行ルールを追加 ([#1209](https://github.com/kufu/tamatebako/issues/1209)) ([c29bf7c](https://github.com/kufu/tamatebako/commit/c29bf7c628e25216245f4082354a88add7e650b2))
23
+ * **require-barrel-import:** エラーメッセージを詳細化して実用性を向上 ([#1227](https://github.com/kufu/tamatebako/issues/1227)) ([e2280e5](https://github.com/kufu/tamatebako/commit/e2280e5e47bfb5c9a2b89bd08331a0b2643c278e))
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * **eslint-plugin-smarthr:** 不要な engines フィールドを削除 ([#1240](https://github.com/kufu/tamatebako/issues/1240)) ([678cfc6](https://github.com/kufu/tamatebako/commit/678cfc6fa4b05eb8547f45c135d27d44f51a7fc7))
29
+ * **format-import-path:** 複数の拡張子(.presentational.tsx等)に対応 ([#1242](https://github.com/kufu/tamatebako/issues/1242)) ([9f51b41](https://github.com/kufu/tamatebako/commit/9f51b41e1be02f6d6f982f1211c4bfb516179119))
30
+
5
31
  ## [6.10.4](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.10.3...eslint-plugin-smarthr-v6.10.4) (2026-04-13)
6
32
 
7
33
 
@@ -63,7 +63,8 @@ const calculateDomainNode = (calclatedContext, node) => {
63
63
  replacedPath = exts.map((j) => `${replacedPath}${j}`).find((j) => fs.existsSync(j)) || replacedPath
64
64
  }
65
65
 
66
- replacedPath = replacedPath.replace(/^(.+?)((\/index)?\.[a-z0-9]+|\/)$/, '$1')
66
+ // 拡張子を除去(.js, .jsx, .ts, .tsx のみ除去し、.presentational などは残す)
67
+ replacedPath = replacedPath.replace(/^(.+?)((\/index)?\.(js|jsx|ts|tsx)|\/)$/, '$1')
67
68
  }
68
69
 
69
70
  const resolvedImportPath = replacedPath[0] === '/' ? replacedPath : ''
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "6.10.4",
3
+ "version": "6.12.0",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
7
7
  "main": "index.js",
8
- "engines": {
9
- "node": ">=24.14.1"
10
- },
11
8
  "scripts": {
12
9
  "test": "jest"
13
10
  },
@@ -37,5 +34,5 @@
37
34
  "eslintplugin",
38
35
  "smarthr"
39
36
  ],
40
- "gitHead": "5e89902850d7084a12d50400dad9d1b0e5764e00"
37
+ "gitHead": "ae65de908cf0b81ad201a18e376bb500a9bd7842"
41
38
  }
@@ -56,10 +56,10 @@ autofixer-smarthr-ui-migrationルールに新しいバージョン(v[XX]→v[Y
56
56
  ## 参考にするファイル
57
57
 
58
58
  必ず以下のファイルを読んで、実装パターンを踏襲してください(最新のversionディレクトリを参照):
59
- - rules/autofixer-smarthr-ui-migration/versions/v90-to-v91/REFERENCE.md(実装パターンの詳細説明)
60
- - rules/autofixer-smarthr-ui-migration/versions/v90-to-v91/index.js(実装例)
61
- - rules/autofixer-smarthr-ui-migration/versions/v90-to-v91/README.md(ユーザー向け移行ガイド)
62
- - rules/autofixer-smarthr-ui-migration/versions/v90-to-v91/test.js(テストケース)
59
+ - rules/autofixer-smarthr-ui-migration/versions/v91-to-v92/REFERENCE.md(実装パターンの詳細説明)
60
+ - rules/autofixer-smarthr-ui-migration/versions/v91-to-v92/index.js(実装例)
61
+ - rules/autofixer-smarthr-ui-migration/versions/v91-to-v92/README.md(ユーザー向け移行ガイド)
62
+ - rules/autofixer-smarthr-ui-migration/versions/v91-to-v92/test.js(テストケース)
63
63
  - test/autofixer-smarthr-ui-migration.js(メインテスト)
64
64
  - libs/common.js(rootPathの取得、tsconfig.jsonのpaths設定読み込み)
65
65
 
@@ -91,6 +91,18 @@ smarthr-ui v[YY]のリリースノート: [GitHubリリースページのURL]
91
91
  - ⚠️ エラーのみ: 手動確認が必要な場合(未知の属性がある、複数の対処方法がある等)
92
92
  - ❌ 検出しない: 複雑すぎる、影響範囲が広すぎる場合
93
93
 
94
+ **escape hatch用classNameの確認(重要):**
95
+
96
+ コンポーネント名が変更される場合、escape hatch用className(例: `smarthr-ui-ComponentName`)も変更される可能性があります。
97
+
98
+ 1. **確認方法**: smarthr-uiのPRの差分を確認し、`smarthr-ui-` で始まるclassNameが変更されているか調査
99
+ 2. **変更がある場合**: CSS/SCSS/styled-components等での使用箇所を検出・置換するチェッカーを追加
100
+ 3. **変更がない場合**: README.mdに「escape hatch classNameの変更なし」と明記
101
+
102
+ **例(v91→v92の場合):**
103
+ - RemoteTriggerダイアログのリネームがあったが、内部的にはControlledダイアログを使用しているため、className変更なし
104
+ - サイズ指定の大文字統一はReact propsの値変更のみで、className生成ロジックには影響なし
105
+
94
106
  ## 実装内容
95
107
 
96
108
  1. versionディレクトリを作成: `versions/v[XX]-to-v[YY]/`
@@ -193,27 +205,159 @@ smarthr-ui v[YY]のリリースノート: [GitHubリリースページのURL]
193
205
  - JSDocコメントを適切に追加してください
194
206
  - ディレクトリ名は必ず `vXX-to-vYY` 形式にしてください(内部キーと統一)
195
207
 
208
+ ## 複数バージョンスキップ時の考慮事項
209
+
210
+ 複数のバージョンをスキップする移行(例: v90→v93)では、コンポーネント名の衝突が発生する可能性があります。
211
+
212
+ ### 問題が起こるケース
213
+
214
+ **例: v90→v92の一気実行**
215
+
216
+ v90→v91とv91→v92の両方が実行されると、以下のような衝突が発生します:
217
+
218
+ ```javascript
219
+ // 元のコード
220
+ import { ActionDialog, RemoteTriggerActionDialog } from 'smarthr-ui'
221
+
222
+ // 1回目の自動修正(v90→v91 + v91→v92が同時に適用)
223
+ import { ControlledActionDialog, ActionDialog } from 'smarthr-ui'
224
+
225
+ // ESLintは自動的に再実行される(staged fixes)
226
+ // 2回目の自動修正で、上記のActionDialogがさらに変換される
227
+ import { ControlledActionDialog, ControlledActionDialog } from 'smarthr-ui'
228
+ // ❌ 重複!RemoteTriggerActionDialogの情報が失われる
229
+ ```
230
+
231
+ **原因:**
232
+ - v90→v91: `ActionDialog` → `ControlledActionDialog`
233
+ - v91→v92: `RemoteTriggerActionDialog` → `ActionDialog`
234
+ - ESLintのstaged fixesにより、2回目の自動修正で新しく生成された`ActionDialog`がさらに`ControlledActionDialog`に変換されてしまう
235
+
236
+ ### 衝突検出の実装
237
+
238
+ このような衝突が予想される場合、`getMigrationPath()`関数で検出し、実行を禁止します。
239
+
240
+ **実装例(index.js):**
241
+
242
+ ```javascript
243
+ function getMigrationPath(from, to) {
244
+ // ... 既存のロジック ...
245
+
246
+ // コンポーネント名衝突の検出
247
+ if (path.includes('v90-v91') && path.includes('v91-v92')) {
248
+ return {
249
+ path,
250
+ skipped,
251
+ conflict: true,
252
+ conflictData: {
253
+ from,
254
+ to,
255
+ middle: '91',
256
+ },
257
+ }
258
+ }
259
+
260
+ return { path, skipped }
261
+ }
262
+ ```
263
+
264
+ **エラーメッセージ(messages):**
265
+
266
+ ```javascript
267
+ messages: {
268
+ conflictingMigration: 'v{{from}}→v{{to}}の一気実行はコンポーネント名の衝突により正しく動作しません。段階的に実行してください: 1. { "from": "{{from}}", "to": "{{middle}}" } を実行 2. { "from": "{{middle}}", "to": "{{to}}" } を実行',
269
+ // ...
270
+ }
271
+ ```
272
+
273
+ **create()での使用:**
274
+
275
+ ```javascript
276
+ create(context) {
277
+ // ... オプションチェック ...
278
+
279
+ const migrationResult = getMigrationPath(from, to)
280
+
281
+ // 衝突検出
282
+ if (migrationResult.conflict) {
283
+ return {
284
+ Program(node) {
285
+ context.report({
286
+ node,
287
+ messageId: 'conflictingMigration',
288
+ data: migrationResult.conflictData,
289
+ })
290
+ },
291
+ }
292
+ }
293
+
294
+ // ... 通常の処理 ...
295
+ }
296
+ ```
297
+
298
+ ### 新バージョン追加時のチェック項目
299
+
300
+ 新しいバージョンを追加する際は、以下を確認してください:
301
+
302
+ 1. **過去のバージョンとの衝突可能性を確認**
303
+ - 今回リネームするコンポーネント名が、過去のバージョンでリネーム**元**だった名前と一致しないか
304
+ - 例: v90で`ActionDialog`→`ControlledActionDialog`、v92で`RemoteTriggerActionDialog`→`ActionDialog`の場合、`ActionDialog`という名前が衝突
305
+
306
+ 2. **衝突が発見された場合**
307
+ - `getMigrationPath()`に衝突検出ロジックを追加
308
+ - `conflictingMigration`メッセージで段階的な実行を促す
309
+
310
+ 3. **テストケースを追加**
311
+ - 衝突するバージョン組み合わせでエラーが表示されることを確認
312
+ ```javascript
313
+ {
314
+ code: `import { ActionDialog } from 'smarthr-ui'`,
315
+ options: [{ from: '90', to: '92' }],
316
+ errors: [{ messageId: 'conflictingMigration', data: { from: '90', to: '92', middle: '91' } }],
317
+ },
318
+ ```
319
+
320
+ ### 参考実装
321
+
322
+ - [v91→v92追加時のコミット](https://github.com/kufu/tamatebako/commit/dc29036): v90→v92衝突検出の実装例
323
+
196
324
  ## 共通機能:smarthrUiAlias オプション
197
325
 
198
326
  プロジェクト固有のsmarthr-ui aliasパスに対応するため、`smarthrUiAlias`オプションが利用可能です。
199
327
 
328
+ ### 共通ヘルパー関数の使用
329
+
330
+ `helpers.js` に共通のヘルパー関数が用意されています。これにより、各versionファイルで重複コードを書く必要がありません。
331
+
332
+ **利用可能なヘルパー:**
333
+ - `setupSmarthrUiAliasOptions(context, options)`: validSources拡張とaliasファイル判定を一括で行う
334
+ - `isFileMatchingSmarthrUiAlias(filename, smarthrUiAlias)`: ファイルパスマッチング(低レベル、通常は不要)
335
+
200
336
  ### createCheckers関数でのオプション利用
201
337
 
202
338
  ```javascript
339
+ const { setupSmarthrUiAliasOptions } = require('../../helpers')
340
+
203
341
  createCheckers(context, sourceCode, options = {}) {
204
- const customSmarthrUiAlias = options.smarthrUiAlias
205
- const validSources = ['smarthr-ui']
206
- if (customSmarthrUiAlias) {
207
- validSources.push(customSmarthrUiAlias)
342
+ // 1行でセットアップ完了
343
+ const { validSources, isAliasFile, filename } = setupSmarthrUiAliasOptions(context, options)
344
+
345
+ // validSourcesを使ってimportをチェック
346
+ const checkers = {
347
+ ImportDeclaration(node) {
348
+ if (!validSources.includes(node.source.value)) return
349
+ // ...
350
+ },
208
351
  }
209
352
 
210
- // aliasファイルかどうかの判定
211
- const isAliasFile = customSmarthrUiAlias && isFileMatchingSmarthrUiAlias(
212
- context.getFilename(),
213
- customSmarthrUiAlias
214
- )
353
+ // aliasファイルの場合のみ、export変数名の置換を追加
354
+ if (isAliasFile) {
355
+ checkers['ExportNamedDeclaration > VariableDeclaration > VariableDeclarator'] = function(node) {
356
+ // ...
357
+ }
358
+ }
215
359
 
216
- // ...
360
+ return checkers
217
361
  }
218
362
  ```
219
363
 
@@ -224,18 +368,39 @@ createCheckers(context, sourceCode, options = {}) {
224
368
 
225
369
  詳細は[README.md](./README.md#smarthr-ui-の-alias-を使用している場合)を参照。
226
370
 
227
- ### 🔄 今後の検討事項:共通化
371
+ ### 共通化済みの機能
372
+
373
+ 以下の機能は `helpers.js` に共通化されています(v92移行ルール追加時に実装):
374
+
375
+ - `setupSmarthrUiAliasOptions`: validSources拡張とaliasファイル判定
376
+ - `isFileMatchingSmarthrUiAlias`: ファイルパスマッチング
377
+
378
+ これにより、各versionファイルで約30行の重複コードが削減されました。
379
+
380
+ ### 🔄 将来的な共通化の検討事項
381
+
382
+ **現状(v92時点):**
383
+ 各versionディレクトリで以下のパターンが繰り返されています:
384
+ - ImportDeclarationチェッカー(コンポーネント名リネーム)
385
+ - ExportNamedDeclarationチェッカー(re-export対応)
386
+ - JSXOpeningElementチェッカー(JSX要素のリネーム)
387
+ - Programチェッカー(aliasファイル名変更エラー)
388
+ - VariableDeclaratorチェッカー(aliasファイル内のexport変数名置換)
389
+
390
+ **共通化の可能性:**
391
+ これらのチェッカーは構造が似ていますが、以下の理由で現時点では見送っています:
228
392
 
229
- **現状:** 各versionディレクトリ(v90-to-v91など)で個別にsmarthrUiAlias関連の処理を実装しています。
393
+ 1. **読みやすさ優先の方針**: このルールは「一時的な使用」を想定し、読みやすさを重視
394
+ 2. **version特有のロジック**: 各versionで微妙に異なる処理が必要になる可能性
395
+ 3. **パターンの確立**: v93, v94... と増えて明確なパターンが確立されてから検討すべき
230
396
 
231
- **検討中:** 以下の処理を共通化できる可能性があります:
232
- - `validSources`の拡張ロジック
233
- - `isFileMatchingSmarthrUiAlias`ヘルパー関数
234
- - export変数名の置換チェッカー追加ロジック
397
+ **再検討のタイミング:**
398
+ - v93, v94などが追加され、パターンが安定した時点
399
+ - チェッカー生成ヘルパー(`createComponentRenameCheckers`など)の実装を検討
400
+ - その際は `helpers.js` に追加し、REFERENCE.mdに「共通パターン」として記載
235
401
 
236
- **実装時期:** v92移行ルール追加時に、重複を確認して共通化を検討してください。共通化する場合は、以下のような場所が候補です:
237
- - `libs/common.js`に共通ヘルパー関数を追加
238
- - 各versionモジュールで共通の基底関数を提供
402
+ **注意:**
403
+ 共通化を進める際は、抽象化しすぎて読みにくくならないよう注意してください。ヘルパー関数のパラメータが複雑になる場合は、重複を許容する方が保守性が高い場合もあります。
239
404
 
240
405
  ## 完了後の作業
241
406
 
@@ -413,7 +578,7 @@ https://github.com/kufu/smarthr-ui/releases
413
578
 
414
579
  各versionディレクトリに`REFERENCE.md`があり、実装パターンや注意点が記載されています。
415
580
 
416
- **最新version:** [v90-to-v91/REFERENCE.md](./versions/v90-to-v91/REFERENCE.md)
581
+ **最新version:** [v91-to-v92/REFERENCE.md](./versions/v91-to-v92/REFERENCE.md)
417
582
 
418
583
  このドキュメントには以下が含まれます:
419
584
  - ファイル構造と各セクションの説明
@@ -192,6 +192,7 @@ export const ActionDialog = (props) => <div>{props.children}</div>
192
192
  | バージョン | 詳細 |
193
193
  |-----------|------|
194
194
  | `90` → `91` | [移行ガイド](./versions/v90-to-v91/README.md) |
195
+ | `91` → `92` | [移行ガイド](./versions/v91-to-v92/README.md) |
195
196
 
196
197
  ## 使用方法
197
198
 
@@ -0,0 +1,78 @@
1
+ /**
2
+ * autofixer-smarthr-ui-migration 専用のヘルパー関数
3
+ *
4
+ * このファイルは複数のバージョン間で共通して使用されるヘルパー関数を提供します。
5
+ */
6
+
7
+ const { rootPath } = require('../../libs/common')
8
+
9
+ /**
10
+ * smarthrUiAliasで指定されたパスと現在のファイルパスがマッチするか判定
11
+ *
12
+ * @param {string} filename - 現在処理中のファイルパス
13
+ * @param {string} smarthrUiAlias - smarthrUiAliasオプションの値(例: '@/components/parts/smarthr-ui')
14
+ * @returns {boolean} マッチする場合true
15
+ */
16
+ function isFileMatchingSmarthrUiAlias(filename, smarthrUiAlias) {
17
+ // rootPathを使って絶対パスで比較を試みる
18
+ const resolved = smarthrUiAlias.replace(/^@\//, `${rootPath}/`)
19
+ if (filename.includes(resolved)) {
20
+ return true
21
+ }
22
+
23
+ // rootPathでマッチしない場合:
24
+ // パスの一部としてマッチング(テスト環境などで使用)
25
+ // 例: '@/components/parts/smarthr-ui' -> 'components/parts/smarthr-ui'
26
+ const pathPart = smarthrUiAlias.replace(/^@\//, '').replace(/^~\//, '')
27
+
28
+ // 以下のパターンにマッチング:
29
+ // 1. ディレクトリ形式: /components/parts/smarthr-ui/index.tsx
30
+ // 2. 個別ファイル: /components/parts/smarthr-ui/ActionDialog.tsx
31
+ // 3. 単一ファイル形式: /components/parts/smarthr-ui.tsx
32
+ return (
33
+ filename.includes(`/${pathPart}/`) ||
34
+ filename.endsWith(`/${pathPart}`) ||
35
+ filename.includes(`/${pathPart}.`)
36
+ )
37
+ }
38
+
39
+ /**
40
+ * smarthrUiAliasオプションのセットアップ
41
+ *
42
+ * validSourcesの拡張とaliasファイル判定を行います。
43
+ *
44
+ * @param {Object} context - ESLintのcontext
45
+ * @param {Object} options - ルールオプション({ smarthrUiAlias?: string })
46
+ * @returns {{ validSources: string[], isAliasFile: boolean, filename: string }}
47
+ *
48
+ * @example
49
+ * const { validSources, isAliasFile, filename } = setupSmarthrUiAliasOptions(context, options)
50
+ *
51
+ * // ImportDeclarationで使用
52
+ * if (!validSources.includes(node.source.value)) return
53
+ *
54
+ * // aliasファイル内のexport変数名置換で使用
55
+ * if (isAliasFile) {
56
+ * // aliasファイル専用の処理
57
+ * }
58
+ */
59
+ function setupSmarthrUiAliasOptions(context, options) {
60
+ const customSmarthrUiAlias = options.smarthrUiAlias
61
+ const validSources = ['smarthr-ui']
62
+ if (customSmarthrUiAlias) {
63
+ validSources.push(customSmarthrUiAlias)
64
+ }
65
+
66
+ const filename = context.getFilename()
67
+ const isAliasFile = customSmarthrUiAlias && isFileMatchingSmarthrUiAlias(
68
+ filename,
69
+ customSmarthrUiAlias
70
+ )
71
+
72
+ return { validSources, isAliasFile, filename }
73
+ }
74
+
75
+ module.exports = {
76
+ isFileMatchingSmarthrUiAlias,
77
+ setupSmarthrUiAliasOptions,
78
+ }
@@ -16,10 +16,12 @@
16
16
  */
17
17
 
18
18
  const v90ToV91 = require('./versions/v90-to-v91/index')
19
+ const v91ToV92 = require('./versions/v91-to-v92/index')
19
20
 
20
21
  // サポートしているバージョン間の移行モジュール
21
22
  const VERSION_MODULES = {
22
23
  'v90-v91': v90ToV91,
24
+ 'v91-v92': v91ToV92,
23
25
  }
24
26
 
25
27
  module.exports = {
@@ -49,8 +51,10 @@ module.exports = {
49
51
  messages: {
50
52
  missingOptions: 'オプションで from と to を指定してください。例: { "from": "90", "to": "91" }',
51
53
  unsupportedVersion: 'サポートされていないバージョンです: {{from}} to {{to}}',
54
+ conflictingMigration: 'v{{from}}→v{{to}}の一気実行はコンポーネント名の衝突により正しく動作しません。段階的に実行してください: 1. { "from": "{{from}}", "to": "{{middle}}" } を実行 2. { "from": "{{middle}}", "to": "{{to}}" } を実行',
52
55
  skippedVersion: 'v{{version}} の自動修正ルールが実装されていません。変更内容は https://github.com/kufu/smarthr-ui/releases から対応するversionの情報を確認してください',
53
56
  ...v90ToV91.messages,
57
+ ...v91ToV92.messages,
54
58
  },
55
59
  },
56
60
  create(context) {
@@ -87,6 +91,19 @@ module.exports = {
87
91
  }
88
92
  }
89
93
 
94
+ // コンポーネント名衝突の検出
95
+ if (migrationResult.conflict) {
96
+ return {
97
+ Program(node) {
98
+ context.report({
99
+ node,
100
+ messageId: 'conflictingMigration',
101
+ data: migrationResult.conflictData,
102
+ })
103
+ },
104
+ }
105
+ }
106
+
90
107
  const { path, skipped } = migrationResult
91
108
 
92
109
  // 各ステップのチェッカーを収集してマージ
@@ -113,16 +130,21 @@ module.exports = {
113
130
  *
114
131
  * @param {string} from - 移行元バージョン(例: "90")
115
132
  * @param {string} to - 移行先バージョン(例: "91")
116
- * @returns {{ path: string[], skipped: number[] } | null} 移行パス情報、または無効な場合はnull
133
+ * @returns {{ path: string[], skipped: number[], conflict?: boolean, conflictData?: object } | null} 移行パス情報、または無効な場合はnull
117
134
  *
118
135
  * @example
119
136
  * getMigrationPath('90', '91')
120
- * // => { path: ['90-91'], skipped: [] }
137
+ * // => { path: ['v90-v91'], skipped: [] }
121
138
  *
122
139
  * @example
123
140
  * getMigrationPath('90', '93')
124
141
  * // 92のモジュールがない場合
125
- * // => { path: ['90-91'], skipped: [92, 93] }
142
+ * // => { path: ['v90-v91'], skipped: [92, 93] }
143
+ *
144
+ * @example
145
+ * getMigrationPath('90', '92')
146
+ * // v90-v91とv91-v92の組み合わせは衝突
147
+ * // => { path: ['v90-v91', 'v91-v92'], skipped: [], conflict: true, conflictData: {...} }
126
148
  */
127
149
  function getMigrationPath(from, to) {
128
150
  const fromNum = parseInt(from)
@@ -153,6 +175,22 @@ function getMigrationPath(from, to) {
153
175
  return null
154
176
  }
155
177
 
178
+ // コンポーネント名衝突の検出
179
+ // v90→v91とv91→v92の両方が含まれる場合、ActionDialogの名前が衝突する
180
+ // (v90のActionDialog→ControlledActionDialog、v90のRemoteTriggerActionDialog→ActionDialog)
181
+ if (path.includes('v90-v91') && path.includes('v91-v92')) {
182
+ return {
183
+ path,
184
+ skipped,
185
+ conflict: true,
186
+ conflictData: {
187
+ from,
188
+ to,
189
+ middle: '91',
190
+ },
191
+ }
192
+ }
193
+
156
194
  return { path, skipped }
157
195
  }
158
196
 
@@ -15,11 +15,13 @@ v91 では Dialog 系コンポーネントが Controlled プレフィックス
15
15
  import { ActionDialog, FormDialog } from 'smarthr-ui'
16
16
  <ActionDialog>...</ActionDialog>
17
17
  <FormDialog>...</FormDialog>
18
+ type Props = ComponentProps<typeof ActionDialog>
18
19
 
19
20
  // Correct(自動修正)
20
21
  import { ControlledActionDialog, ControlledFormDialog } from 'smarthr-ui'
21
22
  <ControlledActionDialog>...</ControlledActionDialog>
22
23
  <ControlledFormDialog>...</ControlledFormDialog>
24
+ type Props = ComponentProps<typeof ControlledActionDialog>
23
25
  ```
24
26
 
25
27
  **対応コンポーネント:**
@@ -28,6 +30,12 @@ import { ControlledActionDialog, ControlledFormDialog } from 'smarthr-ui'
28
30
  - `MessageDialog` → `ControlledMessageDialog`
29
31
  - `StepFormDialog` → `ControlledStepFormDialog`
30
32
 
33
+ **自動修正される箇所:**
34
+ - import文
35
+ - JSX要素(開始タグ・終了タグ)
36
+ - export文(re-export)
37
+ - typeof型参照(`typeof ActionDialog` など)
38
+
31
39
  ### 2. ResponseMessage の `type` → `status` リネーム
32
40
 
33
41
  ```tsx
@@ -13,7 +13,7 @@
13
13
  * 参考: https://github.com/kufu/smarthr-ui/releases/tag/smarthr-ui-v91.0.0
14
14
  */
15
15
 
16
- const { rootPath } = require('../../../../libs/common')
16
+ const { setupSmarthrUiAliasOptions } = require('../../helpers')
17
17
 
18
18
  // ============================================================
19
19
  // 定数定義
@@ -57,18 +57,7 @@ module.exports = {
57
57
  },
58
58
 
59
59
  createCheckers(context, sourceCode, options = {}) {
60
- const customSmarthrUiAlias = options.smarthrUiAlias
61
- const validSources = ['smarthr-ui']
62
- if (customSmarthrUiAlias) {
63
- validSources.push(customSmarthrUiAlias)
64
- }
65
-
66
- // 現在のファイルがaliasファイルか判定
67
- const filename = context.getFilename()
68
- const isAliasFile = customSmarthrUiAlias && isFileMatchingSmarthrUiAlias(
69
- filename,
70
- customSmarthrUiAlias
71
- )
60
+ const { validSources, isAliasFile, filename } = setupSmarthrUiAliasOptions(context, options)
72
61
 
73
62
  const checkers = {
74
63
  // ============================================================
@@ -192,6 +181,24 @@ module.exports = {
192
181
  }
193
182
  },
194
183
 
184
+ // typeof型参照での検出と修正
185
+ // 例: typeof ActionDialog → typeof ControlledActionDialog
186
+ 'TSTypeQuery > Identifier'(node) {
187
+ const componentName = node.name
188
+ const newName = DIALOG_COMPONENTS[componentName]
189
+
190
+ if (newName) {
191
+ context.report({
192
+ node,
193
+ messageId: 'renameDialog',
194
+ data: { old: componentName, new: newName, to: TARGET_VERSION },
195
+ fix(fixer) {
196
+ return fixer.replaceText(node, newName)
197
+ },
198
+ })
199
+ }
200
+ },
201
+
195
202
  // ============================================================
196
203
  // 2, 3, 4. ResponseMessageの属性変更
197
204
  // ============================================================
@@ -576,32 +583,3 @@ module.exports = {
576
583
  },
577
584
  }
578
585
 
579
- /**
580
- * smarthrUiAliasで指定されたパスと現在のファイルパスがマッチするか判定
581
- *
582
- * @param {string} filename - 現在処理中のファイルパス
583
- * @param {string} smarthrUiAlias - smarthrUiAliasオプションの値(例: '@/components/parts/smarthr-ui')
584
- * @returns {boolean} マッチする場合true
585
- */
586
- function isFileMatchingSmarthrUiAlias(filename, smarthrUiAlias) {
587
- // rootPathを使って絶対パスで比較を試みる
588
- const resolved = smarthrUiAlias.replace(/^@\//, `${rootPath}/`)
589
- if (filename.includes(resolved)) {
590
- return true
591
- }
592
-
593
- // rootPathでマッチしない場合:
594
- // パスの一部としてマッチング(テスト環境などで使用)
595
- // 例: '@/components/parts/smarthr-ui' -> 'components/parts/smarthr-ui'
596
- const pathPart = smarthrUiAlias.replace(/^@\//, '').replace(/^~\//, '')
597
-
598
- // 以下のパターンにマッチング:
599
- // 1. ディレクトリ形式: /components/parts/smarthr-ui/index.tsx
600
- // 2. 個別ファイル: /components/parts/smarthr-ui/ActionDialog.tsx
601
- // 3. 単一ファイル形式: /components/parts/smarthr-ui.tsx
602
- return (
603
- filename.includes(`/${pathPart}/`) ||
604
- filename.endsWith(`/${pathPart}`) ||
605
- filename.includes(`/${pathPart}.`)
606
- )
607
- }
@@ -119,6 +119,41 @@ module.exports = {
119
119
  errors: [{ messageId: 'renameDialog', data: { old: 'ActionDialog', new: 'ControlledActionDialog', to: 'v91' } }],
120
120
  },
121
121
 
122
+ // typeof型参照
123
+ {
124
+ code: `import { ActionDialog } from 'smarthr-ui'
125
+ type Props = ComponentProps<typeof ActionDialog>`,
126
+ output: `import { ControlledActionDialog } from 'smarthr-ui'
127
+ type Props = ComponentProps<typeof ControlledActionDialog>`,
128
+ options: v90ToV91Options,
129
+ languageOptions: {
130
+ parser: require('typescript-eslint').parser,
131
+ },
132
+ errors: [
133
+ { messageId: 'renameDialog', data: { old: 'ActionDialog', new: 'ControlledActionDialog', to: 'v91' } },
134
+ { messageId: 'renameDialog', data: { old: 'ActionDialog', new: 'ControlledActionDialog', to: 'v91' } },
135
+ ],
136
+ },
137
+
138
+ // 複数のtypeof型参照
139
+ {
140
+ code: `import { FormDialog } from 'smarthr-ui'
141
+ type RefType = ElementRef<typeof FormDialog>
142
+ const handleAction: ComponentPropsWithoutRef<typeof FormDialog>['onClickClose'] = () => {}`,
143
+ output: `import { ControlledFormDialog } from 'smarthr-ui'
144
+ type RefType = ElementRef<typeof ControlledFormDialog>
145
+ const handleAction: ComponentPropsWithoutRef<typeof ControlledFormDialog>['onClickClose'] = () => {}`,
146
+ options: v90ToV91Options,
147
+ languageOptions: {
148
+ parser: require('typescript-eslint').parser,
149
+ },
150
+ errors: [
151
+ { messageId: 'renameDialog', data: { old: 'FormDialog', new: 'ControlledFormDialog', to: 'v91' } },
152
+ { messageId: 'renameDialog', data: { old: 'FormDialog', new: 'ControlledFormDialog', to: 'v91' } },
153
+ { messageId: 'renameDialog', data: { old: 'FormDialog', new: 'ControlledFormDialog', to: 'v91' } },
154
+ ],
155
+ },
156
+
122
157
  // ============================================================
123
158
  // 2. ResponseMessageのtype→status
124
159
  // ============================================================