eslint-plugin-smarthr 6.10.1 → 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 +15 -0
- package/package.json +2 -2
- package/rules/require-barrel-import/index.js +59 -33
- package/rules/trim-props/index.js +16 -8
- package/test/prohibit-export-array-type.js +2 -1
- package/test/require-barrel-import.js +395 -0
- package/test/trim-props.js +2 -2
- package/tsconfig.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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
|
+
|
|
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)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **test:** typescript-eslint v8のparserに更新 ([#1217](https://github.com/kufu/tamatebako/issues/1217)) ([e44ac27](https://github.com/kufu/tamatebako/commit/e44ac27e26afc9cbe26244afd38e62bb65aecaa4))
|
|
19
|
+
|
|
5
20
|
## [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
21
|
|
|
7
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "6.10.
|
|
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": "
|
|
40
|
+
"gitHead": "e1a8159fdc6396bb83fdf7f616f52f3c714ff390"
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
59
|
-
if (
|
|
60
|
-
return
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return absolutePath
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
/**
|
|
@@ -101,6 +104,24 @@ const isImportedInsideImporter = (importerDir, importedPath) => {
|
|
|
101
104
|
return importedPath === importerDir || importedPath.startsWith(importerDir + '/')
|
|
102
105
|
}
|
|
103
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
|
+
|
|
104
125
|
/**
|
|
105
126
|
* allowedImportsオプションに基づいて、特定のimportが許可されているかチェックする
|
|
106
127
|
* @param {object} node - ImportDeclaration node
|
|
@@ -164,6 +185,10 @@ const findBarrelFile = (importedPath, importerDir) => {
|
|
|
164
185
|
let currentPath = importedPath
|
|
165
186
|
let barrel = undefined
|
|
166
187
|
|
|
188
|
+
// import元とimport先の共通の親ディレクトリを見つける
|
|
189
|
+
// 共通の親のbarrelファイルは除外する(同じディレクトリツリー内の相対importには適用されない)
|
|
190
|
+
const commonParent = findCommonParent(importerDir, importedPath)
|
|
191
|
+
|
|
167
192
|
// ディレクトリ指定の場合、そのindex.tsを指していることは自明なので一階層上から探索
|
|
168
193
|
if (fs.existsSync(currentPath) && fs.statSync(currentPath).isDirectory()) {
|
|
169
194
|
pathSegments.pop()
|
|
@@ -172,9 +197,10 @@ const findBarrelFile = (importedPath, importerDir) => {
|
|
|
172
197
|
|
|
173
198
|
while (pathSegments.length > 0) {
|
|
174
199
|
// 以下の場合は探索終了
|
|
175
|
-
// 1.
|
|
176
|
-
// 2.
|
|
177
|
-
|
|
200
|
+
// 1. 共通の親ディレクトリに到達した場合(commonParent自体のbarrelは除外)
|
|
201
|
+
// 2. いずれかのreplacePathsのルートに到達した場合
|
|
202
|
+
// 3. import先がimport元の内部にある場合(同階層・サブディレクトリからのimport)
|
|
203
|
+
if (currentPath === commonParent || ALL_ROOT_PATHS.includes(currentPath) || isImportedInsideImporter(importerDir, currentPath)) {
|
|
178
204
|
break
|
|
179
205
|
}
|
|
180
206
|
|
|
@@ -12,15 +12,23 @@ module.exports = {
|
|
|
12
12
|
fixable: 'whitespace',
|
|
13
13
|
},
|
|
14
14
|
create(context) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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:
|
|
7
|
+
parser: tseslint.parser,
|
|
7
8
|
parserOptions: {
|
|
8
9
|
ecmaFeatures: {
|
|
9
10
|
jsx: true,
|
|
@@ -0,0 +1,395 @@
|
|
|
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
|
+
// 親階層からのimport(commonParentにbarrelがある場合)
|
|
266
|
+
{
|
|
267
|
+
code: `import { createUserRole } from '../hooks/createUserRoleAction'`,
|
|
268
|
+
filename: (() => {
|
|
269
|
+
createFixture('parent-import-common-parent-barrel', {
|
|
270
|
+
'components': {
|
|
271
|
+
'index.tsx': 'export {}', // commonParentのbarrelは除外される
|
|
272
|
+
'AddDialog': {
|
|
273
|
+
'AddDialog.tsx': '',
|
|
274
|
+
},
|
|
275
|
+
'hooks': {
|
|
276
|
+
'createUserRoleAction.ts': '',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
return `${fixturesRoot}/parent-import-common-parent-barrel/components/AddDialog/AddDialog.tsx`
|
|
281
|
+
})(),
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// 複雑なネスト - commonParentのbarrelは除外される
|
|
285
|
+
{
|
|
286
|
+
code: `import type { RequestStepActionNotSkipped } from '../utils/withSkipped'`,
|
|
287
|
+
filename: (() => {
|
|
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)
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
return `${fixturesRoot}/nested-common-parent-barrel/Nodes/StepNodeRequestView/Approvers/ApproverRow/buildUserRowsProps.ts`
|
|
307
|
+
})(),
|
|
308
|
+
languageOptions: {
|
|
309
|
+
parser: require('typescript-eslint').parser,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
|
|
314
|
+
invalid: [
|
|
315
|
+
// 親階層からのimport(import先の親にbarrelがある場合)
|
|
316
|
+
{
|
|
317
|
+
code: `import { api } from '../api/client'`,
|
|
318
|
+
filename: (() => {
|
|
319
|
+
createFixture('parent-import-with-barrel', {
|
|
320
|
+
'components': {
|
|
321
|
+
'AddDialog': {
|
|
322
|
+
'AddDialog.tsx': '',
|
|
323
|
+
},
|
|
324
|
+
'api': {
|
|
325
|
+
'index.tsx': 'export {}', // import先の親にbarrel
|
|
326
|
+
'client.ts': '',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
})
|
|
330
|
+
return `${fixturesRoot}/parent-import-with-barrel/components/AddDialog/AddDialog.tsx`
|
|
331
|
+
})(),
|
|
332
|
+
errors: [
|
|
333
|
+
{
|
|
334
|
+
message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Next.js特殊文字パス - import先の親にbarrel
|
|
340
|
+
{
|
|
341
|
+
code: `import { api } from '../api/client'`,
|
|
342
|
+
filename: (() => {
|
|
343
|
+
createFixture('nextjs-import-parent-barrel', {
|
|
344
|
+
'app': {
|
|
345
|
+
'(private)': {
|
|
346
|
+
'settings': {
|
|
347
|
+
'user_roles': {
|
|
348
|
+
'_components': {
|
|
349
|
+
'AddUserRoleDialog': {
|
|
350
|
+
'AddUserRoleDialog.tsx': '',
|
|
351
|
+
},
|
|
352
|
+
'api': {
|
|
353
|
+
'index.tsx': 'export {}', // import先の親にbarrel
|
|
354
|
+
'client.ts': '',
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
})
|
|
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`
|
|
387
|
+
})(),
|
|
388
|
+
errors: [
|
|
389
|
+
{
|
|
390
|
+
message: /からimportするか、.*のbarrelファイルを削除して直接import可能にしてください/,
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
})
|
package/test/trim-props.js
CHANGED
|
@@ -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
|
})
|