eslint-plugin-stratified-design 0.9.2 → 0.11.0
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/.github/workflows/release.yml +3 -3
- package/docs/rules/lower-level-imports.md +30 -4
- package/docs/rules/no-disallowed-imports.md +38 -2
- package/docs/rules/stratified-imports.md +11 -1
- package/lib/helpers/lowerLevelImports/1 layer.js +1 -1
- package/lib/helpers/lowerLevelImports/3 layer.js +19 -2
- package/lib/helpers/stratifiedImports/1 layer.js +4 -2
- package/lib/helpers/stratifiedImports/2 layer.js +24 -1
- package/lib/rules/lower-level-imports.js +5 -2
- package/lib/rules/stratified-imports.js +2 -0
- package/mocked/stratified-imports/.stratified.json +2 -1
- package/package.json +3 -2
- package/tests/lib/rules/lower-level-imports.js +17 -2
- package/tests/lib/rules/stratified-imports.js +17 -2
|
@@ -9,12 +9,12 @@ jobs:
|
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- name: Checkout repository
|
|
12
|
-
uses: actions/checkout@
|
|
12
|
+
uses: actions/checkout@v4
|
|
13
13
|
|
|
14
14
|
- name: Set up Node.js
|
|
15
|
-
uses: actions/setup-node@
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
16
|
with:
|
|
17
|
-
node-version:
|
|
17
|
+
node-version: 20
|
|
18
18
|
registry-url: https://registry.npmjs.org/
|
|
19
19
|
|
|
20
20
|
- name: Update package version
|
|
@@ -40,6 +40,23 @@ To designate a layer as an abstract barrier, set `barrier` to `true`:
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
A language layer can 'pass' any abstract barrier:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"stratified-design/lower-level-imports": [
|
|
48
|
+
"error",
|
|
49
|
+
{
|
|
50
|
+
"structure": [
|
|
51
|
+
"layer1",
|
|
52
|
+
{ "name": "layer2", "barrier": true },
|
|
53
|
+
{ "name": "layer3", "language": true }
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
43
60
|
For the 'abstract barrier,' refer to "[Grokking Simplicity](https://grokkingsimplicity.com)."
|
|
44
61
|
|
|
45
62
|
To locate a node module in the structure, set `nodeModule` to `true`:
|
|
@@ -148,10 +165,11 @@ src/
|
|
|
148
165
|
┃ ┣ file.js
|
|
149
166
|
┃ ┣ otherFile.js
|
|
150
167
|
┃ ┗ subFolder/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
┃ layer3/
|
|
169
|
+
┃ ┣ entry.js
|
|
170
|
+
┃ ┣ 1 layer.js
|
|
171
|
+
┃ ┗ 2 layer.js
|
|
172
|
+
┗ layer4.js
|
|
155
173
|
```
|
|
156
174
|
|
|
157
175
|
Examples of **incorrect** code for this rule:
|
|
@@ -242,3 +260,11 @@ import { func } from "../layer3/entry";
|
|
|
242
260
|
// ./src/layer2/index.js
|
|
243
261
|
import { func } from "./file";
|
|
244
262
|
```
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
/* "lower-level-imports": ["error", {
|
|
266
|
+
"structure": ["layer1", { name: "layer2", barrier: true }, "layer3", { name: "layer4", language: true }]
|
|
267
|
+
}] */
|
|
268
|
+
// ./src/layer1.js
|
|
269
|
+
import { func } from "./layer4";
|
|
270
|
+
```
|
|
@@ -57,7 +57,7 @@ Examples of **incorrect** code for this rule:
|
|
|
57
57
|
// {
|
|
58
58
|
// "imports": [
|
|
59
59
|
// {
|
|
60
|
-
// "import": { "member": ["foo"], "from": "src/fileA" },
|
|
60
|
+
// "import": { "member": ["foo", "default", "*"], "from": "src/fileA" },
|
|
61
61
|
// "allow": ["src/**/fileB.js"],
|
|
62
62
|
// "disallow": ["src/**/fileC.*"]
|
|
63
63
|
// }
|
|
@@ -69,6 +69,42 @@ Examples of **incorrect** code for this rule:
|
|
|
69
69
|
import { foo } from "./fileA";
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
```js
|
|
73
|
+
// "stratified-design/no-disallowed-imports": [
|
|
74
|
+
// "error",
|
|
75
|
+
// {
|
|
76
|
+
// "imports": [
|
|
77
|
+
// {
|
|
78
|
+
// "import": { "member": ["foo", "default", "*"], "from": "src/fileA" },
|
|
79
|
+
// "allow": ["src/**/fileB.js"],
|
|
80
|
+
// "disallow": ["src/**/fileC.*"]
|
|
81
|
+
// }
|
|
82
|
+
// ]
|
|
83
|
+
// }
|
|
84
|
+
// ]
|
|
85
|
+
|
|
86
|
+
// ./src/fileC.js
|
|
87
|
+
import foo from "./fileA";
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
// "stratified-design/no-disallowed-imports": [
|
|
92
|
+
// "error",
|
|
93
|
+
// {
|
|
94
|
+
// "imports": [
|
|
95
|
+
// {
|
|
96
|
+
// "import": { "member": ["foo", "default", "*"], "from": "src/fileA" },
|
|
97
|
+
// "allow": ["src/**/fileB.js"],
|
|
98
|
+
// "disallow": ["src/**/fileC.*"]
|
|
99
|
+
// }
|
|
100
|
+
// ]
|
|
101
|
+
// }
|
|
102
|
+
// ]
|
|
103
|
+
|
|
104
|
+
// ./src/fileC.js
|
|
105
|
+
import * as foo from "./fileA";
|
|
106
|
+
```
|
|
107
|
+
|
|
72
108
|
Examples of **correct** code for this rule:
|
|
73
109
|
|
|
74
110
|
```js
|
|
@@ -77,7 +113,7 @@ Examples of **correct** code for this rule:
|
|
|
77
113
|
// {
|
|
78
114
|
// "imports": [
|
|
79
115
|
// {
|
|
80
|
-
// "import": { "member": ["foo"], "from": "src/fileA" },
|
|
116
|
+
// "import": { "member": ["foo", "default", "*"], "from": "src/fileA" },
|
|
81
117
|
// "allow": ["src/**/fileB.js"],
|
|
82
118
|
// "disallow": ["src/**/fileC.*"]
|
|
83
119
|
// }
|
|
@@ -10,6 +10,7 @@ This rule enforces the requirement for importing lower-level modules. The hierar
|
|
|
10
10
|
[{ "name": "layerB", "barrier": true }],
|
|
11
11
|
[{ "name": "nodeModuleC", "nodeModule": true }],
|
|
12
12
|
["layerD", "layerE"]
|
|
13
|
+
[{ "name": "layerF", "language": true }]
|
|
13
14
|
]
|
|
14
15
|
```
|
|
15
16
|
|
|
@@ -24,6 +25,7 @@ And consider that the folder structure is as follows:
|
|
|
24
25
|
┃ ┣ entry
|
|
25
26
|
┃ ┣ layerEA
|
|
26
27
|
┃ ┗ .stratified.json
|
|
28
|
+
┣ layerF
|
|
27
29
|
┗ .stratified.json
|
|
28
30
|
```
|
|
29
31
|
|
|
@@ -33,6 +35,7 @@ The above JSON file indicates the following:
|
|
|
33
35
|
- 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
36
|
- `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
37
|
- The `layerD` file/folder and the `layerE` file/folder are at the same level and represent the lowest level layers.
|
|
38
|
+
- The `layerF` file/folder is a language layer and 'pass' any abstract barrier.
|
|
36
39
|
|
|
37
40
|
Consider that the `.stratified.json` in the `layerE` folder is as follows:
|
|
38
41
|
|
|
@@ -109,6 +112,7 @@ src/
|
|
|
109
112
|
┃ ┣ entry
|
|
110
113
|
┃ ┣ layerEA.js
|
|
111
114
|
┃ ┗ .stratified.json
|
|
115
|
+
┣ layerF
|
|
112
116
|
┗ .stratified.json
|
|
113
117
|
```
|
|
114
118
|
|
|
@@ -119,7 +123,8 @@ and the `.stratified.json` in `src/` is as follows:
|
|
|
119
123
|
["layerA"],
|
|
120
124
|
[{ "name": "layerB", "barrier": true }],
|
|
121
125
|
[{ "name": "nodeModuleC", "nodeModule": true }],
|
|
122
|
-
["layerD", "layerE"]
|
|
126
|
+
["layerD", "layerE"],
|
|
127
|
+
[{ "name": "layerF", "language": true }]
|
|
123
128
|
]
|
|
124
129
|
```
|
|
125
130
|
|
|
@@ -187,3 +192,8 @@ import { func } from "./layerE";
|
|
|
187
192
|
// ./layerB.js
|
|
188
193
|
import { func } from "./layerE/entry";
|
|
189
194
|
```
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
// ./layerA.js
|
|
198
|
+
import { func } from "./layerF";
|
|
199
|
+
```
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
* @typedef {{
|
|
25
25
|
* name: string;
|
|
26
26
|
* barrier?: boolean | undefined;
|
|
27
|
+
* language?: boolean | undefined;
|
|
27
28
|
* interface?: boolean | undefined;
|
|
28
29
|
* nodeModule?: boolean | undefined;
|
|
29
30
|
* isNodeModule?: boolean | undefined;
|
|
@@ -107,7 +108,22 @@ const findLevel = (structure) => {
|
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
|
-
* Check if
|
|
111
|
+
* Check if a layer is language layer
|
|
112
|
+
* @param {Structure} structure
|
|
113
|
+
*/
|
|
114
|
+
const isLanguage = (structure) => {
|
|
115
|
+
/**
|
|
116
|
+
* @param {string} path
|
|
117
|
+
*/
|
|
118
|
+
return (path) => {
|
|
119
|
+
return Boolean(
|
|
120
|
+
structure.find((layer) => layer.name === path && layer.language === true)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if there is an barrier between file layer and module layer
|
|
111
127
|
* @param {Structure} structure
|
|
112
128
|
* @param {number} fileLevel
|
|
113
129
|
*/
|
|
@@ -115,7 +131,8 @@ const hasBarrier = (structure, fileLevel) => {
|
|
|
115
131
|
/**
|
|
116
132
|
* @param {number} moduleLevel
|
|
117
133
|
*/
|
|
118
|
-
return (moduleLevel) => {
|
|
134
|
+
return (modulePath, moduleLevel) => {
|
|
135
|
+
if (isLanguage(structure)(modulePath)) return false;
|
|
119
136
|
const layerBarrier = structure
|
|
120
137
|
.slice(fileLevel + 1, moduleLevel)
|
|
121
138
|
.find((layer) => layer.barrier || layer.interface);
|
|
@@ -106,7 +106,9 @@ const findLevelInCurrent = (structure, fileLevel) => {
|
|
|
106
106
|
return (modulePath) => {
|
|
107
107
|
const level = findLevel(structure)(modulePath);
|
|
108
108
|
if (level === null) return null;
|
|
109
|
-
return hasBarrier(structure, fileLevel)(level)
|
|
109
|
+
return hasBarrier(structure, fileLevel)(modulePath, level)
|
|
110
|
+
? BARRIER
|
|
111
|
+
: level;
|
|
110
112
|
};
|
|
111
113
|
};
|
|
112
114
|
|
|
@@ -173,7 +175,7 @@ const findLevelInParent = (cwd, fileDir, fileLevel) => {
|
|
|
173
175
|
|
|
174
176
|
if (moduleLevel === null) {
|
|
175
177
|
return result(null);
|
|
176
|
-
} else if (hasBarrier(structure, fileLevel)(moduleLevel)) {
|
|
178
|
+
} else if (hasBarrier(structure, fileLevel)(modulePath, moduleLevel)) {
|
|
177
179
|
return result(BARRIER);
|
|
178
180
|
} else {
|
|
179
181
|
return result(originalFileLevel + moduleLevel - fileLevel);
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
* name: string;
|
|
15
15
|
* barrier?: boolean;
|
|
16
16
|
* nodeModule?: boolean;
|
|
17
|
+
* language?: boolean;
|
|
17
18
|
* }} Layer
|
|
18
19
|
*/
|
|
19
20
|
|
|
@@ -29,6 +30,24 @@ const {
|
|
|
29
30
|
* @typedef {RawLayer[][]} RawStructure
|
|
30
31
|
*/
|
|
31
32
|
|
|
33
|
+
// @level 3
|
|
34
|
+
/**
|
|
35
|
+
* @param {Structure} structure
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
const isLanguage = (structure) => {
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} path
|
|
41
|
+
*/
|
|
42
|
+
return (path) => {
|
|
43
|
+
return Boolean(
|
|
44
|
+
structure.find((layers) =>
|
|
45
|
+
layers.find((layer) => layer.name === path && layer.language === true)
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
32
51
|
// @level 2
|
|
33
52
|
/**
|
|
34
53
|
* @param {string} fileDir
|
|
@@ -64,6 +83,8 @@ const validateRawStructure = (rawStructure) => {
|
|
|
64
83
|
if (typeof value !== "boolean") return false;
|
|
65
84
|
} else if (key === "barrier") {
|
|
66
85
|
if (typeof value !== "boolean") return false;
|
|
86
|
+
} else if (key === "language") {
|
|
87
|
+
if (typeof value !== "boolean") return false;
|
|
67
88
|
} else {
|
|
68
89
|
return false;
|
|
69
90
|
}
|
|
@@ -148,9 +169,11 @@ const findLayerWithReducedPath = (structure) => {
|
|
|
148
169
|
*/
|
|
149
170
|
const hasBarrier = (structure, fileLevel) => {
|
|
150
171
|
/**
|
|
172
|
+
* @param {string} modulePath
|
|
151
173
|
* @param {number} moduleLevel
|
|
152
174
|
*/
|
|
153
|
-
return (moduleLevel) => {
|
|
175
|
+
return (modulePath, moduleLevel) => {
|
|
176
|
+
if (isLanguage(structure)(modulePath)) return false;
|
|
154
177
|
const layerBarrier = structure
|
|
155
178
|
.slice(fileLevel + 1, moduleLevel)
|
|
156
179
|
.find((layers) => layers.find((layer) => layer.barrier));
|
|
@@ -40,9 +40,10 @@ const layerSchema = {
|
|
|
40
40
|
pattern: layerNamePattern,
|
|
41
41
|
},
|
|
42
42
|
barrier: { type: "boolean" },
|
|
43
|
-
|
|
43
|
+
language: { type: "boolean" },
|
|
44
|
+
interface: { type: "boolean" }, // deprecated
|
|
44
45
|
nodeModule: { type: "boolean" },
|
|
45
|
-
isNodeModule: { type: "boolean" },
|
|
46
|
+
isNodeModule: { type: "boolean" }, // deprecated
|
|
46
47
|
},
|
|
47
48
|
required: ["name"],
|
|
48
49
|
additionalProperties: false,
|
|
@@ -166,6 +167,8 @@ module.exports = {
|
|
|
166
167
|
const isFileIndex = isFileIndexOfModule(options, fileDir, filePath);
|
|
167
168
|
|
|
168
169
|
const report = (node) => {
|
|
170
|
+
if (node.importKind === "type") return;
|
|
171
|
+
|
|
169
172
|
if (isExcludedFile) return;
|
|
170
173
|
|
|
171
174
|
const { modulePath, isModuleExcluded } = createModulePath(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-stratified-design",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "ESlint rules for stratified design",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"author": "Hodoug Joung",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "https://github.com/anisotropy/eslint-plugin-stratified-design"
|
|
15
|
+
"url": "git+https://github.com/anisotropy/eslint-plugin-stratified-design.git"
|
|
16
16
|
},
|
|
17
17
|
"homepage": "https://github.com/anisotropy/eslint-plugin-stratified-design",
|
|
18
18
|
"main": "./lib/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"requireindex": "^1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@typescript-eslint/parser": "^7.1.0",
|
|
30
31
|
"eslint": "^8.19.0",
|
|
31
32
|
"eslint-plugin-eslint-plugin": "^5.0.0",
|
|
32
33
|
"eslint-plugin-node": "^11.1.0",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
//------------------------------------------------------------------------------
|
|
10
10
|
|
|
11
11
|
const rule = require("../../../lib/rules/lower-level-imports"),
|
|
12
|
-
RuleTester = require("eslint").RuleTester
|
|
12
|
+
RuleTester = require("eslint").RuleTester,
|
|
13
|
+
path = require("path");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Tests
|
|
@@ -24,10 +25,14 @@ const structure = [
|
|
|
24
25
|
"layer3/subLayer1",
|
|
25
26
|
"layer3/subLayer2",
|
|
26
27
|
"layer3/subLayer3",
|
|
28
|
+
{ name: "layer4", language: true },
|
|
27
29
|
];
|
|
28
30
|
|
|
29
31
|
const ruleTester = new RuleTester({
|
|
30
|
-
|
|
32
|
+
parser: path.resolve(
|
|
33
|
+
"./node_modules/@typescript-eslint/parser/dist/parser.js"
|
|
34
|
+
),
|
|
35
|
+
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
ruleTester.run("lower-level-imports", rule, {
|
|
@@ -226,6 +231,16 @@ ruleTester.run("lower-level-imports", rule, {
|
|
|
226
231
|
},
|
|
227
232
|
],
|
|
228
233
|
},
|
|
234
|
+
{
|
|
235
|
+
code: "import { func } from '@/layer4'",
|
|
236
|
+
filename: "./src/layer1/subLayer2.js",
|
|
237
|
+
options: [{ structure, root: "./src", aliases: { "@/": "./src/" } }],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
code: "import type { SomeType } from '@/layer2/subLayer2'",
|
|
241
|
+
filename: "./src/layer1/subLayer2.js",
|
|
242
|
+
options: [{ structure, root: "./src", aliases: { "@/": "./src/" } }],
|
|
243
|
+
},
|
|
229
244
|
],
|
|
230
245
|
invalid: [
|
|
231
246
|
{
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const rule = require("../../../lib/rules/stratified-imports");
|
|
12
12
|
const RuleTester = require("eslint").RuleTester;
|
|
13
|
+
const path = require("path");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Tests
|
|
@@ -27,7 +28,8 @@ const RuleTester = require("eslint").RuleTester;
|
|
|
27
28
|
["layerG", "layerH"],
|
|
28
29
|
["layerI"],
|
|
29
30
|
["layerJ"],
|
|
30
|
-
["layerK"]
|
|
31
|
+
["layerK"],
|
|
32
|
+
[{ "name": "layerL", "language": true }]
|
|
31
33
|
]
|
|
32
34
|
|
|
33
35
|
// layerB/.stratified.json
|
|
@@ -56,7 +58,10 @@ const RuleTester = require("eslint").RuleTester;
|
|
|
56
58
|
*/
|
|
57
59
|
|
|
58
60
|
const ruleTester = new RuleTester({
|
|
59
|
-
|
|
61
|
+
parser: path.resolve(
|
|
62
|
+
"./node_modules/@typescript-eslint/parser/dist/parser.js"
|
|
63
|
+
),
|
|
64
|
+
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
|
60
65
|
});
|
|
61
66
|
|
|
62
67
|
ruleTester.run("stratified-imports", rule, {
|
|
@@ -156,6 +161,16 @@ ruleTester.run("stratified-imports", rule, {
|
|
|
156
161
|
filename: "./mocked/stratified-imports/layerD/layerDB/layerDBA.js",
|
|
157
162
|
options: [{ aliases: { "@/": "./mocked/stratified-imports/" } }],
|
|
158
163
|
},
|
|
164
|
+
{
|
|
165
|
+
code: "import { func } from '@/layerL'",
|
|
166
|
+
filename: "./mocked/stratified-imports/layerA.js",
|
|
167
|
+
options: [{ aliases: { "@/": "./mocked/stratified-imports/" } }],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
code: "import type { SomeType } from './layerA'",
|
|
171
|
+
filename: "./mocked/stratified-imports/layerB.js",
|
|
172
|
+
options: [],
|
|
173
|
+
},
|
|
159
174
|
],
|
|
160
175
|
invalid: [
|
|
161
176
|
{
|