eslint-plugin-stratified-design 0.12.8 → 0.12.9

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.
@@ -85,6 +85,11 @@ const funcC = (...) => {
85
85
  }
86
86
  ```
87
87
 
88
+ ```js
89
+ const func = () => "a";
90
+ const obj = { a: func() };
91
+ ```
92
+
88
93
  Examples of **correct** code for this rule:
89
94
 
90
95
  ```js
@@ -116,3 +121,9 @@ const funcC = (...) => {
116
121
  ...
117
122
  }
118
123
  ```
124
+
125
+ ```js
126
+ const func = () => "a";
127
+ // @data
128
+ const obj = { a: func() };
129
+ ```
@@ -134,6 +134,24 @@ const match = (path) => {
134
134
  return (pattern) => minimatch(path, pattern);
135
135
  };
136
136
 
137
+ /**
138
+ * @template T
139
+ * @param {...(() => T)} args
140
+ * @returns {T | undefined}
141
+ */
142
+ const or = (...args) => {
143
+ for (const arg of args) {
144
+ try {
145
+ const result = arg();
146
+ if (result !== undefined) return result;
147
+ else continue;
148
+ } catch (e) {
149
+ continue;
150
+ }
151
+ }
152
+ return undefined;
153
+ };
154
+
137
155
  module.exports = {
138
156
  toRelative,
139
157
  toSegments,
@@ -149,4 +167,5 @@ module.exports = {
149
167
  replaceAlias,
150
168
  fromCwd,
151
169
  match,
170
+ or,
152
171
  };
@@ -1,18 +1,17 @@
1
1
  /**
2
2
  * @fileoverview Disallow calling functions in the same file.
3
3
  * @author Hodoug Joung
4
+ * @description 최상위에 있는 node가 함수인지 데이터인지 구분하지 않고 모두 함수로 취급힌다. (데이터인 경우에는 주석으로 '@data'를 붙여줘야 한다. 단, 명백하게 데이터인 경우에는 그 주석을 붙여주지 않아도 된다.)
4
5
  */
5
6
  "use strict";
6
7
 
7
8
  const path = require("path");
8
- const { fromCwd, match } = require("../helpers/common");
9
+ const { fromCwd, match, or } = require("../helpers/common");
9
10
 
10
11
  //------------------------------------------------------------------------------
11
12
  // Rule Definition
12
13
  //------------------------------------------------------------------------------
13
14
 
14
- // FIXME: const Body = styled.div``; const Body2 = styled(Body)`` (TaggedTemplateExpression)
15
-
16
15
  /**
17
16
  * @typedef {import('eslint').Rule.Node} Node
18
17
  * @typedef {import('eslint').AST.Token} Token
@@ -20,131 +19,91 @@ const { fromCwd, match } = require("../helpers/common");
20
19
  * @typedef {({[name: string]: number})} Levels
21
20
  */
22
21
 
23
- const hasArrFunction = (elements, levels) => {
24
- return elements.reduce((hasFunc, element) => {
25
- if (hasFunc) return true;
26
- const type = element.type;
27
- if (type === "FunctionExpression" || type === "ArrowFunctionExpression")
28
- return true;
29
- if (type === "ObjectExpression")
30
- return hasObjFunction(element.properties, levels);
31
- if (type === "ArrayExpression")
32
- return hasArrFunction(element.elements, levels);
33
- if ("name" in element && element.name in levels) return true;
34
- return false;
35
- }, false);
36
- };
37
-
38
- const hasObjFunction = (properties, levels) => {
39
- return properties.reduce((hasFunc, property) => {
40
- if (hasFunc) return true;
41
- if (!("value" in property)) return false;
42
- const type = property.value.type;
43
- if (type === "FunctionExpression" || type === "ArrowFunctionExpression")
44
- return true;
45
- if (type === "ObjectExpression")
46
- return hasObjFunction(property.value.properties, levels);
47
- if (type === "ArrayExpression")
48
- return hasArrFunction(property.value.elements, levels);
49
- if ("name" in property.value && property.value.name in levels) return true;
50
- return false;
51
- }, false);
52
- };
53
-
54
22
  /**
55
23
  * @param {Node | Token} nodeOrToken
56
24
  */
57
25
  const deriveDeclaration = (nodeOrToken) => {
58
- return (nodeOrToken.type === "ExportNamedDeclaration" ||
59
- nodeOrToken.type === "ExportDefaultDeclaration") &&
26
+ const declaration =
27
+ (nodeOrToken.type === "ExportNamedDeclaration" ||
28
+ nodeOrToken.type === "ExportDefaultDeclaration") &&
60
29
  Boolean(nodeOrToken.declaration)
61
- ? nodeOrToken.declaration
62
- : nodeOrToken;
30
+ ? nodeOrToken.declaration
31
+ : nodeOrToken;
32
+ return or(
33
+ () => declaration.declarations[0],
34
+ () => declaration
35
+ );
63
36
  };
64
37
 
65
- /**
66
- * @param {Node | Token} nodeOrToken
67
- * @returns {string | null}
68
- */
69
- const deriveFuncName = (nodeOrToken) => {
70
- const declaration = deriveDeclaration(nodeOrToken);
71
-
72
- if (declaration.type === "FunctionDeclaration") return declaration.id.name;
73
- if (declaration.type !== "VariableDeclaration") return null;
38
+ const isDataOrImported = (declarationInit, levels) => {
39
+ const expression = or(
40
+ () => declarationInit.expression,
41
+ () => declarationInit
42
+ );
43
+ if (!expression) return false;
74
44
 
75
- const { init, id } = declaration.declarations[0];
76
- if (
77
- [
78
- "ArrowFunctionExpression",
79
- "FunctionExpression",
80
- "CallExpression",
81
- "TaggedTemplateExpression",
82
- ].includes(init.type)
83
- ) {
84
- return id.name;
45
+ const type = expression.type;
46
+ if (type === "Literal") return true;
47
+ if (type === "Identifier") {
48
+ const name = or(() => expression.name);
49
+ return name && !levels[name];
85
50
  }
86
-
87
- return null;
51
+ if (type === "MemberExpression") {
52
+ const name = or(() => expression.object.name);
53
+ return name && !levels[name];
54
+ }
55
+ if (type === "CallExpression") {
56
+ const name = or(() => expression.callee.name);
57
+ return name && !levels[name];
58
+ }
59
+ if (type === "JSXElement") {
60
+ const name = or(() => expression.openingElement.name.name);
61
+ return name && !levels[name];
62
+ }
63
+ if (type === "SpreadElement")
64
+ return isDataOrImported(expression.argument, levels);
65
+ if (type === "ArrayExpression")
66
+ return expression.elements.every((init) => isDataOrImported(init, levels));
67
+ if (type === "ObjectExpression")
68
+ return expression.properties.every(({ value }) =>
69
+ isDataOrImported(value, levels)
70
+ );
71
+ return false;
88
72
  };
89
73
 
90
74
  /**
91
75
  * @param {Node | Token} nodeOrToken
92
76
  * @param {Levels} levels
93
- * @returns {string | null}
77
+ * @returns {string[] | undefined}
94
78
  */
95
- const deriveVarName = (nodeOrToken, levels) => {
96
- const declaration = deriveDeclaration(nodeOrToken);
97
- if (declaration.type !== "VariableDeclaration") return null;
98
- const { init, id } = declaration.declarations[0];
99
- if (
100
- init.type === "ObjectExpression" &&
101
- hasObjFunction(init.properties, levels)
102
- ) {
103
- return id.name;
104
- } else if (
105
- init.type === "ArrayExpression" &&
106
- hasArrFunction(init.elements, levels)
107
- ) {
108
- return id.name;
109
- } else {
110
- return null;
111
- }
112
- };
113
-
114
- const or = (...args) => {
115
- for (const arg of args) {
116
- try {
117
- const result = arg();
118
- if (result) return result;
119
- else continue;
120
- } catch (e) {
121
- continue;
122
- }
123
- }
124
- return undefined;
125
- };
126
-
127
- const deriveNames = (nodeOrToken) => {
79
+ const deriveNames = (nodeOrToken, levels) => {
128
80
  const declaration = deriveDeclaration(nodeOrToken);
81
+ if (isDataOrImported(declaration.init, levels)) return [];
129
82
  const names = or(
130
83
  () => declaration.id.name,
131
- () => declaration.declarations[0].id.name,
132
- () => declaration.declarations[0].id.elements.map(({ name }) => name),
133
- () =>
134
- declaration.declarations[0].id.properties.map(({ value }) => value.name)
84
+ () => declaration.id.elements.map(({ name }) => name),
85
+ () => declaration.id.properties.map(({ value }) => value.name)
135
86
  );
136
87
  return names === undefined ? [] : Array.isArray(names) ? names : [names];
137
88
  };
138
89
 
90
+ /**
91
+ * @param {Node | Token} nodeOrToken
92
+ * @returns {string | undefined}
93
+ */
94
+ const deriveCallerName = (nodeOrToken) =>
95
+ or(() => deriveDeclaration(nodeOrToken).id.name);
96
+
139
97
  /**
140
98
  * @param {SourceCode} sourceCode
141
99
  * @param {Node | Token} nodeOrToken
142
- *
100
+ * @returns {number | null}
143
101
  */
144
102
  const deriveLevel = (sourceCode, nodeOrToken) => {
145
103
  const comments = sourceCode.getCommentsBefore(nodeOrToken);
146
- for (const { value } of comments) {
147
- const levelInStr = value.replace(/^[^]*@level\s+?([0-9]+)[^0-9]*$/, "$1");
104
+ for (const { value: comment } of comments) {
105
+ if (comment.includes("@data") || comment.includes("@import")) return null;
106
+ const levelInStr = comment.replace(/^[^]*@level\s+?([0-9]+)[^0-9]*$/, "$1");
148
107
  const levelInNum = Number(levelInStr);
149
108
  if (levelInStr && !Number.isNaN(levelInNum)) {
150
109
  return levelInNum;
@@ -154,6 +113,7 @@ const deriveLevel = (sourceCode, nodeOrToken) => {
154
113
  };
155
114
 
156
115
  /**
116
+ * root node(Program) 바로 아래에 있는 부모 노드를 리턴한다.
157
117
  * @param {SourceCode} sourceCode
158
118
  * @param {Node} node
159
119
  */
@@ -175,7 +135,7 @@ const isCalleeLowerLevel = (sourceCode, levels) => {
175
135
  if (calleeLevel === undefined) return true; // 호출되는 함수가 존재하지 않으면 true 리턴. 함수 내의 함수의 경우에는 항상 calleeLevel 이 undefined 이다.
176
136
 
177
137
  const caller = traceAncestor(sourceCode, node);
178
- const callerName = deriveFuncName(caller) || deriveVarName(caller, levels);
138
+ const callerName = deriveCallerName(caller);
179
139
  const callerLevel = levels[callerName];
180
140
 
181
141
  if (callerLevel === undefined) return true;
@@ -268,11 +228,20 @@ module.exports = {
268
228
  return {
269
229
  Program(node) {
270
230
  node.body.forEach((token) => {
271
- deriveNames(token).forEach((name) => {
272
- if (name) levels[name] = deriveLevel(sourceCode, token);
231
+ deriveNames(token, levels).forEach((name) => {
232
+ const level = deriveLevel(sourceCode, token);
233
+ if (name && level) levels[name] = level;
273
234
  });
274
235
  });
275
236
  },
237
+ VariableDeclarator(node) {
238
+ const init = or(() => node.init.name);
239
+ const id = or(() => node.id.name);
240
+ if (!init || !id) return;
241
+ if (levels[init] && !levels[id]) {
242
+ levels[id] = levels[init];
243
+ }
244
+ },
276
245
  CallExpression(node) {
277
246
  for (const { name } of node.arguments) {
278
247
  const isReported = report(node, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-stratified-design",
3
- "version": "0.12.8",
3
+ "version": "0.12.9",
4
4
  "description": "ESlint rules for stratified design",
5
5
  "keywords": [
6
6
  "eslint",
@@ -21,6 +21,7 @@
21
21
  "lint": "eslint .",
22
22
  "test": "mocha tests --recursive",
23
23
  "test:watch": "mocha tests --recursive --watch",
24
+ "test:single": "mocha tests --watch",
24
25
  "versioning": "node ./scripts/versioning"
25
26
  },
26
27
  "dependencies": {
@@ -9,13 +9,18 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const rule = require("../../../lib/rules/no-same-level-funcs"),
12
- RuleTester = require("eslint").RuleTester;
12
+ RuleTester = require("eslint").RuleTester,
13
+ path = require("path");
13
14
 
14
15
  //------------------------------------------------------------------------------
15
16
  // Tests
16
17
  //------------------------------------------------------------------------------
17
18
 
18
19
  const ruleTester = new RuleTester({
20
+ parser: path.resolve(
21
+ __dirname,
22
+ "../../../node_modules/@typescript-eslint/parser/dist/index.js"
23
+ ),
19
24
  parserOptions: {
20
25
  ecmaVersion: 2022,
21
26
  sourceType: "module",
@@ -116,12 +121,10 @@ ruleTester.run("no-same-level-funcs", rule, {
116
121
  {
117
122
  code: "// @level 2\nfunction func2(){};\nfunction func1(){ func2(); }",
118
123
  filename: "./src/foo.js",
119
- errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
120
124
  },
121
125
  {
122
126
  code: "// @level 2\nexport function func2(){};\nfunction func1(){ func2(); }",
123
127
  filename: "./src/foo.js",
124
- errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
125
128
  },
126
129
 
127
130
  {
@@ -160,14 +163,14 @@ ruleTester.run("no-same-level-funcs", rule, {
160
163
  },
161
164
 
162
165
  {
163
- code: "const func = () => 'a'; const obj = { a: func() } ",
166
+ code: "/*@level 2*/const func = () => 'a'; const obj = { a: func() }",
164
167
  filename: "./src/foo.js",
165
168
  },
166
169
  {
167
- code: "/*@level 2*/const func = () => 'a'; /*@level 1*/const obj = { func: () => { func() } } ",
170
+ code: "const func = () => 'a'; /*@data*/const obj = { a: func() }",
168
171
  filename: "./src/foo.js",
169
- errors: [{ messageId: "no-same-level-funcs", data: { func: "obj" } }],
170
172
  },
173
+
171
174
  {
172
175
  code: "/*@level 2*/const obj = { func2: () => {} }; /*@level 1*/const func1 = () => { obj.func2() } ",
173
176
  filename: "./src/foo.js",
@@ -179,11 +182,10 @@ ruleTester.run("no-same-level-funcs", rule, {
179
182
  {
180
183
  code: "/*@level 3*/const func3 = () => 'a'; /*@level 2*/const obj = { func2: func3 }; /*@level 1*/const func1 = () => { obj.func2() } ",
181
184
  filename: "./src/foo.js",
182
- errors: [{ messageId: "no-same-level-funcs", data: { func: "obj" } }],
183
185
  },
184
186
 
185
187
  {
186
- code: "const func = () => 'a'; const arr = [func()];",
188
+ code: "/*@level 2*/const func = () => 'a'; const arr = [func()];",
187
189
  filename: "./src/foo.js",
188
190
  },
189
191
  {
@@ -231,6 +233,72 @@ ruleTester.run("no-same-level-funcs", rule, {
231
233
  code: "//@level 2\nconst {dat, fn2: func2} = someFn()\nconst fn1 = () => { func2() }\n",
232
234
  filename: "./src/foo.js",
233
235
  },
236
+
237
+ {
238
+ code: "const arr = []; const fn = () => arr.push(1)",
239
+ filename: "./src/foo.js",
240
+ },
241
+ {
242
+ code: "const arr = [{ a: 1 }]; const fn = () => arr.push(1)",
243
+ filename: "./src/foo.js",
244
+ },
245
+ {
246
+ code: "const obj = { arr: [] }; const fn = () => obj.arr.push(1)",
247
+ filename: "./src/foo.js",
248
+ },
249
+ {
250
+ code: "const arr = [1, 2, 3] as const; const fn = () => arr.some(v => v === 1)",
251
+ filename: "./src/foo.js",
252
+ },
253
+ {
254
+ code: "const data = ''; /*@level 2*/const fn2 = (param) => param; const fn1 = () => fn2(data)",
255
+ filename: "./src/foo.js",
256
+ },
257
+
258
+ {
259
+ code: "const CONST = 1; const data = CONST; const fn = () => data.replace(/a/g, 'b')",
260
+ filename: "./src/foo.js",
261
+ },
262
+ {
263
+ code: "const CONST = 1; const arr = [CONST]; const fn = () => arr.some(v => v === CONST)",
264
+ filename: "./src/foo.js",
265
+ },
266
+ {
267
+ code: "const CONST = 1; const obj = { a: [CONST] }; const fn = () => obj.a.some(v => v === CONST)",
268
+ filename: "./src/foo.js",
269
+ },
270
+ {
271
+ code: "import { lib } from 'lib'; const { fn2 } = lib; const fn1 = () => fn2()",
272
+ filename: "./src/foo.js",
273
+ },
274
+ {
275
+ code: "import { lib } from 'lib'; const fn2 = lib.fn2; const fn1 = () => fn2()",
276
+ filename: "./src/foo.js",
277
+ },
278
+ {
279
+ code: "import { fn2 } from 'lib'; const arr = [fn2]; const fn1 = () => arr[0]()",
280
+ filename: "./src/foo.js",
281
+ },
282
+ {
283
+ code: "import { fn2 } from 'lib'; const arr = [fn2()]; const fn1 = () => arr.push(1)",
284
+ filename: "./src/foo.js",
285
+ },
286
+ {
287
+ code: "import { fn2 } from 'lib'; const data = [...fn2()]; const fn1 = () => data.push(1)",
288
+ filename: "./src/foo.js",
289
+ },
290
+ {
291
+ code: "import { fn2 } from 'lib'; const arr = [fn2]; const fn1 = () => arr.push(1)",
292
+ filename: "./src/foo.js",
293
+ },
294
+ {
295
+ code: "import { Comp2 } from 'lib'; const arr = [<Comp2 />]; const fn1 = () => arr.push(1)",
296
+ filename: "./src/foo.js",
297
+ },
298
+ {
299
+ code: "import { Comp2 } from 'lib'; const arr = [Comp2]; const fn1 = () => arr.push(1)",
300
+ filename: "./src/foo.js",
301
+ },
234
302
  ],
235
303
  invalid: [
236
304
  {
@@ -309,17 +377,15 @@ ruleTester.run("no-same-level-funcs", rule, {
309
377
  errors: [{ messageId: "no-same-level-funcs", data: { func: "hof" } }],
310
378
  },
311
379
  {
312
- code: "const fnByHof = hof(() => 1); const value = fnByHof()",
313
- filename: "./src/foo.js",
314
- errors: [{ messageId: "no-same-level-funcs", data: { func: "fnByHof" } }],
315
- },
316
- {
317
- code: "const fnByHof = hof(() => 1); const fn = fnByHof(() => 1)",
380
+ code: "const hof = (fn) => () => fn(); const fnByHof = hof(() => 1); const value = fnByHof()",
318
381
  filename: "./src/foo.js",
319
- errors: [{ messageId: "no-same-level-funcs", data: { func: "fnByHof" } }],
382
+ errors: [
383
+ { messageId: "no-same-level-funcs", data: { func: "hof" } },
384
+ { messageId: "no-same-level-funcs", data: { func: "fnByHof" } },
385
+ ],
320
386
  },
321
387
  {
322
- code: "const fn = () => 1; const fnByHof = hof(fn)",
388
+ code: "const hof = (f) => () => f(); const fn = () => 1; const fnByHof = hof(fn)",
323
389
  filename: "./src/foo.js",
324
390
  errors: [{ messageId: "no-same-level-funcs", data: { func: "fn" } }],
325
391
  },
@@ -392,19 +458,30 @@ ruleTester.run("no-same-level-funcs", rule, {
392
458
  },
393
459
 
394
460
  {
395
- code: "const [dat, fn2] = someFn()\nconst fn1 = () => { fn2() }\n",
461
+ code: "const someFn = () => [1, () => 2]; const [dat, fn2] = someFn(); const fn1 = () => { fn2() }",
396
462
  filename: "./src/foo.js",
397
463
  errors: [{ messageId: "no-same-level-funcs", data: { func: "fn2" } }],
398
464
  },
399
465
  {
400
- code: "const {dat, fn2} = someFn()\nconst fn1 = () => { fn2() }\n",
466
+ code: "const someFn = () => ({ dat: 1, fn2: () => 2 }); const {dat, fn2} = someFn(); const fn1 = () => { fn2() };",
401
467
  filename: "./src/foo.js",
402
468
  errors: [{ messageId: "no-same-level-funcs", data: { func: "fn2" } }],
403
469
  },
404
470
  {
405
- code: "const {dat, fn2: func2} = someFn()\nconst fn1 = () => { func2() }\n",
471
+ code: "const someFn = () => ({ dat: 1, fn2: () => 2 }); const {dat, fn2: func2} = someFn(); const fn1 = () => { func2() };",
406
472
  filename: "./src/foo.js",
407
473
  errors: [{ messageId: "no-same-level-funcs", data: { func: "func2" } }],
408
474
  },
475
+
476
+ {
477
+ code: "const fn2 = () => {}; const fn1 = () => { const _fn2 = fn2; _fn2()}",
478
+ filename: "./src/foo.js",
479
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "_fn2" } }],
480
+ },
481
+ {
482
+ code: "const fn2 = () => {}; const fn1 = () => { const _fn2 = fn2; const __fn2 = _fn2; __fn2()}",
483
+ filename: "./src/foo.js",
484
+ errors: [{ messageId: "no-same-level-funcs", data: { func: "__fn2" } }],
485
+ },
409
486
  ],
410
487
  });