Skip to content

Commit cd90dcf

Browse files
committed
feat: improve forSiblings option (add forMaxNestingLevel sub-option)
1 parent 006bdc9 commit cd90dcf

File tree

2 files changed

+207
-13
lines changed

2 files changed

+207
-13
lines changed

src/rules/prefer-alias.js

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ const getImportType = importWithoutAlias => {
3838
return 'parent'
3939
}
4040

41+
const getSiblingsMaxNestingLevel = options => {
42+
if (options.forSiblings === true) {
43+
return Infinity
44+
}
45+
if (options.forSiblings) {
46+
return options.forSiblings.ofMaxNestingLevel
47+
}
48+
49+
return -1
50+
}
51+
4152
export default {
4253
create: context => {
4354
const currentFile = context.getFilename()
@@ -66,6 +77,8 @@ export default {
6677
)
6778
}
6879

80+
const siblingsMaxNestingLevel = getSiblingsMaxNestingLevel(options)
81+
6982
const resolvePath = options.resolvePath || defaultResolvePath
7083

7184
return {
@@ -82,17 +95,25 @@ export default {
8295

8396
const importWithoutAlias = resolvePath(sourcePath, currentFile, options)
8497

98+
const importType = getImportType(importWithoutAlias)
99+
100+
const matchingAlias = findMatchingAlias(
101+
sourcePath,
102+
currentFile,
103+
options,
104+
)
105+
106+
const currentFileNestingLevel =
107+
matchingAlias &&
108+
P.relative(matchingAlias.path, currentFile).split(P.sep).length - 1
109+
85110
const shouldAlias =
86111
!hasAlias &&
87112
((importWithoutAlias |> isParentImport) ||
88-
((importWithoutAlias |> isSiblingImport) && options.forSiblings) ||
113+
((importWithoutAlias |> isSiblingImport) &&
114+
currentFileNestingLevel <= siblingsMaxNestingLevel) ||
89115
((importWithoutAlias |> isSubpathImport) && options.forSubpaths))
90116
if (shouldAlias) {
91-
const matchingAlias = findMatchingAlias(
92-
sourcePath,
93-
currentFile,
94-
options,
95-
)
96117
if (!matchingAlias) {
97118
return undefined
98119
}
@@ -105,8 +126,6 @@ export default {
105126
|> replace(/\\/g, '/')
106127
}` |> replace(/\/$/, '')
107128

108-
const importType = getImportType(importWithoutAlias)
109-
110129
return context.report({
111130
fix: fixer =>
112131
fixer.replaceTextRange(
@@ -124,7 +143,8 @@ export default {
124143
const shouldUnalias =
125144
hasAlias &&
126145
!isDirectAlias &&
127-
(((importWithoutAlias |> isSiblingImport) && !options.forSiblings) ||
146+
(((importWithoutAlias |> isSiblingImport) &&
147+
currentFileNestingLevel > siblingsMaxNestingLevel) ||
128148
((importWithoutAlias |> isSubpathImport) && !options.forSubpaths))
129149
if (shouldUnalias) {
130150
return context.report({
@@ -133,7 +153,7 @@ export default {
133153
[node.source.range[0] + 1, node.source.range[1] - 1],
134154
importWithoutAlias,
135155
),
136-
message: `Unexpected subpath import via alias '${sourcePath}'. Use '${importWithoutAlias}' instead`,
156+
message: `Unexpected ${importType} import via alias '${sourcePath}'. Use '${importWithoutAlias}' instead`,
137157
node,
138158
})
139159
}
@@ -152,8 +172,22 @@ export default {
152172
type: 'object',
153173
},
154174
forSiblings: {
155-
default: false,
156-
type: 'boolean',
175+
anyOf: [
176+
{
177+
default: false,
178+
type: 'boolean',
179+
},
180+
{
181+
additionalProperties: false,
182+
properties: {
183+
ofMaxNestingLevel: {
184+
minimum: 0,
185+
type: 'number',
186+
},
187+
},
188+
type: 'object',
189+
},
190+
],
157191
},
158192
forSubpaths: {
159193
default: false,

src/rules/prefer-alias.spec.js

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,166 @@ export default tester(
7373
output: "import foo from '@/foo'",
7474
})
7575
},
76+
'alias for siblings with max nested level': async () => {
77+
await outputFiles({
78+
'.babelrc.json': JSON.stringify({
79+
plugins: [
80+
[
81+
packageName`babel-plugin-module-resolver`,
82+
{ alias: { '@': '.' } },
83+
],
84+
],
85+
}),
86+
})
87+
expect(
88+
lint("import foo from './foo'", {
89+
eslintConfig: {
90+
rules: {
91+
'self/self': [
92+
'error',
93+
{
94+
forSiblings: {
95+
ofMaxNestingLevel: 0,
96+
},
97+
},
98+
],
99+
},
100+
},
101+
filename: 'bar.js',
102+
}),
103+
).toEqual({
104+
messages: ["Unexpected sibling import './foo'. Use '@/foo' instead"],
105+
output: "import foo from '@/foo'",
106+
})
107+
expect(
108+
lint("import foo from './foo'", {
109+
eslintConfig: {
110+
rules: {
111+
'self/self': [
112+
'error',
113+
{
114+
forSiblings: {
115+
ofMaxNestingLevel: 0,
116+
},
117+
},
118+
],
119+
},
120+
},
121+
filename: 'sub/bar.js',
122+
}).messages,
123+
).toEqual([])
124+
expect(
125+
lint("import foo from './foo'", {
126+
eslintConfig: {
127+
rules: {
128+
'self/self': [
129+
'error',
130+
{
131+
forSiblings: {
132+
ofMaxNestingLevel: 1,
133+
},
134+
},
135+
],
136+
},
137+
},
138+
filename: 'sub/bar.js',
139+
}),
140+
).toEqual({
141+
messages: [
142+
"Unexpected sibling import './foo'. Use '@/sub/foo' instead",
143+
],
144+
output: "import foo from '@/sub/foo'",
145+
})
146+
expect(
147+
lint("import foo from './foo'", {
148+
eslintConfig: {
149+
rules: {
150+
'self/self': [
151+
'error',
152+
{
153+
forSiblings: {
154+
ofMaxNestingLevel: 1,
155+
},
156+
},
157+
],
158+
},
159+
},
160+
filename: 'sub/sub/bar.js',
161+
}).messages,
162+
).toEqual([])
163+
expect(
164+
lint("import foo from './foo'", {
165+
eslintConfig: {
166+
rules: {
167+
'self/self': [
168+
'error',
169+
{
170+
forSiblings: {
171+
ofMaxNestingLevel: 2,
172+
},
173+
},
174+
],
175+
},
176+
},
177+
filename: 'sub/sub/bar.js',
178+
}),
179+
).toEqual({
180+
messages: [
181+
"Unexpected sibling import './foo'. Use '@/sub/sub/foo' instead",
182+
],
183+
output: "import foo from '@/sub/sub/foo'",
184+
})
185+
expect(
186+
lint("import foo from './foo'", {
187+
eslintConfig: {
188+
rules: {
189+
'self/self': [
190+
'error',
191+
{
192+
forSiblings: {
193+
ofMaxNestingLevel: 2,
194+
},
195+
},
196+
],
197+
},
198+
},
199+
filename: 'sub/sub/sub/bar.js',
200+
}).messages,
201+
).toEqual([])
202+
},
203+
'alias for siblings, nested': async () => {
204+
await outputFiles({
205+
'.babelrc.json': JSON.stringify({
206+
plugins: [
207+
[
208+
packageName`babel-plugin-module-resolver`,
209+
{ alias: { '@': '.' } },
210+
],
211+
],
212+
}),
213+
'sub/foo.js': '',
214+
})
215+
expect(
216+
lint("import foo from './foo'", {
217+
eslintConfig: {
218+
rules: {
219+
'self/self': [
220+
'error',
221+
{
222+
forSiblings: true,
223+
},
224+
],
225+
},
226+
},
227+
filename: 'sub/bar.js',
228+
}),
229+
).toEqual({
230+
messages: [
231+
"Unexpected sibling import './foo'. Use '@/sub/foo' instead",
232+
],
233+
output: "import foo from '@/sub/foo'",
234+
})
235+
},
76236
'alias for subpaths': async () => {
77237
await outputFiles({
78238
'.babelrc.json': JSON.stringify({
@@ -135,7 +295,7 @@ export default tester(
135295
})
136296
expect(lint("import foo from '@/foo'")).toEqual({
137297
messages: [
138-
"Unexpected subpath import via alias '@/foo'. Use './foo' instead",
298+
"Unexpected sibling import via alias '@/foo'. Use './foo' instead",
139299
],
140300
output: "import foo from './foo'",
141301
})

0 commit comments

Comments
 (0)