eslint-plugin-stratified-design 0.8.0-beta → 0.8.0-beta.1
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/README.md +1 -0
- package/docs/rules/lower-level-imports.md +58 -20
- package/docs/rules/no-same-level-funcs.md +6 -1
- package/docs/rules/stratified-imports.md +178 -0
- package/lib/helpers/common.js +22 -0
- package/lib/helpers/lowerLevelImports/1 layer.js +13 -6
- package/lib/helpers/stratifiedImports/1 layer.js +137 -39
- package/lib/helpers/stratifiedImports/2 layer.js +90 -8
- package/lib/helpers/stratifiedImports/index.js +22 -5
- package/lib/rules/lower-level-imports.js +10 -1
- package/lib/rules/no-same-level-funcs.js +45 -12
- package/lib/rules/stratified-imports.js +40 -28
- package/mocked/stratified-imports/.stratified.json +1 -1
- package/mocked/stratified-imports/layerD/.stratified.json +1 -0
- package/mocked/stratified-imports/layerD/layerDB/.stratified.json +1 -0
- package/package.json +1 -1
- package/tests/lib/helpers/lower-level-imports.js +76 -0
- package/tests/lib/helpers/stratified-imports.js +166 -12
- package/tests/lib/rules/no-same-level-funcs.js +22 -1
- package/tests/lib/rules/stratified-imports.js +51 -1
package/README.md
CHANGED
|
@@ -39,4 +39,5 @@ Then configure the rules you wish to use under the rules section:
|
|
|
39
39
|
## Supported Rules
|
|
40
40
|
|
|
41
41
|
- [lower-level-imports](https://github.com/anisotropy/eslint-plugin-stratified-design/blob/main/docs/rules/lower-level-imports.md): Requires lower-level modules to be imported.
|
|
42
|
+
- [lower-level-imports](https://github.com/anisotropy/eslint-plugin-stratified-design/blob/main/docs/rules/stratified-imports.md): Requires lower-level modules to be imported. The stratified structure is set by `.stratified.json`.
|
|
42
43
|
- [no-same-level-funcs](https://github.com/anisotropy/eslint-plugin-stratified-design/blob/main/docs/rules/no-same-level-funcs.md): Disallows calling functions in the same file.
|
|
@@ -15,9 +15,14 @@ This rule works correctly on POSIX systems, where the path segment separator is
|
|
|
15
15
|
The syntax to specify the level structure is as follows:
|
|
16
16
|
|
|
17
17
|
```json
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
18
|
+
{
|
|
19
|
+
"stratified-design/lower-level-imports": [
|
|
20
|
+
"error",
|
|
21
|
+
{
|
|
22
|
+
"structure": ["layer1", "layer2", "layer3"]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
21
26
|
```
|
|
22
27
|
|
|
23
28
|
In the folder array, the file or folder on the left is considered to be at a higher level than the one on the right.
|
|
@@ -25,9 +30,14 @@ In the folder array, the file or folder on the left is considered to be at a hig
|
|
|
25
30
|
To designate a layer as an abstract barrier, set `barrier` to `true`:
|
|
26
31
|
|
|
27
32
|
```json
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
33
|
+
{
|
|
34
|
+
"stratified-design/lower-level-imports": [
|
|
35
|
+
"error",
|
|
36
|
+
{
|
|
37
|
+
"structure": ["layer1", { "name": "layer2", "barrier": true }, "layer3"]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
31
41
|
```
|
|
32
42
|
|
|
33
43
|
For the 'abstract barrier,' refer to "[Grokking Simplicity](https://grokkingsimplicity.com)."
|
|
@@ -35,33 +45,54 @@ For the 'abstract barrier,' refer to "[Grokking Simplicity](https://grokkingsimp
|
|
|
35
45
|
To locate a node module in the structure, set `nodeModule` to `true`:
|
|
36
46
|
|
|
37
47
|
```json
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
48
|
+
{
|
|
49
|
+
"stratified-design/lower-level-imports": [
|
|
50
|
+
"error",
|
|
51
|
+
{
|
|
52
|
+
"structure": [
|
|
53
|
+
"layer1",
|
|
54
|
+
{ "name": "nodeModule", "nodeModule": true },
|
|
55
|
+
"layer3"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
41
60
|
```
|
|
42
61
|
|
|
43
62
|
The default root directory is the current working directory. To change the root directory, use the `root` option:
|
|
44
63
|
|
|
45
64
|
```json
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
{
|
|
66
|
+
"stratified-design/lower-level-imports": [
|
|
67
|
+
"error",
|
|
68
|
+
{
|
|
69
|
+
"structure": ["layer1", "layer2", "layer3"],
|
|
70
|
+
"root": "./src"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
50
74
|
```
|
|
51
75
|
|
|
52
76
|
If the name of an imported module has an alias, register the alias using the `aliases` option:
|
|
53
77
|
|
|
54
78
|
```json
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
{
|
|
80
|
+
"stratified-design/lower-level-imports": [
|
|
81
|
+
"error",
|
|
82
|
+
{
|
|
83
|
+
"structure": ["layer1", "layer2", "layer3"],
|
|
84
|
+
"aliases": { "@": "./src" }
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
59
88
|
```
|
|
60
89
|
|
|
61
90
|
If you want to register the level of a layer by 'number,' set the option `useLevelNumber` to `true`:
|
|
62
91
|
|
|
63
92
|
```json
|
|
64
|
-
|
|
93
|
+
{
|
|
94
|
+
"stratified-design/lower-level-imports": ["error", { "useLevelNumber": true }]
|
|
95
|
+
}
|
|
65
96
|
```
|
|
66
97
|
|
|
67
98
|
The options `structure` and `useLevelNumber` can be used together.
|
|
@@ -69,13 +100,20 @@ The options `structure` and `useLevelNumber` can be used together.
|
|
|
69
100
|
An `index.xxx` file can be the highest level layer of sibling files when the option `isIndexHighest` is set to `true`:
|
|
70
101
|
|
|
71
102
|
```json
|
|
72
|
-
|
|
103
|
+
{
|
|
104
|
+
"stratified-design/lower-level-imports": ["error", { "isIndexHighest": true }]
|
|
105
|
+
}
|
|
73
106
|
```
|
|
74
107
|
|
|
75
108
|
You can register the files to apply the rule (`lower-level-imports`) using the `include` and `exclude` options:
|
|
76
109
|
|
|
77
110
|
```json
|
|
78
|
-
|
|
111
|
+
{
|
|
112
|
+
"stratified-design/lower-level-imports": [
|
|
113
|
+
"error",
|
|
114
|
+
{ "include": ["**/*.js"], "exclude": ["**/*.test.js"] }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
79
117
|
```
|
|
80
118
|
|
|
81
119
|
The default is as follows:
|
|
@@ -7,7 +7,12 @@ This rule prohibits calling functions at the same level in the same file.
|
|
|
7
7
|
You can register the files to apply the rule (`no-same-level-funcs`) using the `include` and `exclude` options:
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
|
-
|
|
10
|
+
{
|
|
11
|
+
"stratified-design/no-same-level-funcs": [
|
|
12
|
+
"error",
|
|
13
|
+
{ "include": ["**/*.js"], "exclude": ["**/*.test.js"] }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
11
16
|
```
|
|
12
17
|
|
|
13
18
|
The default is as follows:
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Require that lower-level modules be imported (stratified-imports)
|
|
2
|
+
|
|
3
|
+
(Note: This rule works correctly on POSIX systems, where the path segment separator is `/`. It will be updated to work well on Windows systems in the future.)
|
|
4
|
+
|
|
5
|
+
This rule enforces the requirement for importing lower-level modules. The hierarchy should be set by `.stratified.json` in **each folder** as follows:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
[
|
|
9
|
+
["layerA"],
|
|
10
|
+
[{ "name": "layerB", "barrier": true }],
|
|
11
|
+
[{ "name": "nodeModuleC", "nodeModule": true }],
|
|
12
|
+
["layerD", "layerE"]
|
|
13
|
+
]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
And consider that the folder structure is as follows:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┣ layerA
|
|
20
|
+
┣ layerB
|
|
21
|
+
┣ layerD
|
|
22
|
+
┣ layerE
|
|
23
|
+
┃ ┣ index.js
|
|
24
|
+
┃ ┣ entry
|
|
25
|
+
┃ ┣ layerEA
|
|
26
|
+
┃ ┗ .stratified.json
|
|
27
|
+
┗ .stratified.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The above JSON file indicates the following:
|
|
31
|
+
|
|
32
|
+
- The `layerA` file/folder at the highest level.
|
|
33
|
+
- The `layerB` file/folder is a lower-level layer than `layerA` and serves as an abstract barrier. (For the concept of 'abstract barrier,' refer to '[Grokking Simplicity](https://grokkingsimplicity.com).')
|
|
34
|
+
- `nodeModuleC` is an **installed module** (node module) and is at a lower level than `layerB`. (Unregistered node modules are considered to be the lowest layers.)
|
|
35
|
+
- The `layerD` file/folder and the `layerE` file/folder are at the same level and represent the lowest level layers.
|
|
36
|
+
|
|
37
|
+
Consider that the `.stratified.json` in the `layerE` folder is as follows:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
[["index", "entry"], ["layerEA"]]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Higher-level layers than `layerE` can import `./layerE` and `./layer/entry` as follows:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { func } from "./layerE";
|
|
47
|
+
import { func } from "./layerE/entry";
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
However, `./layer/layerEA` should not be imported.
|
|
51
|
+
|
|
52
|
+
## Options
|
|
53
|
+
|
|
54
|
+
If an imported module has an alias, register the alias using the `aliases` option:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"stratified-design/stratified-imports": [
|
|
59
|
+
"error",
|
|
60
|
+
{
|
|
61
|
+
"aliases": { "@/": "./src/" }
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You can register the files to which the rule (`stratified-imports`) should apply using the `include` and `exclude` options:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"stratified-design/lower-level-imports": [
|
|
72
|
+
"error",
|
|
73
|
+
{ "include": ["**/*.js"], "exclude": ["**/*.test.js"] }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The default configuration is as follows:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"include": ["**/*.{js,ts,jsx,tsx}"],
|
|
83
|
+
"exclude": ["**/*.{spec,test}.{js,ts,jsx,tsx}"]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Rule Details
|
|
88
|
+
|
|
89
|
+
Consider the following folder structure:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
src/
|
|
93
|
+
┣ layerA.js
|
|
94
|
+
┣ layerB.js
|
|
95
|
+
┣ layerD.js
|
|
96
|
+
┣ layerE/
|
|
97
|
+
┃ ┣ index.js
|
|
98
|
+
┃ ┣ entry
|
|
99
|
+
┃ ┣ layerEA.js
|
|
100
|
+
┃ ┗ .stratified.json
|
|
101
|
+
┗ .stratified.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
and the `.stratified.json` in `src/` is as follows:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
[
|
|
108
|
+
["layerA"],
|
|
109
|
+
[{ "name": "layerB", "barrier": true }],
|
|
110
|
+
[{ "name": "nodeModuleC", "nodeModule": true }],
|
|
111
|
+
["layerD", "layerE"]
|
|
112
|
+
]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
and the `.stratified.json` in `layerE/` is as follows:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
[["index", "entry"], ["layerEA"]]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Examples of **incorrect** code for this rule:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
// ./layerB.js
|
|
125
|
+
import { func } from "./layerA";
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
// ./layerA.js
|
|
130
|
+
import { func } from "./layerD";
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
// ./layerD.js
|
|
135
|
+
import { func } from "nodeModuleC";
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// ./layerD.js
|
|
140
|
+
import { func } from "layerE";
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
// ./layerB.js
|
|
145
|
+
import { func } from "layerE/layerEA";
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Examples of **correct** code for this rule:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
// ./layerA.js
|
|
152
|
+
import { func } from "./layerB";
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
// ./layerB.js
|
|
157
|
+
import { func } from "nodeModuleC";
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
// ./layerD.js
|
|
162
|
+
import { func } from "some-node-module";
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
// ./layerB.js
|
|
167
|
+
import { func } from "./layerD";
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
// ./layerB.js
|
|
172
|
+
import { func } from "./layerE";
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
// ./layerB.js
|
|
177
|
+
import { func } from "./layerE/entry";
|
|
178
|
+
```
|
package/lib/helpers/common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const p = require("path");
|
|
2
2
|
|
|
3
|
+
// @level 2
|
|
3
4
|
/**
|
|
4
5
|
* @param {string} from
|
|
5
6
|
* @param {string} to
|
|
@@ -10,24 +11,30 @@ const toRelative = (from, to) => {
|
|
|
10
11
|
return `${to}/`.startsWith(`${from}/`) ? `./${rel}` : rel;
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
// @level 2
|
|
13
15
|
/**
|
|
14
16
|
* @param {string} path
|
|
15
17
|
* @returns path to segments
|
|
16
18
|
*/
|
|
17
19
|
const toSegments = (path) => path.split("/");
|
|
18
20
|
|
|
21
|
+
// @level 2
|
|
19
22
|
/**
|
|
20
23
|
* @param {string[]} segments
|
|
21
24
|
* @returns segments to path
|
|
22
25
|
*/
|
|
23
26
|
const toPath = (segments) => segments.join("/");
|
|
24
27
|
|
|
28
|
+
// @level 2
|
|
25
29
|
const joinPath = p.join;
|
|
26
30
|
|
|
31
|
+
// @level 2
|
|
27
32
|
const resolvePath = p.resolve;
|
|
28
33
|
|
|
34
|
+
// @level 2
|
|
29
35
|
const parsePath = p.parse;
|
|
30
36
|
|
|
37
|
+
// @level 2
|
|
31
38
|
/**
|
|
32
39
|
* @template T
|
|
33
40
|
* @param {T[]} array
|
|
@@ -42,6 +49,7 @@ const findLastIndex = (array, callback) => {
|
|
|
42
49
|
}, -1);
|
|
43
50
|
};
|
|
44
51
|
|
|
52
|
+
// @level 2
|
|
45
53
|
/**
|
|
46
54
|
* @param {any[]} array1
|
|
47
55
|
* @param {any[]} array2
|
|
@@ -50,6 +58,7 @@ const equal = (array1, array2) => {
|
|
|
50
58
|
return array1.every((item, index) => item === array2[index]);
|
|
51
59
|
};
|
|
52
60
|
|
|
61
|
+
// @level 2
|
|
53
62
|
/**
|
|
54
63
|
* @template T
|
|
55
64
|
* @param {T[]} array
|
|
@@ -60,6 +69,18 @@ const readArray = (array, index) => {
|
|
|
60
69
|
return index >= 0 ? array[index] : array[array.length + index];
|
|
61
70
|
};
|
|
62
71
|
|
|
72
|
+
// @level 1
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} path
|
|
75
|
+
* @return {string[]}
|
|
76
|
+
*/
|
|
77
|
+
const reducedPaths = (path) => {
|
|
78
|
+
return toSegments(path).reduce((paths, _, index, segments) => {
|
|
79
|
+
paths.push(toPath(segments.slice(0, segments.length - index)));
|
|
80
|
+
return paths;
|
|
81
|
+
}, []);
|
|
82
|
+
};
|
|
83
|
+
|
|
63
84
|
module.exports = {
|
|
64
85
|
toRelative,
|
|
65
86
|
toSegments,
|
|
@@ -70,4 +91,5 @@ module.exports = {
|
|
|
70
91
|
findLastIndex,
|
|
71
92
|
equal,
|
|
72
93
|
readArray,
|
|
94
|
+
reducedPaths,
|
|
73
95
|
};
|
|
@@ -19,7 +19,7 @@ const FINISHED = "finished";
|
|
|
19
19
|
/**
|
|
20
20
|
*
|
|
21
21
|
* @param {string} cwd
|
|
22
|
-
* @param {
|
|
22
|
+
* @param {import("./3 layer").Options} options
|
|
23
23
|
* @returns
|
|
24
24
|
*/
|
|
25
25
|
const createRootDir = (cwd, options) => {
|
|
@@ -27,7 +27,7 @@ const createRootDir = (cwd, options) => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {import("./3 layer").Options} options
|
|
31
31
|
* @param {string} contextFileSource
|
|
32
32
|
*/
|
|
33
33
|
const parseFileSource = (options, contextFileSource) => {
|
|
@@ -50,10 +50,11 @@ const parseFileSource = (options, contextFileSource) => {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* @param {string} cwd
|
|
53
|
+
* @param {string[]} excludeImports
|
|
53
54
|
* @param {string} fileDir
|
|
54
55
|
* @param {{alias: string, path: string}[]} aliases
|
|
55
56
|
*/
|
|
56
|
-
const createModulePath = (cwd, fileDir, aliases) => {
|
|
57
|
+
const createModulePath = (cwd, excludeImports, fileDir, aliases) => {
|
|
57
58
|
const removeAlias = removeAliasFromModuleSource(cwd, fileDir, aliases);
|
|
58
59
|
/**
|
|
59
60
|
* @param {string} moduleSourceWithAlias
|
|
@@ -61,12 +62,18 @@ const createModulePath = (cwd, fileDir, aliases) => {
|
|
|
61
62
|
return (moduleSourceWithAlias) => {
|
|
62
63
|
const moduleSource = removeAlias(moduleSourceWithAlias);
|
|
63
64
|
const isNodeModule = moduleSource.startsWith(".") === false;
|
|
64
|
-
|
|
65
|
+
const modulePath = isNodeModule
|
|
66
|
+
? moduleSource
|
|
67
|
+
: resolvePath(fileDir, moduleSource);
|
|
68
|
+
const isModuleExcluded = Boolean(
|
|
69
|
+
excludeImports.find((pattern) => minimatch(modulePath, pattern))
|
|
70
|
+
);
|
|
71
|
+
return { modulePath, isModuleExcluded };
|
|
65
72
|
};
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
/**
|
|
69
|
-
* @param {
|
|
76
|
+
* @param {import("./3 layer").Options} options
|
|
70
77
|
* @param {string} fileDor
|
|
71
78
|
* @param {string} modulePath
|
|
72
79
|
*/
|
|
@@ -82,7 +89,7 @@ const isFileIndexOfModule = (options, fileDir, filePath) => (modulePath) => {
|
|
|
82
89
|
/**
|
|
83
90
|
* Report error about using `options.useLevelNumber`
|
|
84
91
|
* @param {import('eslint').Rule.RuleContext} context
|
|
85
|
-
* @param {
|
|
92
|
+
* @param {import("./3 layer").Options} options
|
|
86
93
|
* @param {string} rootDir
|
|
87
94
|
* @param {string} filePath
|
|
88
95
|
*/
|
|
@@ -1,56 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
joinPath,
|
|
6
|
-
toSegments,
|
|
7
|
-
readArray,
|
|
8
|
-
findLastIndex,
|
|
9
|
-
} = require("../common");
|
|
3
|
+
const { minimatch } = require("minimatch");
|
|
4
|
+
const { resolvePath, reducedPaths, parsePath } = require("../common");
|
|
10
5
|
const {
|
|
11
6
|
toStructure,
|
|
12
7
|
replaceAlias,
|
|
13
8
|
readRawStructure,
|
|
14
9
|
findLevel,
|
|
15
|
-
|
|
10
|
+
findLayerWithReducedPath,
|
|
11
|
+
readStructure,
|
|
12
|
+
hasBarrier,
|
|
13
|
+
isNotRegisteredNodeModule,
|
|
16
14
|
} = require("./2 layer");
|
|
17
15
|
|
|
16
|
+
const SUCCESS = "success";
|
|
17
|
+
const FAIL = "fail";
|
|
18
|
+
const BARRIER = "barrier";
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
|
-
* @
|
|
21
|
+
* @typedef {number | null | SUCCESS | FAIL | BARRIER} Level
|
|
20
22
|
*/
|
|
21
|
-
const createStructure = (fileDir) => {
|
|
22
|
-
const parentFileDir = joinPath(fileDir, "..");
|
|
23
|
-
|
|
24
|
-
const rawStructure = readRawStructure(fileDir);
|
|
25
|
-
const parentRawStructure = readRawStructure(parentFileDir);
|
|
26
|
-
|
|
27
|
-
const fileDirname = `${readArray(toSegments(fileDir), -1)}/`;
|
|
28
|
-
const theIndex = findLastIndex(parentRawStructure, (rawLayers) => {
|
|
29
|
-
return Boolean(
|
|
30
|
-
rawLayers.find((rawLayer) => {
|
|
31
|
-
if (typeof rawLayer === "string")
|
|
32
|
-
return `${rawLayer}/`.startsWith(fileDirname);
|
|
33
|
-
return `${rawLayer.name}/`.startsWith(fileDirname);
|
|
34
|
-
})
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const structure = toStructure(rawStructure, fileDir);
|
|
39
|
-
const parentStructure = toStructure(parentRawStructure, parentFileDir);
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* aliases: Record<string, string>,
|
|
27
|
+
* exclude: string[],
|
|
28
|
+
* include: string[],
|
|
29
|
+
* excludeImports: string[],
|
|
30
|
+
* }} Options
|
|
31
|
+
*/
|
|
44
32
|
|
|
45
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @param {Options} options
|
|
35
|
+
* @param {string} contextFileSource
|
|
36
|
+
*/
|
|
37
|
+
const parseFileSource = (options, contextFileSource) => {
|
|
38
|
+
const fileSource = resolvePath(contextFileSource);
|
|
39
|
+
const parsedFileSource = parsePath(fileSource);
|
|
40
|
+
const fileDir = resolvePath(parsedFileSource.dir);
|
|
41
|
+
const filePath = resolvePath(fileDir, parsedFileSource.name);
|
|
42
|
+
const isIncludedFile = Boolean(
|
|
43
|
+
options.include.find((pattern) => minimatch(fileSource, pattern))
|
|
44
|
+
);
|
|
45
|
+
const isExcludedFile = Boolean(
|
|
46
|
+
options.exclude.find((pattern) => minimatch(fileSource, pattern))
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
fileDir,
|
|
50
|
+
filePath,
|
|
51
|
+
isExcludedFile: !isIncludedFile || isExcludedFile,
|
|
52
|
+
};
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
56
|
* @param {string} cwd
|
|
57
|
+
* @param {string[]} excludeImports
|
|
50
58
|
* @param {string} fileDir
|
|
51
59
|
* @param {import('./2 layer').Aliases} aliases
|
|
52
60
|
*/
|
|
53
|
-
const createModulePath = (cwd, fileDir, aliases) => {
|
|
61
|
+
const createModulePath = (cwd, excludeImports, fileDir, aliases) => {
|
|
54
62
|
/**
|
|
55
63
|
* @param {string} moduleSourceWithAlias
|
|
56
64
|
*/
|
|
@@ -61,7 +69,28 @@ const createModulePath = (cwd, fileDir, aliases) => {
|
|
|
61
69
|
aliases
|
|
62
70
|
)(moduleSourceWithAlias);
|
|
63
71
|
const isNodeModule = moduleSource.startsWith(".") === false;
|
|
64
|
-
|
|
72
|
+
const modulePath = isNodeModule
|
|
73
|
+
? moduleSource
|
|
74
|
+
: resolvePath(fileDir, moduleSource);
|
|
75
|
+
const isModuleExcluded = Boolean(
|
|
76
|
+
excludeImports.find((pattern) => minimatch(modulePath, pattern))
|
|
77
|
+
);
|
|
78
|
+
return { modulePath, isModuleExcluded };
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {import('./2 layer').Structure} structure
|
|
84
|
+
* @param {number} fileLevel
|
|
85
|
+
*/
|
|
86
|
+
const findLevelInCurrent = (structure, fileLevel) => {
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} modulePath
|
|
89
|
+
*/
|
|
90
|
+
return (modulePath) => {
|
|
91
|
+
const level = findLevel(structure)(modulePath);
|
|
92
|
+
if (level === null) return null;
|
|
93
|
+
return hasBarrier(structure, fileLevel)(level) ? BARRIER : level;
|
|
65
94
|
};
|
|
66
95
|
};
|
|
67
96
|
|
|
@@ -70,17 +99,86 @@ const createModulePath = (cwd, fileDir, aliases) => {
|
|
|
70
99
|
*/
|
|
71
100
|
const findLevelInChild = (structure) => {
|
|
72
101
|
/**
|
|
73
|
-
* @param {string}
|
|
102
|
+
* @param {string} modulePath
|
|
74
103
|
*/
|
|
75
|
-
return (
|
|
76
|
-
const layer =
|
|
104
|
+
return (modulePath) => {
|
|
105
|
+
const layer = findLayerWithReducedPath(structure)(modulePath);
|
|
77
106
|
if (!layer) return null;
|
|
78
107
|
const fileDir = layer.name;
|
|
79
108
|
const rawChildStructure = readRawStructure(fileDir);
|
|
80
109
|
const childStructure = toStructure(rawChildStructure, fileDir);
|
|
81
|
-
const childLevel = findLevel(childStructure)(
|
|
110
|
+
const childLevel = findLevel(childStructure)(modulePath);
|
|
82
111
|
return childLevel !== null ? findLevel(structure)(fileDir) : null;
|
|
83
112
|
};
|
|
84
113
|
};
|
|
85
114
|
|
|
86
|
-
|
|
115
|
+
/**
|
|
116
|
+
* @param {string} cwd
|
|
117
|
+
* @param {string} fileDir
|
|
118
|
+
* @param {number} fileLevel
|
|
119
|
+
*/
|
|
120
|
+
const findLevelInParent = (cwd, fileDir, fileLevel) => {
|
|
121
|
+
const originalFileDir = fileDir;
|
|
122
|
+
const originalFileLevel = fileLevel;
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} modulePath
|
|
125
|
+
*/
|
|
126
|
+
return (modulePath) => {
|
|
127
|
+
const parentDir = resolvePath(originalFileDir, "..");
|
|
128
|
+
const parentDirs = reducedPaths(parentDir);
|
|
129
|
+
/**
|
|
130
|
+
* @param {[Level, string]} param
|
|
131
|
+
* @param {string} dir
|
|
132
|
+
*/
|
|
133
|
+
const searchLevel = ([level, filePath], dir) => {
|
|
134
|
+
/**
|
|
135
|
+
* @param {Level} lv
|
|
136
|
+
* @return {[lv: Level, dir: string]}
|
|
137
|
+
*/
|
|
138
|
+
const result = (lv) => [lv, dir];
|
|
139
|
+
|
|
140
|
+
if (level !== null) {
|
|
141
|
+
return result(level);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const structure = readStructure(dir);
|
|
145
|
+
if (structure.length === 0) {
|
|
146
|
+
return result(FAIL);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fileLevel = findLevel(structure)(filePath);
|
|
150
|
+
const moduleLevel = findLevel(structure)(modulePath);
|
|
151
|
+
|
|
152
|
+
if (hasBarrier(structure, fileLevel)(moduleLevel)) {
|
|
153
|
+
return result(BARRIER);
|
|
154
|
+
} else if (fileLevel === null) {
|
|
155
|
+
return result(FAIL);
|
|
156
|
+
} else if (moduleLevel === null) {
|
|
157
|
+
return result(null);
|
|
158
|
+
} else {
|
|
159
|
+
return result(originalFileLevel + moduleLevel - fileLevel);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const [level] = parentDirs.reduce(searchLevel, [null, originalFileDir]);
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
(level === null || level == FAIL) &&
|
|
167
|
+
isNotRegisteredNodeModule(cwd)(modulePath)
|
|
168
|
+
) {
|
|
169
|
+
return SUCCESS;
|
|
170
|
+
}
|
|
171
|
+
return level === FAIL ? null : level;
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
parseFileSource,
|
|
177
|
+
createModulePath,
|
|
178
|
+
findLevelInCurrent,
|
|
179
|
+
findLevelInChild,
|
|
180
|
+
findLevelInParent,
|
|
181
|
+
FAIL,
|
|
182
|
+
SUCCESS,
|
|
183
|
+
BARRIER,
|
|
184
|
+
};
|