eslint-plugin-smarthr 6.10.2 → 6.10.3

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
+ ## [6.10.3](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.10.2...eslint-plugin-smarthr-v6.10.3) (2026-04-10)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **require-barrel-import:** commonParentのbarrelを除外して同一ツリー内の相対importを許可 ([#1222](https://github.com/kufu/tamatebako/issues/1222)) ([fe5a68c](https://github.com/kufu/tamatebako/commit/fe5a68cd50a6f76dde0c403dd1ec33e5b1dd5d57))
11
+ * trim-propsのセレクターをesquery互換へ修正し、ESLint 9.xでのクラッシュを解消 ([#1214](https://github.com/kufu/tamatebako/issues/1214)) ([d2345a0](https://github.com/kufu/tamatebako/commit/d2345a0623ebbf0f805c7c8a6b2b957ac21b0ffd))
12
+
5
13
  ## [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
14
 
7
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "6.10.2",
3
+ "version": "6.10.3",
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": "662f8876643b981d82a4a5aaf5d9a96d7d7ed58b"
40
+ "gitHead": "e1a8159fdc6396bb83fdf7f616f52f3c714ff390"
41
41
  }
@@ -104,6 +104,24 @@ const isImportedInsideImporter = (importerDir, importedPath) => {
104
104
  return importedPath === importerDir || importedPath.startsWith(importerDir + '/')
105
105
  }
106
106
 
107
+ /**
108
+ * 2つのパスの共通の親ディレクトリを見つける
109
+ * @param {string} path1 - パス1
110
+ * @param {string} path2 - パス2
111
+ * @returns {string} 共通の親ディレクトリの絶対パス
112
+ */
113
+ const findCommonParent = (path1, path2) => {
114
+ const segments1 = path1.split('/')
115
+ const segments2 = path2.split('/')
116
+
117
+ let i = 0
118
+ while (i < segments1.length && i < segments2.length && segments1[i] === segments2[i]) {
119
+ i++
120
+ }
121
+
122
+ return segments1.slice(0, i).join('/')
123
+ }
124
+
107
125
  /**
108
126
  * allowedImportsオプションに基づいて、特定のimportが許可されているかチェックする
109
127
  * @param {object} node - ImportDeclaration node
@@ -167,6 +185,10 @@ const findBarrelFile = (importedPath, importerDir) => {
167
185
  let currentPath = importedPath
168
186
  let barrel = undefined
169
187
 
188
+ // import元とimport先の共通の親ディレクトリを見つける
189
+ // 共通の親のbarrelファイルは除外する(同じディレクトリツリー内の相対importには適用されない)
190
+ const commonParent = findCommonParent(importerDir, importedPath)
191
+
170
192
  // ディレクトリ指定の場合、そのindex.tsを指していることは自明なので一階層上から探索
171
193
  if (fs.existsSync(currentPath) && fs.statSync(currentPath).isDirectory()) {
172
194
  pathSegments.pop()
@@ -175,9 +197,10 @@ const findBarrelFile = (importedPath, importerDir) => {
175
197
 
176
198
  while (pathSegments.length > 0) {
177
199
  // 以下の場合は探索終了
178
- // 1. いずれかのreplacePathsのルートに到達した場合
179
- // 2. import先がimport元の内部にある場合(同階層・サブディレクトリからのimport)
180
- if (ALL_ROOT_PATHS.includes(currentPath) || isImportedInsideImporter(importerDir, currentPath)) {
200
+ // 1. 共通の親ディレクトリに到達した場合(commonParent自体のbarrelは除外)
201
+ // 2. いずれかのreplacePathsのルートに到達した場合
202
+ // 3. import先がimport元の内部にある場合(同階層・サブディレクトリからのimport)
203
+ if (currentPath === commonParent || ALL_ROOT_PATHS.includes(currentPath) || isImportedInsideImporter(importerDir, currentPath)) {
181
204
  break
182
205
  }
183
206
 
@@ -12,15 +12,23 @@ module.exports = {
12
12
  fixable: 'whitespace',
13
13
  },
14
14
  create(context) {
15
- return {
16
- ':matches(JSXAttribute > Literal[value=/(^ | $)/], JSXAttribute > JSXExpressionContainer > TemplateLiteral:has(> TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/])))': (node) => {
17
- context.report({
18
- node,
19
- message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
15
+ const checker = (node) => {
16
+ context.report({
17
+ node,
18
+ message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
20
19
  - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/trim-props`,
21
- fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(TRIM_REGEX, '$1$2$3')),
22
- })
23
- },
20
+ fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(TRIM_REGEX, '$1$2$3')),
21
+ })
22
+ }
23
+
24
+ return {
25
+ // esquery は :matches()・:has() 内での > (子結合子) を解釈できないため、
26
+ // 1つの複合セレクターではなく個別のセレクターに分割している
27
+ 'JSXAttribute > Literal[value=/(^ | $)/]': checker,
28
+ 'JSXAttribute > JSXExpressionContainer > TemplateLiteral > TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/])':
29
+ (node) => {
30
+ checker(node.parent)
31
+ },
24
32
  }
25
33
  },
26
34
  }
@@ -261,16 +261,14 @@ ruleTester.run('require-barrel-import', rule, {
261
261
  return `${fixturesRoot}/path-alias-parent-no-barrel/Button/Button.tsx`
262
262
  })(),
263
263
  },
264
- ],
265
264
 
266
- invalid: [
267
- // 親階層からのimport(barrelが存在する場合)
265
+ // 親階層からのimport(commonParentにbarrelがある場合)
268
266
  {
269
267
  code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
270
268
  filename: (() => {
271
- createFixture('parent-import-with-barrel', {
269
+ createFixture('parent-import-common-parent-barrel', {
272
270
  'components': {
273
- 'index.tsx': 'export {}',
271
+ 'index.tsx': 'export {}', // commonParentのbarrelは除外される
274
272
  'AddDialog': {
275
273
  'AddDialog.tsx': '',
276
274
  },
@@ -279,91 +277,57 @@ ruleTester.run('require-barrel-import', rule, {
279
277
  },
280
278
  },
281
279
  })
282
- return `${fixturesRoot}/parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
280
+ return `${fixturesRoot}/parent-import-common-parent-barrel/components/AddDialog/AddDialog.tsx`
283
281
  })(),
284
- errors: [
285
- {
286
- message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
287
- },
288
- ],
289
282
  },
290
283
 
291
- // Next.js特殊文字パス - 親階層からのimport
284
+ // 複雑なネスト - commonParentのbarrelは除外される
292
285
  {
293
- code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
286
+ code: `import type { RequestStepActionNotSkipped } from '../utils/withSkipped'`,
294
287
  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
- },
288
+ createFixture('nested-common-parent-barrel', {
289
+ 'Nodes': {
290
+ 'index.tsx': 'export {}', // commonParentより上のbarrel(除外される)
291
+ 'StepNodeRequestView': {
292
+ 'index.tsx': 'export {}', // commonParent(除外される)
293
+ 'Approvers': {
294
+ 'ApproverRow': {
295
+ 'buildUserRowsProps.ts': '',
296
+ },
297
+ 'utils': {
298
+ 'withSkipped': {
299
+ 'index.ts': 'export {}', // import先のbarrel(valid)
308
300
  },
309
301
  },
310
302
  },
311
303
  },
312
304
  },
313
305
  })
314
- return `${fixturesRoot}/nextjs-parent-import/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
306
+ return `${fixturesRoot}/nested-common-parent-barrel/Nodes/StepNodeRequestView/Approvers/ApproverRow/buildUserRowsProps.ts`
315
307
  })(),
316
- errors: [
317
- {
318
- message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
319
- },
320
- ],
308
+ languageOptions: {
309
+ parser: require('typescript-eslint').parser,
310
+ },
321
311
  },
312
+ ],
322
313
 
323
- // [id] Dynamic Routes - 親階層からのimport
314
+ invalid: [
315
+ // 親階層からのimport(import先の親にbarrelがある場合)
324
316
  {
325
317
  code: `import { api } from '../api/client'`,
326
318
  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', {
319
+ createFixture('parent-import-with-barrel', {
356
320
  'components': {
357
- 'index.tsx': 'export {}',
358
321
  'AddDialog': {
359
322
  'AddDialog.tsx': '',
360
323
  },
361
- 'hooks': {
362
- 'createUserRoleAction.ts': '',
324
+ 'api': {
325
+ 'index.tsx': 'export {}', // import先の親にbarrel
326
+ 'client.ts': '',
363
327
  },
364
328
  },
365
329
  })
366
- return `${fixturesRoot}/path-alias-parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
330
+ return `${fixturesRoot}/parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
367
331
  })(),
368
332
  errors: [
369
333
  {
@@ -372,22 +336,22 @@ ruleTester.run('require-barrel-import', rule, {
372
336
  ],
373
337
  },
374
338
 
375
- // Path alias + Next.js特殊文字パス - 親階層からのimport
339
+ // Next.js特殊文字パス - import先の親にbarrel
376
340
  {
377
- code: `import { createUserRole } from '~/path-alias-nextjs-parent/app/(private)/settings/user_roles/_components/hooks/createUserRoleAction'`,
341
+ code: `import { api } from '../api/client'`,
378
342
  filename: (() => {
379
- createFixture('path-alias-nextjs-parent', {
343
+ createFixture('nextjs-import-parent-barrel', {
380
344
  'app': {
381
345
  '(private)': {
382
346
  'settings': {
383
347
  'user_roles': {
384
348
  '_components': {
385
- 'index.tsx': 'export {}',
386
349
  'AddUserRoleDialog': {
387
350
  'AddUserRoleDialog.tsx': '',
388
351
  },
389
- 'hooks': {
390
- 'createUserRoleAction.ts': '',
352
+ 'api': {
353
+ 'index.tsx': 'export {}', // import先の親にbarrel
354
+ 'client.ts': '',
391
355
  },
392
356
  },
393
357
  },
@@ -395,7 +359,31 @@ ruleTester.run('require-barrel-import', rule, {
395
359
  },
396
360
  },
397
361
  })
398
- return `${fixturesRoot}/path-alias-nextjs-parent/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
362
+ return `${fixturesRoot}/nextjs-import-parent-barrel/app/(private)/settings/user_roles/_components/AddUserRoleDialog/AddUserRoleDialog.tsx`
363
+ })(),
364
+ errors: [
365
+ {
366
+ message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
367
+ },
368
+ ],
369
+ },
370
+
371
+ // Path alias - import先の親にbarrel
372
+ {
373
+ code: `import { api } from '~/path-alias-import-parent-barrel/components/api/client'`,
374
+ filename: (() => {
375
+ createFixture('path-alias-import-parent-barrel', {
376
+ 'components': {
377
+ 'AddDialog': {
378
+ 'AddDialog.tsx': '',
379
+ },
380
+ 'api': {
381
+ 'index.tsx': 'export {}', // import先の親にbarrel
382
+ 'client.ts': '',
383
+ },
384
+ },
385
+ })
386
+ return `${fixturesRoot}/path-alias-import-parent-barrel/components/AddDialog/AddDialog.tsx`
399
387
  })(),
400
388
  errors: [
401
389
  {
@@ -55,12 +55,12 @@ ruleTester.run('trim-props', rule, {
55
55
  {
56
56
  code: '<div data-spec={` a${b} c `}>....</div>',
57
57
  output: '<div data-spec={`a${b} c`}>....</div>',
58
- errors: [{ message: ERROR_MESSAGE }],
58
+ errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
59
59
  },
60
60
  {
61
61
  code: '<div data-spec={` a${b ? ` ${c} ` : " "} d `}>....</div>',
62
62
  output: '<div data-spec={`a${b ? ` ${c} ` : " "} d`}>....</div>',
63
- errors: [{ message: ERROR_MESSAGE }],
63
+ errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
64
64
  },
65
65
  ],
66
66
  })