eslint-plugin-stratified-design 0.7.0 → 0.8.0-beta

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.
@@ -29,8 +29,9 @@ const resolvePath = p.resolve;
29
29
  const parsePath = p.parse;
30
30
 
31
31
  /**
32
- * @param {any[]} array
33
- * @param {(item: any) => boolean} callback
32
+ * @template T
33
+ * @param {T[]} array
34
+ * @param {(item: T) => boolean} callback
34
35
  * @returns {number}
35
36
  */
36
37
  const findLastIndex = (array, callback) => {
@@ -49,6 +50,16 @@ const equal = (array1, array2) => {
49
50
  return array1.every((item, index) => item === array2[index]);
50
51
  };
51
52
 
53
+ /**
54
+ * @template T
55
+ * @param {T[]} array
56
+ * @param {number} index
57
+ * @returns {T}
58
+ */
59
+ const readArray = (array, index) => {
60
+ return index >= 0 ? array[index] : array[array.length + index];
61
+ };
62
+
52
63
  module.exports = {
53
64
  toRelative,
54
65
  toSegments,
@@ -58,4 +69,5 @@ module.exports = {
58
69
  parsePath,
59
70
  findLastIndex,
60
71
  equal,
72
+ readArray,
61
73
  };
@@ -3,7 +3,7 @@ const { report: reportError } = require("./2 layer");
3
3
  const {
4
4
  isNodeModule,
5
5
  findLevel: findLayerLevel,
6
- hasInterface: hasInterfaceBetween,
6
+ hasBarrier: hasBarrierBetween,
7
7
  removeAlias: removeAliasFromModuleSource,
8
8
  } = require("./3 layer");
9
9
  const {
@@ -12,7 +12,7 @@ const {
12
12
  toPath,
13
13
  resolvePath,
14
14
  parsePath,
15
- } = require("./4 layer");
15
+ } = require("../common");
16
16
 
17
17
  const FINISHED = "finished";
18
18
 
@@ -191,10 +191,10 @@ const reportHasProperLevel = (
191
191
  filePath
192
192
  ) => {
193
193
  const findLevel = findLayerLevel(structure);
194
- const hasInterface = hasInterfaceBetween(structure, fileLevel);
194
+ const hasBarrier = hasBarrierBetween(structure, fileLevel);
195
195
 
196
196
  /**
197
- * @param {import('eslint').Rule.NodeParentExtension} node
197
+ * @param {import('../type').Node} node
198
198
  * @param {string} modulePath
199
199
  */
200
200
  return (node, modulePath) => {
@@ -222,7 +222,7 @@ const reportHasProperLevel = (
222
222
  return FINISHED;
223
223
  }
224
224
 
225
- if (hasInterface(moduleLevel)) {
225
+ if (hasBarrier(moduleLevel)) {
226
226
  report("barrier");
227
227
  return FINISHED;
228
228
  }
@@ -1,15 +1,15 @@
1
1
  const { isNodeModule } = require("./3 layer");
2
- const { toRelative } = require("./4 layer");
2
+ const { toRelative } = require("../common");
3
3
 
4
4
  /**
5
5
  * Report eslint error
6
- * @param {import('eslint').Rule.RuleContext} context
6
+ * @param {import('../type.js').Context} context
7
7
  * @param {string} rootDir
8
- * * @param {string} filePath
8
+ * @param {string} filePath
9
9
  */
10
10
  const report = (context, rootDir, filePath) => {
11
11
  /**
12
- * @param {import('eslint').Rule.NodeParentExtension} node
12
+ * @param {import('../type').Node} node
13
13
  * @param {string} messageId
14
14
  * @param {string} modulePath
15
15
  */
@@ -4,11 +4,36 @@ const {
4
4
  joinPath,
5
5
  resolvePath,
6
6
  toRelative,
7
- } = require("./4 layer");
7
+ } = require("../common");
8
8
 
9
9
  /**
10
- * @param {*} options
10
+ * @typedef {{
11
+ * structure: Array<
12
+ * string | { name: string, barrier?: boolean, interface?: boolean, nodeModule?: boolean, isNodeModule?: boolean }
13
+ * >,
14
+ * root: string,
15
+ * aliases: Record<string, string>,
16
+ * exclude: string[],
17
+ * include: string[],
18
+ * useLevelNumber: boolean
19
+ * isIndexHighest: boolean
20
+ * }} Options
21
+ */
22
+
23
+ /**
24
+ * @typedef {{
25
+ * name: string;
26
+ * barrier?: boolean | undefined;
27
+ * interface?: boolean | undefined;
28
+ * nodeModule?: boolean | undefined;
29
+ * isNodeModule?: boolean | undefined;
30
+ * }[]} Structure
31
+ */
32
+
33
+ /**
34
+ * @param {Options} options
11
35
  * @param {string} rootDir
36
+ * @returns {Structure}
12
37
  */
13
38
  const createStructure = (options, rootDir) => {
14
39
  return options.structure.map((layer) => {
@@ -20,7 +45,7 @@ const createStructure = (options, rootDir) => {
20
45
  };
21
46
 
22
47
  /**
23
- * @param {*} options
48
+ * @param {Options} options
24
49
  */
25
50
  const createAliases = (options) => {
26
51
  return Object.keys(options.aliases)
@@ -63,7 +88,7 @@ const isNodeModule = (rootDir) => {
63
88
 
64
89
  /**
65
90
  * Find the layer level for a module
66
- * @param {{[string]: {name: string; nodeModule: boolean; barrier: boolean; isNodeModule: boolean, interface: boolean}}} structure
91
+ * @param {Structure} structure
67
92
  * @returns the level of the module with `path`
68
93
  */
69
94
  const findLevel = (structure) => {
@@ -83,25 +108,25 @@ const findLevel = (structure) => {
83
108
 
84
109
  /**
85
110
  * Check if there is an interface between file layer and module layer
86
- * @param {{[string]: {name: string; nodeModule: boolean; barrier: boolean; isNodeModule: boolean, interface: boolean}}} structure
111
+ * @param {Structure} structure
87
112
  * @param {number} fileLevel
88
113
  */
89
- const hasInterface = (structure, fileLevel) => {
114
+ const hasBarrier = (structure, fileLevel) => {
90
115
  /**
91
116
  * @param {number} moduleLevel
92
117
  */
93
118
  return (moduleLevel) => {
94
- const layerInterface = structure
119
+ const layerBarrier = structure
95
120
  .slice(fileLevel + 1, moduleLevel)
96
121
  .find((layer) => layer.barrier || layer.interface);
97
- return Boolean(layerInterface);
122
+ return Boolean(layerBarrier);
98
123
  };
99
124
  };
100
125
 
101
126
  module.exports = {
102
127
  isNodeModule,
103
128
  findLevel,
104
- hasInterface,
129
+ hasBarrier,
105
130
  createStructure,
106
131
  createAliases,
107
132
  removeAlias,
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+
3
+ const {
4
+ resolvePath,
5
+ joinPath,
6
+ toSegments,
7
+ readArray,
8
+ findLastIndex,
9
+ } = require("../common");
10
+ const {
11
+ toStructure,
12
+ replaceAlias,
13
+ readRawStructure,
14
+ findLevel,
15
+ findLayerWithSimilarPath,
16
+ } = require("./2 layer");
17
+
18
+ /**
19
+ * @param {string} fileDir
20
+ */
21
+ const createStructure = (fileDir) => {
22
+ const parentFileDir = joinPath(fileDir, "..");
23
+
24
+ const rawStructure = readRawStructure(fileDir);
25
+ const parentRawStructure = readRawStructure(parentFileDir);
26
+
27
+ const fileDirname = `${readArray(toSegments(fileDir), -1)}/`;
28
+ const theIndex = findLastIndex(parentRawStructure, (rawLayers) => {
29
+ return Boolean(
30
+ rawLayers.find((rawLayer) => {
31
+ if (typeof rawLayer === "string")
32
+ return `${rawLayer}/`.startsWith(fileDirname);
33
+ return `${rawLayer.name}/`.startsWith(fileDirname);
34
+ })
35
+ );
36
+ });
37
+
38
+ const structure = toStructure(rawStructure, fileDir);
39
+ const parentStructure = toStructure(parentRawStructure, parentFileDir);
40
+
41
+ for (let i = theIndex + 1; i <= parentRawStructure.length - 1; i++) {
42
+ structure.push(parentStructure[i]);
43
+ }
44
+
45
+ return structure;
46
+ };
47
+
48
+ /**
49
+ * @param {string} cwd
50
+ * @param {string} fileDir
51
+ * @param {import('./2 layer').Aliases} aliases
52
+ */
53
+ const createModulePath = (cwd, fileDir, aliases) => {
54
+ /**
55
+ * @param {string} moduleSourceWithAlias
56
+ */
57
+ return (moduleSourceWithAlias) => {
58
+ const moduleSource = replaceAlias(
59
+ cwd,
60
+ fileDir,
61
+ aliases
62
+ )(moduleSourceWithAlias);
63
+ const isNodeModule = moduleSource.startsWith(".") === false;
64
+ return isNodeModule ? moduleSource : resolvePath(fileDir, moduleSource);
65
+ };
66
+ };
67
+
68
+ /**
69
+ * @param {import('./2 layer').Structure} structure
70
+ */
71
+ const findLevelInChild = (structure) => {
72
+ /**
73
+ * @param {string} path
74
+ */
75
+ return (path) => {
76
+ const layer = findLayerWithSimilarPath(structure)(path);
77
+ if (!layer) return null;
78
+ const fileDir = layer.name;
79
+ const rawChildStructure = readRawStructure(fileDir);
80
+ const childStructure = toStructure(rawChildStructure, fileDir);
81
+ const childLevel = findLevel(childStructure)(path);
82
+ return childLevel !== null ? findLevel(structure)(fileDir) : null;
83
+ };
84
+ };
85
+
86
+ module.exports = { createStructure, createModulePath, findLevelInChild };
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+
3
+ const { readFileSync } = require("fs");
4
+ const {
5
+ resolvePath,
6
+ toRelative,
7
+ joinPath,
8
+ toSegments,
9
+ toPath,
10
+ } = require("../common");
11
+
12
+ /**
13
+ * @typedef {{
14
+ * name: string;
15
+ * barrier?: boolean;
16
+ * nodeModule?: boolean;
17
+ * }} Layer
18
+ */
19
+
20
+ /**
21
+ * @typedef {string|Layer} RawLayer
22
+ */
23
+
24
+ /**
25
+ * @typedef {Layer[][]} Structure
26
+ */
27
+
28
+ /**
29
+ * @typedef {RawLayer[][]} RawStructure
30
+ */
31
+
32
+ /**
33
+ * @typedef {{alias: string, path: string}[]} Aliases
34
+ */
35
+
36
+ /**
37
+ * @param {string} fileDir
38
+ * @returns {RawStructure}
39
+ */
40
+ const readRawStructure = (fileDir) => {
41
+ try {
42
+ return JSON.parse(
43
+ readFileSync(resolvePath(fileDir, "./.stratified.json"), "utf-8")
44
+ );
45
+ } catch (err) {
46
+ return [];
47
+ }
48
+ };
49
+
50
+ /**
51
+ * @param {RawStructure} rawStructure
52
+ * @param {string} fileDir
53
+ * @return {Structure}
54
+ */
55
+ const toStructure = (rawStructure, fileDir) => {
56
+ return rawStructure.map((rawLayers) => {
57
+ return rawLayers.map((rawLayer) => {
58
+ const layer =
59
+ typeof rawLayer === "string" ? { name: rawLayer } : rawLayer;
60
+ return layer.nodeModule
61
+ ? layer
62
+ : { ...layer, name: joinPath(fileDir, layer.name) };
63
+ });
64
+ });
65
+ };
66
+
67
+ /**
68
+ * @param {Record<string, string>} rawAliases
69
+ * @returns {Aliases}
70
+ */
71
+ const createAliases = (rawAliases) => {
72
+ return Object.keys(rawAliases)
73
+ .sort((a, b) => b.length - a.length)
74
+ .map((alias) => ({ alias, path: rawAliases[alias] }));
75
+ };
76
+
77
+ /**
78
+ * Replace an alias into the corresponding path
79
+ * @param {string} cwd
80
+ * @param {string} fileDir
81
+ * @param {Aliases} aliases
82
+ */
83
+ const replaceAlias = (cwd, fileDir, aliases) => {
84
+ /**
85
+ * @param {string} moduleSource
86
+ */
87
+ return (moduleSource) => {
88
+ const { alias, path } =
89
+ aliases.find(({ alias }) => moduleSource.startsWith(alias)) || {};
90
+ if (!alias) return moduleSource;
91
+ const modulePath = resolvePath(cwd, moduleSource.replace(alias, path));
92
+ return toRelative(fileDir, modulePath);
93
+ };
94
+ };
95
+
96
+ /**
97
+ * Find the layer level for a module
98
+ * @param {Structure} structure
99
+ * @returns the level of the module with `path`
100
+ */
101
+ const findLevel = (structure) => {
102
+ /**
103
+ * @param {string} path
104
+ */
105
+ return (path) => {
106
+ const level = structure.findIndex((layers) =>
107
+ Boolean(layers.find((layer) => layer.name === path))
108
+ );
109
+ return level >= 0 ? level : null;
110
+ };
111
+ };
112
+
113
+ /**
114
+ * @param {import('./2 layer').Structure} structure
115
+ */
116
+ const findLayerWithSimilarPath = (structure) => {
117
+ /**
118
+ * @param {string} path
119
+ * @return {Layer | null}
120
+ */
121
+ return (path) => {
122
+ return (
123
+ toSegments(path).reduce((theLayer, _, index, segments) => {
124
+ if (theLayer) return theLayer;
125
+ const similarPath = toPath(segments.slice(0, segments.length - index));
126
+ /**
127
+ * @type {Layer}
128
+ */
129
+ return structure.reduce((foundLayer, layers) => {
130
+ if (foundLayer) return foundLayer;
131
+ return layers.find((layer) => layer.name === similarPath);
132
+ }, undefined);
133
+ }, undefined) || null
134
+ );
135
+ };
136
+ };
137
+
138
+ /**
139
+ * @param {string} cwd
140
+ */
141
+ const isNotRegisteredNodeModule = (cwd) => {
142
+ /**
143
+ * @param {string} modulePath
144
+ */
145
+ return (modulePath) => modulePath.startsWith(cwd) === false;
146
+ };
147
+
148
+ module.exports = {
149
+ readRawStructure,
150
+ toStructure,
151
+ createAliases,
152
+ replaceAlias,
153
+ findLevel,
154
+ findLayerWithSimilarPath,
155
+ isNotRegisteredNodeModule,
156
+ };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ const {
4
+ createStructure,
5
+ createModulePath,
6
+ findLevelInChild,
7
+ } = require("./1 layer");
8
+ const {
9
+ createAliases,
10
+ findLevel,
11
+ isNotRegisteredNodeModule,
12
+ } = require("./2 layer");
13
+
14
+ const { parseFileSource } = require("../lowerLevelImports");
15
+
16
+ module.exports = {
17
+ createStructure,
18
+ createModulePath,
19
+ createAliases,
20
+ parseFileSource,
21
+ findLevel,
22
+ isNotRegisteredNodeModule,
23
+ findLevelInChild,
24
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @typedef {import('eslint').Rule.RuleContext} Context
3
+ * @typedef {import('eslint').Rule.Node} Node
4
+ * @typedef {import('eslint').AST.Token} Token
5
+ * @typedef {import('eslint').SourceCode} SourceCode
6
+ */
7
+
8
+ module.exports = {};
@@ -117,15 +117,16 @@ module.exports = {
117
117
  include: ["**/*.{js,ts,jsx,tsx}"],
118
118
  aliases: {},
119
119
  useLevelNumber: false,
120
+ isIndexHighest: false,
120
121
  ...(context.options[0] || {}),
121
122
  };
122
123
 
123
- const cwd = context.getCwd();
124
+ const cwd = context.cwd;
124
125
  const rootDir = createRootDir(cwd, options);
125
126
 
126
127
  const { fileDir, filePath, isExcludedFile } = parseFileSource(
127
128
  options,
128
- context.getFilename()
129
+ context.filename
129
130
  );
130
131
 
131
132
  const structure = createStructure(options, rootDir);
@@ -35,17 +35,11 @@ const deriveLevel = (sourceCode, nodeOrToken) => {
35
35
  };
36
36
 
37
37
  /**
38
- *
38
+ * @param {SourceCode} sourceCode
39
39
  * @param {Node} node
40
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;
41
+ const traceAncestor = (sourceCode, node) => {
42
+ return sourceCode.getAncestors(node)[1];
49
43
  };
50
44
 
51
45
  /** @type {import('eslint').Rule.RuleModule} */
@@ -83,7 +77,7 @@ module.exports = {
83
77
  ...(context.options[0] || {}),
84
78
  };
85
79
 
86
- const fileSource = path.resolve(context.getFilename());
80
+ const fileSource = path.resolve(context.filename);
87
81
 
88
82
  const isIncludedFile = options.include.find((pattern) =>
89
83
  minimatch(fileSource, pattern)
@@ -100,7 +94,7 @@ module.exports = {
100
94
  */
101
95
  const levels = {};
102
96
 
103
- const sourceCode = context.getSourceCode();
97
+ const sourceCode = context.sourceCode;
104
98
 
105
99
  return {
106
100
  Program(node) {
@@ -127,7 +121,7 @@ module.exports = {
127
121
  const calleeLevel = levels[node.callee.name];
128
122
  if (calleeLevel === undefined) return;
129
123
  if (calleeLevel !== null) {
130
- const ancestor = traceAncestor(node);
124
+ const ancestor = traceAncestor(sourceCode, node);
131
125
  const ancestorLevel = deriveLevel(sourceCode, ancestor);
132
126
  if (ancestorLevel !== null && ancestorLevel < calleeLevel) return;
133
127
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @fileoverview Require that lower level modules be imported (stratified-imports)
3
+ * @author Hodoug Joung
4
+ */
5
+
6
+ "use strict";
7
+
8
+ const { parsePath } = require("../helpers/common");
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ const helper = require("../helpers/stratifiedImports");
14
+
15
+ //------------------------------------------------------------------------------
16
+ // Rule Definition
17
+ //------------------------------------------------------------------------------
18
+
19
+ /** @type {import('eslint').Rule.RuleModule} */
20
+ module.exports = {
21
+ meta: {
22
+ type: "problem",
23
+ fixable: "code",
24
+ schema: {
25
+ type: "array",
26
+ items: [
27
+ {
28
+ type: "object",
29
+ properties: {
30
+ aliases: {
31
+ type: "object",
32
+ patternProperties: {
33
+ ["^.+$"]: {
34
+ type: "string",
35
+ pattern: "^\\.{1,2}(/[^/]+)*/?$",
36
+ },
37
+ },
38
+ additionalProperties: false,
39
+ },
40
+ exclude: {
41
+ type: "array",
42
+ items: [{ type: "string" }],
43
+ },
44
+ include: {
45
+ type: "array",
46
+ items: [{ type: "string" }],
47
+ },
48
+ },
49
+ additionalProperties: false,
50
+ },
51
+ ],
52
+ additionalItems: false,
53
+ },
54
+ messages: {
55
+ "not-lower-level": "'{{module}}' is NOT LOWER level than '{{file}}'",
56
+ barrier:
57
+ "An ABSTRACT BARRIER prevents '{{file}}' from importing '{{module}}'",
58
+ "not-registered": "'{{file}}' does NOT registered at .stratified.json",
59
+ },
60
+ },
61
+ create(context) {
62
+ const options = {
63
+ exclude: ["**/*.{test,spec}.{js,ts,jsx,tsx}"],
64
+ include: ["**/*.{js,ts,jsx,tsx}"],
65
+ aliases: {},
66
+ ...(context.options[0] || {}),
67
+ };
68
+
69
+ const { fileDir, filePath, isExcludedFile } = helper.parseFileSource(
70
+ options,
71
+ context.filename
72
+ );
73
+
74
+ if (isExcludedFile) return {};
75
+
76
+ const structure = helper.createStructure(fileDir);
77
+
78
+ const fileLevel = helper.findLevel(structure)(filePath);
79
+
80
+ const createModulePath = helper.createModulePath(
81
+ context.cwd,
82
+ fileDir,
83
+ helper.createAliases(options.aliases)
84
+ );
85
+
86
+ const findLevel = helper.findLevel(structure);
87
+
88
+ const findLevelInChild = helper.findLevelInChild(structure);
89
+
90
+ const isNotRegisteredNodeModule = helper.isNotRegisteredNodeModule(
91
+ context.cwd
92
+ );
93
+
94
+ /**
95
+ * @param {number} moduleLevel
96
+ */
97
+ const hasBarrier = (moduleLevel) => {
98
+ const layerBarrier = structure
99
+ .slice(fileLevel + 1, moduleLevel)
100
+ .find((layers) => layers.find((layer) => layer.barrier));
101
+ return Boolean(layerBarrier);
102
+ };
103
+
104
+ /**
105
+ * @param {import('../helpers/type').Node} node
106
+ * @param {'not-lower-level'|'barrier'|'not-registered'} messageId
107
+ * @param {string | undefined} modulePath
108
+ */
109
+ const reportError = (node, messageId, modulePath) => {
110
+ context.report({
111
+ node,
112
+ messageId,
113
+ data: {
114
+ file: parsePath(filePath).name,
115
+ ...(modulePath ? { module: parsePath(modulePath).name } : {}),
116
+ },
117
+ });
118
+ };
119
+
120
+ /**
121
+ * @param {import('../helpers/type').Node} node
122
+ * @returns
123
+ */
124
+ const report = (node) => {
125
+ // TODO: invalid .stratified.json
126
+
127
+ if (fileLevel === null) {
128
+ reportError(node, "not-registered");
129
+ return;
130
+ }
131
+
132
+ const modulePath = createModulePath(node.source.value);
133
+ const moduleLevel = (() => {
134
+ const level = findLevel(modulePath);
135
+ return level !== null
136
+ ? level
137
+ : isNotRegisteredNodeModule(modulePath)
138
+ ? "notRegisteredNodeModule"
139
+ : findLevelInChild(modulePath);
140
+ })();
141
+
142
+ if (moduleLevel === "notRegisteredNodeModule") return;
143
+
144
+ if (moduleLevel === null) {
145
+ reportError(node, "not-lower-level", modulePath);
146
+ return;
147
+ }
148
+
149
+ if (hasBarrier(moduleLevel)) {
150
+ reportError(node, "barrier", modulePath);
151
+ return;
152
+ }
153
+
154
+ if (moduleLevel <= fileLevel) {
155
+ reportError(node, "not-lower-level", modulePath);
156
+ return;
157
+ }
158
+ };
159
+
160
+ return {
161
+ ImportDeclaration(node) {
162
+ report(node);
163
+ },
164
+ ExportNamedDeclaration(node) {
165
+ if (node.source) report(node);
166
+ },
167
+ ExportAllDeclaration(node) {
168
+ if (node.source) report(node);
169
+ },
170
+ };
171
+ },
172
+ };
@@ -0,0 +1,12 @@
1
+ [
2
+ ["layerA"],
3
+ ["layerB"],
4
+ [{ "name": "layerC", "barrier": true }],
5
+ ["layerD"],
6
+ [{ "name": "nodeModuleE", "nodeModule": "true" }],
7
+ ["layerF"],
8
+ ["layerG", "layerH"],
9
+ ["layerI"],
10
+ ["layerJ"],
11
+ ["layerK"]
12
+ ]
@@ -0,0 +1 @@
1
+ [["layerBA"], ["layerBB"]]
@@ -0,0 +1 @@
1
+ [["entryJA", "entryJB"], ["layerJC"]]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-stratified-design",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-beta",
4
4
  "description": "ESlint rules for stratified design",
5
5
  "keywords": [
6
6
  "eslint",
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @fileoverview test for helpers/stratified-imports
3
+ * @author Hodoug Joung
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const assert = require("assert");
12
+ const {
13
+ findLevel,
14
+ findLayerWithSimilarPath,
15
+ toStructure,
16
+ createAliases,
17
+ replaceAlias,
18
+ } = require("../../../lib/helpers/stratifiedImports/2 layer");
19
+ const {
20
+ createModulePath,
21
+ } = require("../../../lib/helpers/stratifiedImports/1 layer");
22
+
23
+ //------------------------------------------------------------------------------
24
+ // Tests
25
+ //------------------------------------------------------------------------------
26
+
27
+ describe("helpers/stratified-imports", () => {
28
+ describe("toStructure()", () => {
29
+ const fileDir = "/src";
30
+ const testCases = [
31
+ { rawStructure: [["layer"]], structure: [[{ name: "/src/layer" }]] },
32
+ {
33
+ rawStructure: [[{ name: "layer" }]],
34
+ structure: [[{ name: "/src/layer" }]],
35
+ },
36
+ {
37
+ rawStructure: [[{ name: "layer", barrier: true }]],
38
+ structure: [[{ name: "/src/layer", barrier: true }]],
39
+ },
40
+ ];
41
+ testCases.forEach(({ rawStructure, structure }) => {
42
+ it(`${rawStructure} -> ${structure}`, () => {
43
+ assert.deepEqual(toStructure(rawStructure, fileDir), structure);
44
+ });
45
+ });
46
+ });
47
+
48
+ describe("createAliases()", () => {
49
+ const testCases = [
50
+ {
51
+ rawAliases: { "@/": "./src/" },
52
+ aliases: [{ alias: "@/", path: "./src/" }],
53
+ },
54
+ {
55
+ rawAliases: { "@/": "./src/", "@layer/": "./src/layer/" },
56
+ aliases: [
57
+ { alias: "@layer/", path: "./src/layer/" },
58
+ { alias: "@/", path: "./src/" },
59
+ ],
60
+ },
61
+ ];
62
+ testCases.forEach(({ rawAliases, aliases }) => {
63
+ it(`${rawAliases} -> ${aliases}`, () => {
64
+ assert.deepEqual(createAliases(rawAliases), aliases);
65
+ });
66
+ });
67
+ });
68
+
69
+ describe("replaceAlias()", () => {
70
+ const cwd = "/proj";
71
+ const fileDir = "/proj/src/layerA";
72
+ const aliases = [{ alias: "@/", path: "./src/" }];
73
+ const testCases = [
74
+ { moduleSource: "@/layerA/layerAA", relPath: "./layerAA" },
75
+ { moduleSource: "nodeModule", relPath: "nodeModule" },
76
+ ];
77
+ testCases.forEach(({ moduleSource, relPath }) => {
78
+ it(`${moduleSource} -> ${relPath}`, () => {
79
+ assert.equal(
80
+ replaceAlias(cwd, fileDir, aliases)(moduleSource),
81
+ relPath
82
+ );
83
+ });
84
+ });
85
+ });
86
+
87
+ describe("findLevel()", () => {
88
+ const structure = [[{ name: "/src/layerA" }], [{ name: "/src/layerB" }]];
89
+ const testCases = [
90
+ { path: "/src/layerA", level: 0 },
91
+ { path: "/src/layerB", level: 1 },
92
+ { path: "/src/layerA/entry", level: null },
93
+ ];
94
+ testCases.forEach(({ path, level }) => {
95
+ it(`The level of ${path} is ${level}`, () => {
96
+ assert.equal(findLevel(structure)(path), level);
97
+ });
98
+ });
99
+ });
100
+
101
+ describe("findLayerWithSimilarPath()", () => {
102
+ const structure = [[{ name: "/src/layerA" }], [{ name: "/src/layerB" }]];
103
+ const testCases = [
104
+ { path: "/src/layerA", layer: structure[0][0] },
105
+ { path: "/src/layerB", layer: structure[1][0] },
106
+ { path: "/src/layerA/entry", layer: structure[0][0] },
107
+ ];
108
+ testCases.forEach(({ path, layer }) => {
109
+ it(`The level of ${path} is ${JSON.stringify(layer)}`, () => {
110
+ assert.equal(findLayerWithSimilarPath(structure)(path), layer);
111
+ });
112
+ });
113
+ });
114
+
115
+ describe("createModulePath()", () => {
116
+ const cwd = "/proj";
117
+ const fileDir = "/proj/src/layerA";
118
+ const aliases = [{ alias: "@/", path: "./src/" }];
119
+ const testCases = [
120
+ {
121
+ moduleSource: "@/layerA/layerAA",
122
+ modulePath: "/proj/src/layerA/layerAA",
123
+ },
124
+ {
125
+ moduleSource: "nodeModule",
126
+ modulePath: "nodeModule",
127
+ },
128
+ ];
129
+ testCases.forEach(({ moduleSource, modulePath }) => {
130
+ it(`${moduleSource} -> ${modulePath}`, () => {
131
+ assert.equal(
132
+ createModulePath(cwd, fileDir, aliases)(moduleSource),
133
+ modulePath
134
+ );
135
+ });
136
+ });
137
+ });
138
+ });
@@ -0,0 +1,228 @@
1
+ /**
2
+ * @fileoverview test for lower-level-imports
3
+ * @author Hodoug Joung
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const rule = require("../../../lib/rules/stratified-imports");
12
+ const RuleTester = require("eslint").RuleTester;
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Tests
16
+ //------------------------------------------------------------------------------
17
+
18
+ /*
19
+ // .stratified.json
20
+ [
21
+ ["layerA"],
22
+ ["layerB"],
23
+ [{ "name": "layerC", "barrier": true }],
24
+ ["layerD"],
25
+ [{ "name": "nodeModuleE", "nodeModule": "true" }],
26
+ ["layerF"],
27
+ ["layerG", "layerH"],
28
+ ["layerI"],
29
+ ["layerJ"],
30
+ ["layerK"]
31
+ ]
32
+
33
+ // layerB/.stratified.json
34
+ [
35
+ ["layerBA"],
36
+ ["layerBB"]
37
+ ]
38
+
39
+ // layerJ/.stratified.json
40
+ [
41
+ ["entryJA", "entryJB"],
42
+ ["layerJC"]
43
+ ]
44
+ */
45
+
46
+ const ruleTester = new RuleTester({
47
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
48
+ });
49
+
50
+ ruleTester.run("stratified-imports", rule, {
51
+ valid: [
52
+ {
53
+ code: "import { func } from './layerB'",
54
+ filename: "./mocked/stratified-imports/layerA.js",
55
+ options: [],
56
+ },
57
+ {
58
+ code: "import { func } from './layerC'",
59
+ filename: "./mocked/stratified-imports/layerA.js",
60
+ options: [],
61
+ },
62
+ {
63
+ code: "import { func } from 'notRegisteredNodeModule'",
64
+ filename: "./mocked/stratified-imports/layerA.js",
65
+ options: [],
66
+ },
67
+ {
68
+ code: "import { func } from 'nodeModuleE'",
69
+ filename: "./mocked/stratified-imports/layerD.js",
70
+ options: [],
71
+ },
72
+ {
73
+ code: "import { func } from './layerH'",
74
+ filename: "./mocked/stratified-imports/layerF.js",
75
+ options: [],
76
+ },
77
+ {
78
+ code: "import { func } from './layerI'",
79
+ filename: "./mocked/stratified-imports/layerH.js",
80
+ options: [],
81
+ },
82
+ {
83
+ code: "import { func } from './layerBB'",
84
+ filename: "./mocked/stratified-imports/layerB/layerBA.js",
85
+ options: [],
86
+ },
87
+ {
88
+ code: "import { func } from '../layerC'",
89
+ filename: "./mocked/stratified-imports/layerB/layerBA.js",
90
+ options: [],
91
+ },
92
+ {
93
+ code: "import { func } from './layerJC'",
94
+ filename: "./mocked/stratified-imports/layerJ/entryJA.js",
95
+ options: [],
96
+ },
97
+ {
98
+ code: "import { func } from './layerJC'",
99
+ filename: "./mocked/stratified-imports/layerJ/entryJB.js",
100
+ options: [],
101
+ },
102
+ {
103
+ code: "import { func } from '../layerK'",
104
+ filename: "./mocked/stratified-imports/layerJ/layerJC.js",
105
+ options: [],
106
+ },
107
+ {
108
+ code: "import { func } from '@/layerB'",
109
+ filename: "./mocked/stratified-imports/layerA.js",
110
+ options: [{ aliases: { "@/": "./mocked/stratified-imports/" } }],
111
+ },
112
+ {
113
+ code: "import { func } from './notRegisteredLayer'",
114
+ filename: "./mocked/stratified-imports/layerA.test.js",
115
+ options: [],
116
+ },
117
+ {
118
+ code: "import { func } from './notRegisteredLayer'",
119
+ filename: "./mocked/stratified-imports/layerA.js",
120
+ options: [{ include: ["**/*.ts"] }],
121
+ },
122
+ {
123
+ code: "import { func } from './notRegisteredLayer'",
124
+ filename: "./mocked/stratified-imports/layerA.js",
125
+ options: [{ exclude: ["**/*.js"] }],
126
+ },
127
+ {
128
+ code: "import { func } from './notRegisteredLayer'",
129
+ filename: "./mocked/stratified-imports/layerA.js",
130
+ options: [{ include: ["**/*.js"], exclude: ["**/layerA.js"] }],
131
+ },
132
+ {
133
+ code: "import { func } from './layerJ/entryJA'",
134
+ filename: "./mocked/stratified-imports/layerI.js",
135
+ options: [],
136
+ },
137
+ ],
138
+ invalid: [
139
+ {
140
+ code: "import { func } from './layerA'",
141
+ filename: "./mocked/stratified-imports/notRegisteredLayer.js",
142
+ options: [],
143
+ errors: [
144
+ {
145
+ messageId: "not-registered",
146
+ data: { file: "notRegisteredLayer" },
147
+ },
148
+ ],
149
+ },
150
+ {
151
+ code: "import { func } from './layerA'",
152
+ filename: "./mocked/stratified-imports/layerB.js",
153
+ options: [],
154
+ errors: [
155
+ {
156
+ messageId: "not-lower-level",
157
+ data: { module: "layerA", file: "layerB" },
158
+ },
159
+ ],
160
+ },
161
+ {
162
+ code: "import { func } from './layerD'",
163
+ filename: "./mocked/stratified-imports/layerB.js",
164
+ options: [],
165
+ errors: [
166
+ {
167
+ messageId: "barrier",
168
+ data: { module: "layerD", file: "layerB" },
169
+ },
170
+ ],
171
+ },
172
+ {
173
+ code: "import { func } from './layerBA'",
174
+ filename: "./mocked/stratified-imports/layerB/layerBB.js",
175
+ options: [],
176
+ errors: [
177
+ {
178
+ messageId: "not-lower-level",
179
+ data: { module: "layerBA", file: "layerBB" },
180
+ },
181
+ ],
182
+ },
183
+ {
184
+ code: "import { func } from '../layerA'",
185
+ filename: "./mocked/stratified-imports/layerB/layerBA.js",
186
+ options: [],
187
+ errors: [
188
+ {
189
+ messageId: "not-lower-level",
190
+ data: { module: "layerA", file: "layerBA" },
191
+ },
192
+ ],
193
+ },
194
+ {
195
+ code: "import { func } from './entryJA'",
196
+ filename: "./mocked/stratified-imports/layerJ/layerJC.js",
197
+ options: [],
198
+ errors: [
199
+ {
200
+ messageId: "not-lower-level",
201
+ data: { module: "entryJA", file: "layerJC" },
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ code: "import { func } from '../layerI'",
207
+ filename: "./mocked/stratified-imports/layerJ/layerJC.js",
208
+ options: [],
209
+ errors: [
210
+ {
211
+ messageId: "not-lower-level",
212
+ data: { module: "layerI", file: "layerJC" },
213
+ },
214
+ ],
215
+ },
216
+ {
217
+ code: "import { func } from './layerJ/notRegisteredEntry'",
218
+ filename: "./mocked/stratified-imports/layerI.js",
219
+ options: [],
220
+ errors: [
221
+ {
222
+ messageId: "not-lower-level",
223
+ data: { module: "notRegisteredEntry", file: "layerI" },
224
+ },
225
+ ],
226
+ },
227
+ ],
228
+ });