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.
@@ -142,7 +142,7 @@ The default is as follows:
142
142
  }
143
143
  ```
144
144
 
145
- Imported modules can be excluded from the rule (`lower-level-imports`) using the `excludeImports` option:
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 can be excluded from the rule (`stratified-imports`) using the `excludeImports` option:
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 | null})} Levels
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 null;
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
- if (calleeLevel !== null) {
61
- const caller = traceAncestor(sourceCode, node);
62
- const callerLevel = deriveLevel(sourceCode, caller);
63
- if (callerLevel !== null && callerLevel < calleeLevel) return true;
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 declaration = (token.type === 'ExportNamedDeclaration' || token.type === 'ExportDefaultDeclaration') && Boolean(token.declaration) ? token.declaration : token
144
- const isFuncDeclaration = declaration.type === "FunctionDeclaration";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-stratified-design",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "description": "ESlint rules for stratified design",
5
5
  "keywords": [
6
6
  "eslint",
@@ -220,7 +220,7 @@ ruleTester.run("lower-level-imports", rule, {
220
220
  ],
221
221
  },
222
222
  {
223
- code: "import { func } from '@/other/file.js'",
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: [{ structure, root: "./src", aliases: { "@/": "./src/" } }],
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.js'",
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.js", file: "./other/index" },
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: "// @level 2\nexport function func2(){};\n// @level 1\nfunction func1(){ func2(); }",
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: "// @level 2\nfunction func2(){};\nfunction func1(){ func2(); }",
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: "// @level 2\nexport function func2(){};\nfunction func1(){ func2(); }",
287
+ code: "const func = () => 'a'; const obj = { a: func() } ",
233
288
  filename: "./src/foo.js",
234
- errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
289
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "func" } }],
235
290
  },
236
-
237
291
  {
238
- code: "function func2(){};\n// @level 1\nfunction func1(){ func2(); }",
292
+ code: "const func = () => 'a'; const obj = { func: () => { func() } } ",
239
293
  filename: "./src/foo.js",
240
- errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
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
  });