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.
@@ -9,12 +9,12 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
11
  - name: Checkout repository
12
- uses: actions/checkout@v3
12
+ uses: actions/checkout@v4
13
13
 
14
14
  - name: Set up Node.js
15
- uses: actions/setup-node@v3
15
+ uses: actions/setup-node@v4
16
16
  with:
17
- node-version: 16
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
- layer3/
152
- ┣ entry.js
153
- ┣ 1 layer.js
154
- ┗ 2 layer.js
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
+ ```
@@ -236,7 +236,7 @@ const reportHasProperLevel = (
236
236
  return FINISHED;
237
237
  }
238
238
 
239
- if (hasBarrier(moduleLevel)) {
239
+ if (hasBarrier(modulePath, moduleLevel)) {
240
240
  report("barrier");
241
241
  return FINISHED;
242
242
  }
@@ -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 there is an interface between file layer and module layer
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) ? BARRIER : 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
- interface: { type: "boolean" },
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(
@@ -124,6 +124,8 @@ module.exports = {
124
124
  * @returns
125
125
  */
126
126
  const report = (node) => {
127
+ if (node.importKind === "type") return;
128
+
127
129
  if (!isStructureValid) {
128
130
  reportError(node, "invalid-json");
129
131
  return;
@@ -8,5 +8,6 @@
8
8
  ["layerG", "layerH"],
9
9
  ["layerI"],
10
10
  ["layerJ"],
11
- ["layerK"]
11
+ ["layerK"],
12
+ [{ "name": "layerL", "language": true }]
12
13
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-stratified-design",
3
- "version": "0.9.2",
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
- parserOptions: { ecmaVersion: 2022, sourceType: "module" },
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
- parserOptions: { ecmaVersion: 2022, sourceType: "module" },
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
  {