eslint-plugin-stratified-design 0.12.3 → 0.12.5
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/docs/rules/lower-level-imports.md +13 -1
- package/docs/rules/stratified-imports.md +13 -1
- package/lib/rules/lower-level-imports.js +5 -1
- package/lib/rules/no-same-level-funcs.js +44 -23
- package/lib/rules/stratified-imports.js +18 -1
- package/package.json +1 -1
- package/tests/lib/rules/lower-level-imports.js +28 -4
- package/tests/lib/rules/no-same-level-funcs.js +61 -7
- package/tests/lib/rules/stratified-imports.js +11 -1
|
@@ -142,7 +142,7 @@ The default is as follows:
|
|
|
142
142
|
}
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Imported modules
|
|
145
|
+
Imported modules are excluded from the rule (`lower-level-imports`) using the `excludeImports` option:
|
|
146
146
|
|
|
147
147
|
```json
|
|
148
148
|
{
|
|
@@ -153,6 +153,18 @@ Imported modules can be excluded from the rule (`lower-level-imports`) using the
|
|
|
153
153
|
}
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
Explicitly imported types are excluded using `ignoreType` option:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"stratified-design/stratified-imports": ["error", { "ignoreType": true }]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import type { SomeType } from "./type"; // Explicitly imported types are excluded
|
|
166
|
+
```
|
|
167
|
+
|
|
156
168
|
## Rule Details
|
|
157
169
|
|
|
158
170
|
If a file structure is as follows:
|
|
@@ -87,7 +87,7 @@ The default configuration is as follows:
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Imported modules
|
|
90
|
+
Imported modules are excluded from the rule (`stratified-imports`) using the `excludeImports` option:
|
|
91
91
|
|
|
92
92
|
```json
|
|
93
93
|
{
|
|
@@ -98,6 +98,18 @@ Imported modules can be excluded from the rule (`stratified-imports`) using the
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
Explicitly imported types are excluded using `ignoreType` option:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"stratified-design/stratified-imports": ["error", { "ignoreType": true }]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import type { SomeType } from "./type"; // Explicitly imported types are excluded
|
|
111
|
+
```
|
|
112
|
+
|
|
101
113
|
## Rule Details
|
|
102
114
|
|
|
103
115
|
Consider the following folder structure:
|
|
@@ -98,6 +98,9 @@ module.exports = {
|
|
|
98
98
|
isIndexHighest: {
|
|
99
99
|
type: "boolean",
|
|
100
100
|
},
|
|
101
|
+
ignoreType: {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
},
|
|
101
104
|
},
|
|
102
105
|
additionalProperties: false,
|
|
103
106
|
},
|
|
@@ -124,6 +127,7 @@ module.exports = {
|
|
|
124
127
|
aliases: {},
|
|
125
128
|
useLevelNumber: false,
|
|
126
129
|
isIndexHighest: false,
|
|
130
|
+
ignoreType: false,
|
|
127
131
|
...(context.options[0] || {}),
|
|
128
132
|
};
|
|
129
133
|
|
|
@@ -167,7 +171,7 @@ module.exports = {
|
|
|
167
171
|
const isFileIndex = isFileIndexOfModule(options, fileDir, filePath);
|
|
168
172
|
|
|
169
173
|
const report = (node) => {
|
|
170
|
-
if (node.importKind === "type") return;
|
|
174
|
+
if (options.ignoreType && node.importKind === "type") return;
|
|
171
175
|
|
|
172
176
|
if (isExcludedFile) return;
|
|
173
177
|
|
|
@@ -17,9 +17,39 @@ const { fromCwd, match } = require("../helpers/common");
|
|
|
17
17
|
* @typedef {import('eslint').Rule.Node} Node
|
|
18
18
|
* @typedef {import('eslint').AST.Token} Token
|
|
19
19
|
* @typedef {import('eslint').SourceCode} SourceCode
|
|
20
|
-
* @typedef {({[name: string]: number
|
|
20
|
+
* @typedef {({[name: string]: number})} Levels
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {Node | Token} nodeOrToken
|
|
25
|
+
* @returns {string | null}
|
|
26
|
+
*/
|
|
27
|
+
const deriveFuncName = (nodeOrToken) => {
|
|
28
|
+
const declaration =
|
|
29
|
+
(nodeOrToken.type === "ExportNamedDeclaration" ||
|
|
30
|
+
nodeOrToken.type === "ExportDefaultDeclaration") &&
|
|
31
|
+
Boolean(nodeOrToken.declaration)
|
|
32
|
+
? nodeOrToken.declaration
|
|
33
|
+
: nodeOrToken;
|
|
34
|
+
const isFuncDeclaration = declaration.type === "FunctionDeclaration";
|
|
35
|
+
const isVarDeclaration =
|
|
36
|
+
declaration.type === "VariableDeclaration" &&
|
|
37
|
+
[
|
|
38
|
+
"ArrowFunctionExpression",
|
|
39
|
+
"FunctionExpression",
|
|
40
|
+
"CallExpression",
|
|
41
|
+
"TaggedTemplateExpression",
|
|
42
|
+
].includes(declaration.declarations[0].init.type);
|
|
43
|
+
|
|
44
|
+
if (isFuncDeclaration || isVarDeclaration) {
|
|
45
|
+
const name = isFuncDeclaration
|
|
46
|
+
? declaration.id.name
|
|
47
|
+
: declaration.declarations[0].id.name;
|
|
48
|
+
return name;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
|
|
23
53
|
/**
|
|
24
54
|
* @param {SourceCode} sourceCode
|
|
25
55
|
* @param {Node | Token} nodeOrToken
|
|
@@ -34,7 +64,7 @@ const deriveLevel = (sourceCode, nodeOrToken) => {
|
|
|
34
64
|
return levelInNum;
|
|
35
65
|
}
|
|
36
66
|
}
|
|
37
|
-
return
|
|
67
|
+
return 1;
|
|
38
68
|
};
|
|
39
69
|
|
|
40
70
|
/**
|
|
@@ -56,12 +86,15 @@ const isCalleeLowerLevel = (sourceCode, levels) => {
|
|
|
56
86
|
*/
|
|
57
87
|
return (node, calleeName) => {
|
|
58
88
|
const calleeLevel = levels[calleeName];
|
|
59
|
-
if (calleeLevel === undefined) return true;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
if (calleeLevel === undefined) return true; // 호출되는 함수가 존재하지 않으면 true 리턴. 함수 내의 함수의 경우에는 항상 calleeLevel 이 undefined 이다.
|
|
90
|
+
|
|
91
|
+
const caller = traceAncestor(sourceCode, node);
|
|
92
|
+
const callerLevel = deriveLevel(sourceCode, caller);
|
|
93
|
+
if (callerLevel < calleeLevel) return true;
|
|
94
|
+
|
|
95
|
+
const callerName = deriveFuncName(caller);
|
|
96
|
+
if (callerName === calleeName) return true; // recursive function
|
|
97
|
+
|
|
65
98
|
return false;
|
|
66
99
|
};
|
|
67
100
|
};
|
|
@@ -116,6 +149,7 @@ module.exports = {
|
|
|
116
149
|
if (isExcludedFile) return {};
|
|
117
150
|
|
|
118
151
|
/**
|
|
152
|
+
* @description 함수들의 레벨. 함수 내의 함수는 여기에 기록되지 않는다.
|
|
119
153
|
* @type {Levels}
|
|
120
154
|
*/
|
|
121
155
|
const levels = {};
|
|
@@ -140,22 +174,9 @@ module.exports = {
|
|
|
140
174
|
return {
|
|
141
175
|
Program(node) {
|
|
142
176
|
node.body.forEach((token) => {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const isVarDeclaration =
|
|
146
|
-
declaration.type === "VariableDeclaration" &&
|
|
147
|
-
[
|
|
148
|
-
"ArrowFunctionExpression",
|
|
149
|
-
"FunctionExpression",
|
|
150
|
-
"CallExpression",
|
|
151
|
-
"TaggedTemplateExpression",
|
|
152
|
-
].includes(declaration.declarations[0].init.type);
|
|
153
|
-
|
|
154
|
-
if (isFuncDeclaration || isVarDeclaration) {
|
|
177
|
+
const name = deriveFuncName(token);
|
|
178
|
+
if (name) {
|
|
155
179
|
const level = deriveLevel(sourceCode, token);
|
|
156
|
-
const name = isFuncDeclaration
|
|
157
|
-
? declaration.id.name
|
|
158
|
-
: declaration.declarations[0].id.name;
|
|
159
180
|
levels[name] = level;
|
|
160
181
|
}
|
|
161
182
|
});
|
|
@@ -16,6 +16,16 @@ const helper = require("../helpers/stratifiedImports");
|
|
|
16
16
|
// Rule Definition
|
|
17
17
|
//------------------------------------------------------------------------------
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* aliases: Record<string, string>,
|
|
22
|
+
* exclude: string[],
|
|
23
|
+
* include: string[],
|
|
24
|
+
* excludeImports: string[],
|
|
25
|
+
* ignoreType: boolean,
|
|
26
|
+
* }} Options
|
|
27
|
+
*/
|
|
28
|
+
|
|
19
29
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
20
30
|
module.exports = {
|
|
21
31
|
meta: {
|
|
@@ -49,6 +59,9 @@ module.exports = {
|
|
|
49
59
|
type: "array",
|
|
50
60
|
items: [{ type: "string" }],
|
|
51
61
|
},
|
|
62
|
+
ignoreType: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
},
|
|
52
65
|
},
|
|
53
66
|
additionalProperties: false,
|
|
54
67
|
},
|
|
@@ -64,10 +77,14 @@ module.exports = {
|
|
|
64
77
|
},
|
|
65
78
|
},
|
|
66
79
|
create(context) {
|
|
80
|
+
/**
|
|
81
|
+
* @type {Options}
|
|
82
|
+
*/
|
|
67
83
|
const options = {
|
|
68
84
|
exclude: ["**/*.{test,spec}.{js,ts,jsx,tsx}"],
|
|
69
85
|
include: ["**/*.{js,ts,jsx,tsx}"],
|
|
70
86
|
excludeImports: [],
|
|
87
|
+
ignoreType: false,
|
|
71
88
|
aliases: {},
|
|
72
89
|
...(context.options[0] || {}),
|
|
73
90
|
};
|
|
@@ -124,7 +141,7 @@ module.exports = {
|
|
|
124
141
|
* @returns
|
|
125
142
|
*/
|
|
126
143
|
const report = (node) => {
|
|
127
|
-
if (node.importKind === "type") return;
|
|
144
|
+
if (options.ignoreType && node.importKind === "type") return;
|
|
128
145
|
|
|
129
146
|
if (!isStructureValid) {
|
|
130
147
|
reportError(node, "invalid-json");
|
package/package.json
CHANGED
|
@@ -220,7 +220,7 @@ ruleTester.run("lower-level-imports", rule, {
|
|
|
220
220
|
],
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
|
-
code: "import { func } from '@/other/file
|
|
223
|
+
code: "import { func } from '@/other/file'",
|
|
224
224
|
filename: "./src/other/index.js",
|
|
225
225
|
options: [
|
|
226
226
|
{
|
|
@@ -239,7 +239,14 @@ ruleTester.run("lower-level-imports", rule, {
|
|
|
239
239
|
{
|
|
240
240
|
code: "import type { SomeType } from '@/layer2/subLayer2'",
|
|
241
241
|
filename: "./src/layer1/subLayer2.js",
|
|
242
|
-
options: [
|
|
242
|
+
options: [
|
|
243
|
+
{
|
|
244
|
+
structure,
|
|
245
|
+
root: "./src",
|
|
246
|
+
aliases: { "@/": "./src/" },
|
|
247
|
+
ignoreType: true,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
243
250
|
},
|
|
244
251
|
],
|
|
245
252
|
invalid: [
|
|
@@ -415,7 +422,7 @@ ruleTester.run("lower-level-imports", rule, {
|
|
|
415
422
|
],
|
|
416
423
|
},
|
|
417
424
|
{
|
|
418
|
-
code: "import { func } from '@/other/file
|
|
425
|
+
code: "import { func } from '@/other/file'",
|
|
419
426
|
filename: "./src/other/index.js",
|
|
420
427
|
options: [
|
|
421
428
|
{
|
|
@@ -427,7 +434,24 @@ ruleTester.run("lower-level-imports", rule, {
|
|
|
427
434
|
errors: [
|
|
428
435
|
{
|
|
429
436
|
messageId: "not-registered:file",
|
|
430
|
-
data: { module: "./other/file
|
|
437
|
+
data: { module: "./other/file", file: "./other/index" },
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
code: "import type { SomeType } from '@/layer2/subLayer2'",
|
|
443
|
+
filename: "./src/layer1/subLayer2.js",
|
|
444
|
+
options: [
|
|
445
|
+
{
|
|
446
|
+
structure,
|
|
447
|
+
root: "./src",
|
|
448
|
+
aliases: { "@/": "./src/" },
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
errors: [
|
|
452
|
+
{
|
|
453
|
+
messageId: "barrier",
|
|
454
|
+
data: { module: "./layer2/subLayer2", file: "./layer1/subLayer2" },
|
|
431
455
|
},
|
|
432
456
|
],
|
|
433
457
|
},
|
|
@@ -56,7 +56,15 @@ ruleTester.run("no-same-level-funcs", rule, {
|
|
|
56
56
|
filename: "./src/foo.js",
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
|
-
code: "
|
|
59
|
+
code: "/*@level 2*/function func2(){}; /*@level 1*/function func1(){ func2(); }",
|
|
60
|
+
filename: "./src/foo.js",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
code: "/*@level 2*/function func2(){}; function func1(){ func2(); }",
|
|
64
|
+
filename: "./src/foo.js",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
code: "/*@level 2*/export function func2(){}; /*@level 1*/function func1(){ func2(); }",
|
|
60
68
|
filename: "./src/foo.js",
|
|
61
69
|
},
|
|
62
70
|
|
|
@@ -104,6 +112,52 @@ ruleTester.run("no-same-level-funcs", rule, {
|
|
|
104
112
|
code: "/*\n@level 2\nsomething\n*/\nexport const func2 = () => {};\n/*something\n@level 1\n*/\nconst func1 = () => func2();",
|
|
105
113
|
filename: "./src/foo.js",
|
|
106
114
|
},
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
code: "// @level 2\nfunction func2(){};\nfunction func1(){ func2(); }",
|
|
118
|
+
filename: "./src/foo.js",
|
|
119
|
+
errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
code: "// @level 2\nexport function func2(){};\nfunction func1(){ func2(); }",
|
|
123
|
+
filename: "./src/foo.js",
|
|
124
|
+
errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
code: "function recursiveFunc() { recursiveFunc() }",
|
|
129
|
+
filename: "./src/foo.js",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
code: "const recursiveFunc = () => { recursiveFunc() }",
|
|
133
|
+
filename: "./src/foo.js",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
code: "export const recursiveFunc = () => { recursiveFunc() }",
|
|
137
|
+
filename: "./src/foo.js",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
code: "//@level 2\nconst recursiveFunc = () => { recursiveFunc() }",
|
|
141
|
+
filename: "./src/foo.js",
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
code: "const funcWithInnerFunc = () => { const innerFunc = () => {}; innerFunc() }",
|
|
146
|
+
filename: "./src/foo.js",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
code: "/*@level 2*/const func2 = () => {}; /*@level 1*/const func1 = () => { const innerFunc = () => { func2() }; innerFunc(); }",
|
|
150
|
+
filename: "./src/foo.js",
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
{
|
|
154
|
+
code: "/*@level 2*/const func = () => 'a'; /*@level 1*/const obj = { a: func() } ",
|
|
155
|
+
filename: "./src/foo.js",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
code: "/*@level 2*/const func = () => 'a'; /*@level 1*/const obj = { func: () => { func() } } ",
|
|
159
|
+
filename: "./src/foo.js",
|
|
160
|
+
},
|
|
107
161
|
],
|
|
108
162
|
invalid: [
|
|
109
163
|
{
|
|
@@ -224,20 +278,20 @@ ruleTester.run("no-same-level-funcs", rule, {
|
|
|
224
278
|
},
|
|
225
279
|
|
|
226
280
|
{
|
|
227
|
-
code: "
|
|
281
|
+
code: "function func2(){};\n// @level 1\nfunction func1(){ func2(); }",
|
|
228
282
|
filename: "./src/foo.js",
|
|
229
283
|
errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
|
|
230
284
|
},
|
|
285
|
+
|
|
231
286
|
{
|
|
232
|
-
code: "
|
|
287
|
+
code: "const func = () => 'a'; const obj = { a: func() } ",
|
|
233
288
|
filename: "./src/foo.js",
|
|
234
|
-
errors: [{ messageId: "no-same-level-funcs", data: { func: "
|
|
289
|
+
errors: [{ messageId: "no-same-level-funcs", data: { func: "func" } }],
|
|
235
290
|
},
|
|
236
|
-
|
|
237
291
|
{
|
|
238
|
-
code: "
|
|
292
|
+
code: "const func = () => 'a'; const obj = { func: () => { func() } } ",
|
|
239
293
|
filename: "./src/foo.js",
|
|
240
|
-
errors: [{ messageId: "no-same-level-funcs", data: { func: "
|
|
294
|
+
errors: [{ messageId: "no-same-level-funcs", data: { func: "func" } }],
|
|
241
295
|
},
|
|
242
296
|
],
|
|
243
297
|
});
|
|
@@ -169,7 +169,7 @@ ruleTester.run("stratified-imports", rule, {
|
|
|
169
169
|
{
|
|
170
170
|
code: "import type { SomeType } from './layerA'",
|
|
171
171
|
filename: "./mocked/stratified-imports/layerB.js",
|
|
172
|
-
options: [],
|
|
172
|
+
options: [{ ignoreType: true }],
|
|
173
173
|
},
|
|
174
174
|
],
|
|
175
175
|
invalid: [
|
|
@@ -316,5 +316,15 @@ ruleTester.run("stratified-imports", rule, {
|
|
|
316
316
|
},
|
|
317
317
|
],
|
|
318
318
|
},
|
|
319
|
+
{
|
|
320
|
+
code: "import type { SomeType } from './layerA'",
|
|
321
|
+
filename: "./mocked/stratified-imports/layerB.js",
|
|
322
|
+
errors: [
|
|
323
|
+
{
|
|
324
|
+
messageId: "not-lower-level",
|
|
325
|
+
data: { module: "layerA", file: "layerB" },
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
319
329
|
],
|
|
320
330
|
});
|