eslint-plugin-smarthr 6.10.1 → 6.10.2

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,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.10.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.10.1...eslint-plugin-smarthr-v6.10.2) (2026-04-09)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **test:** typescript-eslint v8のparserに更新 ([#1217](https://github.com/kufu/tamatebako/issues/1217)) ([e44ac27](https://github.com/kufu/tamatebako/commit/e44ac27e26afc9cbe26244afd38e62bb65aecaa4))
11
+
5
12
  ## [6.10.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.10.0...eslint-plugin-smarthr-v6.10.1) (2026-04-08)
6
13
 
7
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "6.10.1",
3
+ "version": "6.10.2",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -37,5 +37,5 @@
37
37
  "eslintplugin",
38
38
  "smarthr"
39
39
  ],
40
- "gitHead": "e2e55eea3e72809fab55082ad6ef4da0c6025ae8"
40
+ "gitHead": "662f8876643b981d82a4a5aaf5d9a96d7d7ed58b"
41
41
  }
@@ -37,13 +37,25 @@ const REGEX_ROOT_PATH = new RegExp(`^${rootPath}/index\.`)
37
37
  const REGEX_INDEX_FILE = /\/index\.(ts|js)x?$/
38
38
  const TARGET_EXTS = ['ts', 'tsx', 'js', 'jsx']
39
39
 
40
- // Path aliasの正規表現を事前生成してキャッシュ
41
- const entriedReplacePathsWithRegex = Object.entries(replacePaths).map(([key, values]) => [
42
- key,
43
- values,
44
- new RegExp(`^${key}(.+)$`),
45
- values.map(v => new RegExp(`^${path.resolve(`${CWD}/${v}`)}(.+)$`))
46
- ])
40
+ // Path aliasの情報を事前計算してキャッシュ
41
+ const REPLACE_PATHS_INFO = Object.entries(replacePaths).map(([key, values]) => {
42
+ const resolvedPaths = values.map(v => path.resolve(`${CWD}/${v.replace(/\/\*$/, '')}`))
43
+ return {
44
+ key,
45
+ values,
46
+ keyRegex: new RegExp(`^${key}(.+)$`),
47
+ resolvedPaths,
48
+ valueRegexes: resolvedPaths.map(p => new RegExp(`^${p}(.+)$`))
49
+ }
50
+ })
51
+
52
+ // @/ と ~/ のパスのみをrootとする(READMEの仕様通り)
53
+ const ALL_ROOT_PATHS = (() => {
54
+ const rootKeys = ['@/', '~/']
55
+ return REPLACE_PATHS_INFO
56
+ .filter(info => rootKeys.includes(info.key))
57
+ .flatMap(info => info.resolvedPaths)
58
+ })()
47
59
 
48
60
  /**
49
61
  * Path aliasを絶対パスに変換する
@@ -55,17 +67,13 @@ const resolvePathAlias = (importPath) => {
55
67
  return importPath
56
68
  }
57
69
 
58
- return entriedReplacePathsWithRegex.reduce((result, [key, values, keyRegex]) => {
59
- if (result === importPath) {
60
- return values.reduce((resolved, value) => {
61
- if (resolved === result && keyRegex.test(result)) {
62
- return resolved.replace(keyRegex, `${path.resolve(`${CWD}/${value}`)}/$1`)
63
- }
64
- return resolved
65
- }, result)
70
+ for (const { keyRegex, resolvedPaths } of REPLACE_PATHS_INFO) {
71
+ if (keyRegex.test(importPath)) {
72
+ return importPath.replace(keyRegex, `${resolvedPaths[0]}/$1`)
66
73
  }
67
- return result
68
- }, importPath)
74
+ }
75
+
76
+ return importPath
69
77
  }
70
78
 
71
79
  /**
@@ -74,20 +82,15 @@ const resolvePathAlias = (importPath) => {
74
82
  * @returns {string} Path alias(例: '@/components/Button')
75
83
  */
76
84
  const convertToPathAlias = (absolutePath) => {
77
- return entriedReplacePathsWithRegex.reduce((result, [key, values, keyRegex, valueRegexes]) => {
78
- if (result === absolutePath) {
79
- return values.reduce((converted, value, index) => {
80
- if (converted === result) {
81
- const regexp = valueRegexes[index]
82
- if (regexp.test(converted)) {
83
- return converted.replace(regexp, `${key}/$1`).replace(REGEX_UNNECESSARY_SLASH, '/')
84
- }
85
- }
86
- return converted
87
- }, result)
85
+ for (const { key, valueRegexes } of REPLACE_PATHS_INFO) {
86
+ for (const regexp of valueRegexes) {
87
+ if (regexp.test(absolutePath)) {
88
+ return absolutePath.replace(regexp, `${key}/$1`).replace(REGEX_UNNECESSARY_SLASH, '/')
89
+ }
88
90
  }
89
- return result
90
- }, absolutePath)
91
+ }
92
+
93
+ return absolutePath
91
94
  }
92
95
 
93
96
  /**
@@ -172,9 +175,9 @@ const findBarrelFile = (importedPath, importerDir) => {
172
175
 
173
176
  while (pathSegments.length > 0) {
174
177
  // 以下の場合は探索終了
175
- // 1. root pathに到達した場合
178
+ // 1. いずれかのreplacePathsのルートに到達した場合
176
179
  // 2. import先がimport元の内部にある場合(同階層・サブディレクトリからのimport)
177
- if (importerDir === rootPath || isImportedInsideImporter(importerDir, currentPath)) {
180
+ if (ALL_ROOT_PATHS.includes(currentPath) || isImportedInsideImporter(importerDir, currentPath)) {
178
181
  break
179
182
  }
180
183
 
@@ -1,9 +1,10 @@
1
1
  const rule = require('../rules/prohibit-export-array-type')
2
2
  const RuleTester = require('eslint').RuleTester
3
+ const tseslint = require('typescript-eslint')
3
4
 
4
5
  const ruleTester = new RuleTester({
5
6
  languageOptions: {
6
- parser: require('@typescript-eslint/parser'),
7
+ parser: tseslint.parser,
7
8
  parserOptions: {
8
9
  ecmaFeatures: {
9
10
  jsx: true,
@@ -0,0 +1,407 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const rule = require('../rules/require-barrel-import')
4
+ const RuleTester = require('eslint').RuleTester
5
+
6
+ const ruleTester = new RuleTester({
7
+ languageOptions: {
8
+ parserOptions: {
9
+ ecmaFeatures: {
10
+ jsx: true,
11
+ },
12
+ },
13
+ },
14
+ })
15
+
16
+ // テストフィクスチャのルートディレクトリ
17
+ const fixturesRoot = path.join(__dirname, '..', 'test-fixtures')
18
+
19
+ /**
20
+ * テスト用のファイル構造を作成するヘルパー
21
+ * @param {string} testName - テスト名(ディレクトリ名として使用)
22
+ * @param {Object} structure - ファイル構造定義
23
+ * @returns {string} 作成したディレクトリのパス
24
+ */
25
+ function createFixture(testName, structure) {
26
+ const fixtureDir = path.join(fixturesRoot, testName)
27
+
28
+ // ディレクトリが既に存在する場合は削除
29
+ if (fs.existsSync(fixtureDir)) {
30
+ fs.rmSync(fixtureDir, { recursive: true, force: true })
31
+ }
32
+
33
+ // ディレクトリとファイルを再帰的に作成
34
+ function createStructure(dir, struct) {
35
+ fs.mkdirSync(dir, { recursive: true })
36
+
37
+ for (const [name, content] of Object.entries(struct)) {
38
+ const fullPath = path.join(dir, name)
39
+
40
+ if (typeof content === 'object' && content !== null) {
41
+ // ディレクトリ
42
+ createStructure(fullPath, content)
43
+ } else {
44
+ // ファイル
45
+ fs.writeFileSync(fullPath, content || '')
46
+ }
47
+ }
48
+ }
49
+
50
+ createStructure(fixtureDir, structure)
51
+ return fixtureDir
52
+ }
53
+
54
+ /**
55
+ * テスト終了後のクリーンアップ
56
+ */
57
+ function cleanupFixtures() {
58
+ if (fs.existsSync(fixturesRoot)) {
59
+ const entries = fs.readdirSync(fixturesRoot)
60
+ for (const entry of entries) {
61
+ const fullPath = path.join(fixturesRoot, entry)
62
+ if (fs.statSync(fullPath).isDirectory()) {
63
+ fs.rmSync(fullPath, { recursive: true, force: true })
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ // テスト終了後にクリーンアップ
70
+ afterAll(() => {
71
+ cleanupFixtures()
72
+ })
73
+
74
+ ruleTester.run('require-barrel-import', rule, {
75
+ valid: [
76
+ // 同階層・サブディレクトリからのimport(エラーにならない)
77
+ {
78
+ code: `import { useMenu } from './hooks/useMenu'`,
79
+ filename: (() => {
80
+ createFixture('same-level-import', {
81
+ 'Menu': {
82
+ 'MenuItem.tsx': '',
83
+ 'index.tsx': 'export {}',
84
+ 'hooks': {
85
+ 'useMenu.ts': '',
86
+ },
87
+ },
88
+ })
89
+ return `${fixturesRoot}/same-level-import/Menu/MenuItem.tsx`
90
+ })(),
91
+ },
92
+
93
+ // Next.js App Router特殊文字パス - 同階層import
94
+ {
95
+ code: `import { useUsers } from './hooks/useUsers'`,
96
+ filename: (() => {
97
+ createFixture('nextjs-special-chars', {
98
+ 'app': {
99
+ '(private)': {
100
+ 'settings': {
101
+ 'user_roles': {
102
+ '_components': {
103
+ 'index.tsx': 'export {}',
104
+ 'AddUserRoleDialog': {
105
+ 'index.tsx': 'export {}',
106
+ 'AddUserRoleDialog.tsx': '',
107
+ 'hooks': {
108
+ 'useUsers.ts': '',
109
+ },
110
+ },
111
+ },
112
+ },
113
+ },
114
+ },
115
+ },
116
+ })
117
+ return `${fixturesRoot}/nextjs-special-chars/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
118
+ })(),
119
+ },
120
+
121
+ // Dynamic Routes - [id]パス(同階層)
122
+ {
123
+ code: `import { useDetail } from './hooks/useDetail'`,
124
+ filename: (() => {
125
+ createFixture('nextjs-dynamic-route', {
126
+ 'app': {
127
+ 'items': {
128
+ '[id]': {
129
+ 'index.tsx': 'export {}',
130
+ 'DetailPage.tsx': '',
131
+ 'hooks': {
132
+ 'useDetail.ts': '',
133
+ },
134
+ },
135
+ },
136
+ },
137
+ })
138
+ return `${fixturesRoot}/nextjs-dynamic-route/app/items/[id]/DetailPage.tsx`
139
+ })(),
140
+ },
141
+
142
+ // barrel が存在しない場合(同階層サブディレクトリ)
143
+ {
144
+ code: `import { helper } from './utils/helper'`,
145
+ filename: (() => {
146
+ createFixture('no-barrel', {
147
+ 'components': {
148
+ 'Button': {
149
+ 'Button.tsx': '',
150
+ 'utils': {
151
+ 'helper.ts': '',
152
+ },
153
+ },
154
+ },
155
+ })
156
+ return `${fixturesRoot}/no-barrel/components/Button/Button.tsx`
157
+ })(),
158
+ },
159
+
160
+ // 親階層からのimport + barrelなし(エラーにならない)
161
+ {
162
+ code: `import { helper } from '../utils/helper'`,
163
+ filename: (() => {
164
+ createFixture('parent-import-no-barrel', {
165
+ // index.tsx なし(barrelなし)
166
+ 'Button': {
167
+ 'Button.tsx': '',
168
+ },
169
+ 'utils': {
170
+ 'helper.ts': '',
171
+ },
172
+ })
173
+ return `${fixturesRoot}/parent-import-no-barrel/Button/Button.tsx`
174
+ })(),
175
+ },
176
+
177
+ // Next.js特殊文字パス - 親階層からのimport + barrelなし
178
+ {
179
+ code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
180
+ filename: (() => {
181
+ createFixture('nextjs-parent-no-barrel', {
182
+ // index.tsx なし(barrelなし)
183
+ 'AddUserRoleDialog': {
184
+ 'AddUserRoleDialog.tsx': '',
185
+ },
186
+ 'hooks': {
187
+ 'createUserRoleAction.ts': '',
188
+ },
189
+ })
190
+ return `${fixturesRoot}/nextjs-parent-no-barrel/AddUserRoleDialog/AddUserRoleDialog.tsx`
191
+ })(),
192
+ },
193
+
194
+ // Dynamic Routes - 親階層からのimport + barrelなし
195
+ {
196
+ code: `import { api } from '../api/client'`,
197
+ filename: (() => {
198
+ createFixture('dynamic-route-parent-no-barrel', {
199
+ // index.tsx なし(barrelなし)
200
+ '[id]': {
201
+ 'DetailPage.tsx': '',
202
+ },
203
+ 'api': {
204
+ 'client.ts': '',
205
+ },
206
+ })
207
+ return `${fixturesRoot}/dynamic-route-parent-no-barrel/[id]/DetailPage.tsx`
208
+ })(),
209
+ },
210
+
211
+ // ============================================================
212
+ // Path alias - 同階層からのimport(エラーにならない)
213
+ // ============================================================
214
+ {
215
+ code: `import { useMenu } from '~/path-alias-same-level/Menu/hooks/useMenu'`,
216
+ filename: (() => {
217
+ createFixture('path-alias-same-level', {
218
+ 'Menu': {
219
+ 'MenuItem.tsx': '',
220
+ 'index.tsx': 'export {}',
221
+ 'hooks': {
222
+ 'useMenu.ts': '',
223
+ },
224
+ },
225
+ })
226
+ return `${fixturesRoot}/path-alias-same-level/Menu/MenuItem.tsx`
227
+ })(),
228
+ },
229
+
230
+ // Path alias - 同階層サブディレクトリ + barrelなし
231
+ {
232
+ code: `import { helper } from '~/path-alias-no-barrel/components/Button/utils/helper'`,
233
+ filename: (() => {
234
+ createFixture('path-alias-no-barrel', {
235
+ 'components': {
236
+ 'Button': {
237
+ 'Button.tsx': '',
238
+ 'utils': {
239
+ 'helper.ts': '',
240
+ },
241
+ },
242
+ },
243
+ })
244
+ return `${fixturesRoot}/path-alias-no-barrel/components/Button/Button.tsx`
245
+ })(),
246
+ },
247
+
248
+ // Path alias - 親階層からのimport + barrelなし
249
+ {
250
+ code: `import { helper } from '~/path-alias-parent-no-barrel/utils/helper'`,
251
+ filename: (() => {
252
+ createFixture('path-alias-parent-no-barrel', {
253
+ // index.tsx なし(barrelなし)
254
+ 'Button': {
255
+ 'Button.tsx': '',
256
+ },
257
+ 'utils': {
258
+ 'helper.ts': '',
259
+ },
260
+ })
261
+ return `${fixturesRoot}/path-alias-parent-no-barrel/Button/Button.tsx`
262
+ })(),
263
+ },
264
+ ],
265
+
266
+ invalid: [
267
+ // 親階層からのimport(barrelが存在する場合)
268
+ {
269
+ code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
270
+ filename: (() => {
271
+ createFixture('parent-import-with-barrel', {
272
+ 'components': {
273
+ 'index.tsx': 'export {}',
274
+ 'AddDialog': {
275
+ 'AddDialog.tsx': '',
276
+ },
277
+ 'hooks': {
278
+ 'createUserRoleAction.ts': '',
279
+ },
280
+ },
281
+ })
282
+ return `${fixturesRoot}/parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
283
+ })(),
284
+ errors: [
285
+ {
286
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
287
+ },
288
+ ],
289
+ },
290
+
291
+ // Next.js特殊文字パス - 親階層からのimport
292
+ {
293
+ code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
294
+ filename: (() => {
295
+ createFixture('nextjs-parent-import', {
296
+ 'app': {
297
+ '(private)': {
298
+ 'settings': {
299
+ 'user_roles': {
300
+ '_components': {
301
+ 'index.tsx': 'export {}',
302
+ 'AddUserRoleDialog': {
303
+ 'AddUserRoleDialog.tsx': '',
304
+ },
305
+ 'hooks': {
306
+ 'createUserRoleAction.ts': '',
307
+ },
308
+ },
309
+ },
310
+ },
311
+ },
312
+ },
313
+ })
314
+ return `${fixturesRoot}/nextjs-parent-import/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
315
+ })(),
316
+ errors: [
317
+ {
318
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
319
+ },
320
+ ],
321
+ },
322
+
323
+ // [id] Dynamic Routes - 親階層からのimport
324
+ {
325
+ code: `import { api } from '../api/client'`,
326
+ filename: (() => {
327
+ createFixture('dynamic-route-parent-import', {
328
+ 'app': {
329
+ 'items': {
330
+ 'index.tsx': 'export {}',
331
+ '[id]': {
332
+ 'DetailPage.tsx': '',
333
+ },
334
+ 'api': {
335
+ 'client.ts': '',
336
+ },
337
+ },
338
+ },
339
+ })
340
+ return `${fixturesRoot}/dynamic-route-parent-import/app/items/[id]/DetailPage.tsx`
341
+ })(),
342
+ errors: [
343
+ {
344
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
345
+ },
346
+ ],
347
+ },
348
+
349
+ // ============================================================
350
+ // Path alias - 親階層からのimport(barrelあり)
351
+ // ============================================================
352
+ {
353
+ code: `import { createUserRole } from '~/path-alias-parent-import-with-barrel/components/hooks/createUserRoleAction'`,
354
+ filename: (() => {
355
+ createFixture('path-alias-parent-import-with-barrel', {
356
+ 'components': {
357
+ 'index.tsx': 'export {}',
358
+ 'AddDialog': {
359
+ 'AddDialog.tsx': '',
360
+ },
361
+ 'hooks': {
362
+ 'createUserRoleAction.ts': '',
363
+ },
364
+ },
365
+ })
366
+ return `${fixturesRoot}/path-alias-parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
367
+ })(),
368
+ errors: [
369
+ {
370
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
371
+ },
372
+ ],
373
+ },
374
+
375
+ // Path alias + Next.js特殊文字パス - 親階層からのimport
376
+ {
377
+ code: `import { createUserRole } from '~/path-alias-nextjs-parent/app/(private)/settings/user_roles/_components/hooks/createUserRoleAction'`,
378
+ filename: (() => {
379
+ createFixture('path-alias-nextjs-parent', {
380
+ 'app': {
381
+ '(private)': {
382
+ 'settings': {
383
+ 'user_roles': {
384
+ '_components': {
385
+ 'index.tsx': 'export {}',
386
+ 'AddUserRoleDialog': {
387
+ 'AddUserRoleDialog.tsx': '',
388
+ },
389
+ 'hooks': {
390
+ 'createUserRoleAction.ts': '',
391
+ },
392
+ },
393
+ },
394
+ },
395
+ },
396
+ },
397
+ })
398
+ return `${fixturesRoot}/path-alias-nextjs-parent/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
399
+ })(),
400
+ errors: [
401
+ {
402
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
403
+ },
404
+ ],
405
+ },
406
+ ],
407
+ })
package/tsconfig.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "paths": {
4
+ // ルール実装で使用
4
5
  "@/*": ["./src/*"],
5
- "~/*": ["./src/*"]
6
+ // テスト用
7
+ "~/*": ["./test-fixtures/*"]
6
8
  }
7
9
  }
8
10
  }