eslint-plugin-stratified-design 0.6.1-beta → 0.7.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.
@@ -66,6 +66,12 @@ If you want to register the level of a layer by 'number,' set the option `useLev
66
66
 
67
67
  The options `structure` and `useLevelNumber` can be used together.
68
68
 
69
+ An `index.xxx` file can be the highest level layer of sibling files when the option `isIndexHighest` is set to `true`:
70
+
71
+ ```json
72
+ "lower-level-imports": ["error", { "isIndexHighest": true }]
73
+ ```
74
+
69
75
  You can register the files to apply the rule (`lower-level-imports`) using the `include` and `exclude` options:
70
76
 
71
77
  ```json
@@ -89,6 +95,7 @@ If a file structure is as follows:
89
95
  src/
90
96
  ┣ layer1.js
91
97
  ┣ layer2/
98
+ ┃ ┣ index.js
92
99
  ┃ ┣ file.js
93
100
  ┃ ┣ otherFile.js
94
101
  ┃ ┗ subFolder/
@@ -180,3 +187,9 @@ import { func } from "./1 layer";
180
187
  // ./src/layer.js
181
188
  import { func } from "../layer3/entry";
182
189
  ```
190
+
191
+ ```js
192
+ /* "lower-level-imports": ["error", { "isIndexHighest": true }] */
193
+ // ./src/layer2/index.js
194
+ import { func } from "./file";
195
+ ```
@@ -36,6 +36,18 @@ function func3(...) {
36
36
  }
37
37
  ```
38
38
 
39
+ ```js
40
+ // @level 1
41
+ const funcA = (...) => { ... }
42
+
43
+ // @level 2
44
+ const funcB = (...) => {
45
+ ...
46
+ funcA(...)
47
+ ...
48
+ }
49
+ ```
50
+
39
51
  Examples of **correct** code for this rule:
40
52
 
41
53
  ```js
@@ -53,3 +65,15 @@ function func1(...) {
53
65
  func2(...);
54
66
  }
55
67
  ```
68
+
69
+ ```js
70
+ // @level 2
71
+ const funcA = (...) => { ... }
72
+
73
+ // @level 1
74
+ const funcB = (...) => {
75
+ ...
76
+ funcA(...)
77
+ ...
78
+ }
79
+ ```
@@ -11,6 +11,43 @@ const path = require("path");
11
11
  // Rule Definition
12
12
  //------------------------------------------------------------------------------
13
13
 
14
+ /**
15
+ * @typedef {import('eslint').Rule.Node} Node
16
+ * @typedef {import('eslint').AST.Token} Token
17
+ * @typedef {import('eslint').SourceCode} SourceCode
18
+ */
19
+
20
+ /**
21
+ * @param {SourceCode} sourceCode
22
+ * @param {Node | Token} nodeOrToken
23
+ *
24
+ */
25
+ const deriveLevel = (sourceCode, nodeOrToken) => {
26
+ const comments = sourceCode.getCommentsBefore(nodeOrToken);
27
+ for (const { value } of comments) {
28
+ const levelInStr = value.replace(/^[^]*@level\s+?([0-9]+)[^0-9]*$/, "$1");
29
+ const levelInNum = Number(levelInStr);
30
+ if (levelInStr && !Number.isNaN(levelInNum)) {
31
+ return levelInNum;
32
+ }
33
+ }
34
+ return null;
35
+ };
36
+
37
+ /**
38
+ *
39
+ * @param {Node} node
40
+ */
41
+ const traceAncestor = (node) => {
42
+ let parent = node;
43
+ let nextParent = node.parent;
44
+ while (nextParent.type !== "Program") {
45
+ parent = parent.parent;
46
+ nextParent = parent.parent;
47
+ }
48
+ return parent;
49
+ };
50
+
14
51
  /** @type {import('eslint').Rule.RuleModule} */
15
52
  module.exports = {
16
53
  meta: {
@@ -36,10 +73,9 @@ module.exports = {
36
73
  additionalItems: false,
37
74
  },
38
75
  messages: {
39
- "no-same-level-funcs": "Disallow calling {{func}} in the same file.",
76
+ "no-same-level-funcs": "{{func}} is NOT lower level",
40
77
  },
41
78
  },
42
-
43
79
  create(context) {
44
80
  const options = {
45
81
  exclude: ["**/*.{test,spec}.{js,ts,jsx,tsx}"],
@@ -59,31 +95,47 @@ module.exports = {
59
95
 
60
96
  if (isExcludedFile) return {};
61
97
 
62
- let funcNames;
98
+ /**
99
+ * @type {({[name: string]: number | null})}
100
+ */
101
+ const levels = {};
102
+
103
+ const sourceCode = context.getSourceCode();
63
104
 
64
105
  return {
65
106
  Program(node) {
66
- funcNames = node.body.reduce((names, { type, id, declarations }) => {
67
- if (type === "FunctionDeclaration") {
68
- names.push(id.name);
69
- } else if (
70
- type === "VariableDeclaration" &&
71
- (declarations[0].init.type === "ArrowFunctionExpression" ||
72
- declarations[0].init.type === "FunctionExpression")
73
- ) {
74
- names.push(declarations[0].id.name);
107
+ node.body.forEach((token) => {
108
+ const isFuncDeclaration = token.type === "FunctionDeclaration";
109
+ const isVarDeclaration =
110
+ token.type === "VariableDeclaration" &&
111
+ [
112
+ "ArrowFunctionExpression",
113
+ "FunctionExpression",
114
+ "CallExpression",
115
+ ].includes(token.declarations[0].init.type);
116
+
117
+ if (isFuncDeclaration || isVarDeclaration) {
118
+ const level = deriveLevel(sourceCode, token);
119
+ const name = isFuncDeclaration
120
+ ? token.id.name
121
+ : token.declarations[0].id.name;
122
+ levels[name] = level;
75
123
  }
76
- return names;
77
- }, []);
124
+ });
78
125
  },
79
126
  CallExpression(node) {
80
- if (funcNames.includes(node.callee.name)) {
81
- context.report({
82
- node,
83
- messageId: "no-same-level-funcs",
84
- data: { func: node.callee.name },
85
- });
127
+ const calleeLevel = levels[node.callee.name];
128
+ if (calleeLevel === undefined) return;
129
+ if (calleeLevel !== null) {
130
+ const ancestor = traceAncestor(node);
131
+ const ancestorLevel = deriveLevel(sourceCode, ancestor);
132
+ if (ancestorLevel !== null && ancestorLevel < calleeLevel) return;
86
133
  }
134
+ context.report({
135
+ node,
136
+ messageId: "no-same-level-funcs",
137
+ data: { func: node.callee.name },
138
+ });
87
139
  },
88
140
  };
89
141
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-stratified-design",
3
- "version": "0.6.1-beta",
3
+ "version": "0.7.0",
4
4
  "description": "ESlint rules for stratified design",
5
5
  "keywords": [
6
6
  "eslint",
@@ -44,6 +44,30 @@ ruleTester.run("no-same-level-funcs", rule, {
44
44
  filename: "./src/foo.js",
45
45
  options: [{ include: ["**/src/**/*.*"], exclude: ["**/foo.js"] }],
46
46
  },
47
+ {
48
+ code: "// @level 2\nfunction func2(){};\n// @level 1\nfunction func1(){ func2(); }",
49
+ filename: "./src/foo.js",
50
+ },
51
+ {
52
+ code: "// @level 2\nconst func2 = () => {};\n// @level 1\nfunction func1(){ func2(); }",
53
+ filename: "./src/foo.js",
54
+ },
55
+ {
56
+ code: "// @level 2\nconst func2 = () => {};\n// @level 1\nconst func1 = () => func2();",
57
+ filename: "./src/foo.js",
58
+ },
59
+ {
60
+ code: "/*@level 2*/\nconst func2 = () => {};\n/*@level 1*/\nconst func1 = () => func2();",
61
+ filename: "./src/foo.js",
62
+ },
63
+ {
64
+ code: "// @level 2 something\nconst func2 = () => {};\n// @level 1 something\nconst func1 = () => func2();",
65
+ filename: "./src/foo.js",
66
+ },
67
+ {
68
+ code: "/*\n@level 2\nsomething\n*/\nconst func2 = () => {};\n/*something\n@level 1\n*/\nconst func1 = () => func2();",
69
+ filename: "./src/foo.js",
70
+ },
47
71
  ],
48
72
  invalid: [
49
73
  {
@@ -71,5 +95,35 @@ ruleTester.run("no-same-level-funcs", rule, {
71
95
  filename: "./src/foo.js",
72
96
  errors: [{ messageId: "no-same-level-funcs", data: { func: "func1" } }],
73
97
  },
98
+ {
99
+ code: "const fn = () => 1; const value = fn()",
100
+ filename: "./src/foo.js",
101
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "fn" } }],
102
+ },
103
+ {
104
+ code: "const fnByHof = hof(() => 1); const value = fnByHof()",
105
+ filename: "./src/foo.js",
106
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "fnByHof" } }],
107
+ },
108
+ {
109
+ code: "const fnByHof = hof(() => 1); const fn = fnByHof(() => 1)",
110
+ filename: "./src/foo.js",
111
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "fnByHof" } }],
112
+ },
113
+ {
114
+ code: "// @level 1\nfunction func1(){};\n// @level 2\nfunction func2(){ func1(); }",
115
+ filename: "./src/foo.js",
116
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "func1" } }],
117
+ },
118
+ {
119
+ code: "// @level 2\nfunction func2(){};\nfunction func1(){ func2(); }",
120
+ filename: "./src/foo.js",
121
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
122
+ },
123
+ {
124
+ code: "function func2(){};\n// @level 1\nfunction func1(){ func2(); }",
125
+ filename: "./src/foo.js",
126
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
127
+ },
74
128
  ],
75
129
  });