eslint-plugin-stratified-design 0.8.0-beta.3 → 0.9.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.
- package/docs/rules/lower-level-imports.md +11 -0
- package/docs/rules/no-disallowed-imports.md +90 -0
- package/docs/rules/stratified-imports.md +12 -1
- package/lib/helpers/common.js +37 -0
- package/lib/helpers/noDisallowedImports.js +38 -0
- package/lib/helpers/stratifiedImports/1 layer.js +6 -2
- package/lib/helpers/stratifiedImports/2 layer.js +0 -37
- package/lib/rules/no-disallowed-imports.js +180 -0
- package/lib/rules/no-same-level-funcs.js +2 -0
- package/lib/rules/stratified-imports.js +2 -2
- package/package.json +1 -1
- package/tests/lib/helpers/common.js +57 -0
- package/tests/lib/helpers/stratified-imports.js +0 -41
- package/tests/lib/rules/no-disallowed-imports.js +184 -0
|
@@ -125,6 +125,17 @@ The default is as follows:
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
Imported modules can be excluded from the rule (`lower-level-imports`) using the `excludeImports` option:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"stratified-design/lower-level-imports": [
|
|
133
|
+
"error",
|
|
134
|
+
{ "excludeImports": ["**/*.css"] }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
128
139
|
## Rule Details
|
|
129
140
|
|
|
130
141
|
If a file structure is as follows:
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Allow or disallow importing specified modules (no-disallowed-imports)
|
|
2
|
+
|
|
3
|
+
In certain cases, it is necessary to restrict the importation of specific modules. For instance:
|
|
4
|
+
|
|
5
|
+
- In a stratified design approach, higher-level modules should not be imported into lower-level modules.
|
|
6
|
+
- There may be situations where global variables should only be writable within a specific context.
|
|
7
|
+
|
|
8
|
+
## Options
|
|
9
|
+
|
|
10
|
+
To control module imports, use the `imports` option:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"stratified-design/no-disallowed-imports": [
|
|
15
|
+
"error",
|
|
16
|
+
{
|
|
17
|
+
"imports": [
|
|
18
|
+
{
|
|
19
|
+
"import": { "member": ["foo"], "from": "**/src/fileA" },
|
|
20
|
+
"allow": ["**/src/some-dir/*.js"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"import": { "member": ["foo", "bar"], "from": "**/src/fileB" },
|
|
24
|
+
"disallow": ["**/src/not-allowed-file.js"]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"import": { "member": ["baz"], "from": "**/src/fileC" },
|
|
28
|
+
"allow": ["**/src/some-dir/*.js"],
|
|
29
|
+
"disallow": ["**/src/not-allowed-file.js"]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If an imported module uses an alias, you can register the alias with the `aliases` option:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"stratified-design/no-disallowed-imports": [
|
|
42
|
+
"error",
|
|
43
|
+
{
|
|
44
|
+
"aliases": { "@/": "./src/" }
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Rule Details
|
|
51
|
+
|
|
52
|
+
Examples of **incorrect** code for this rule:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// "stratified-design/no-disallowed-imports": [
|
|
56
|
+
// "error",
|
|
57
|
+
// {
|
|
58
|
+
// "imports": [
|
|
59
|
+
// {
|
|
60
|
+
// "import": { "member": ["foo"], "from": "**/src/fileA" },
|
|
61
|
+
// "allow": ["**/src/**/fileB.js"],
|
|
62
|
+
// "disallow": ["**/src/**/fileC.*"]
|
|
63
|
+
// }
|
|
64
|
+
// ]
|
|
65
|
+
// }
|
|
66
|
+
// ]
|
|
67
|
+
|
|
68
|
+
// ./src/fileC.js
|
|
69
|
+
import { foo } from "./fileA";
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Examples of **correct** code for this rule:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// "stratified-design/no-disallowed-imports": [
|
|
76
|
+
// "error",
|
|
77
|
+
// {
|
|
78
|
+
// "imports": [
|
|
79
|
+
// {
|
|
80
|
+
// "import": { "member": ["foo"], "from": "**/src/fileA" },
|
|
81
|
+
// "allow": ["**/src/**/fileB.js"],
|
|
82
|
+
// "disallow": ["**/src/**/fileC.*"]
|
|
83
|
+
// }
|
|
84
|
+
// ]
|
|
85
|
+
// }
|
|
86
|
+
// ]
|
|
87
|
+
|
|
88
|
+
// ./src/fileB.js
|
|
89
|
+
import { foo } from "./fileA";
|
|
90
|
+
```
|
|
@@ -68,7 +68,7 @@ You can register the files to which the rule (`stratified-imports`) should apply
|
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
70
|
{
|
|
71
|
-
"stratified-design/
|
|
71
|
+
"stratified-design/stratified-imports": [
|
|
72
72
|
"error",
|
|
73
73
|
{ "include": ["**/*.js"], "exclude": ["**/*.test.js"] }
|
|
74
74
|
]
|
|
@@ -84,6 +84,17 @@ The default configuration is as follows:
|
|
|
84
84
|
}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
Imported modules can be excluded from the rule (`stratified-imports`) using the `excludeImports` option:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"stratified-design/stratified-imports": [
|
|
92
|
+
"error",
|
|
93
|
+
{ "excludeImports": ["**/*.css"] }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
87
98
|
## Rule Details
|
|
88
99
|
|
|
89
100
|
Consider the following folder structure:
|
package/lib/helpers/common.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const p = require("path");
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{alias: string, path: string}[]} Aliases
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
// @level 2
|
|
4
8
|
/**
|
|
5
9
|
* @param {string} from
|
|
@@ -81,6 +85,37 @@ const reducedPaths = (path) => {
|
|
|
81
85
|
}, []);
|
|
82
86
|
};
|
|
83
87
|
|
|
88
|
+
// @level 1
|
|
89
|
+
/**
|
|
90
|
+
* @param {Record<string, string>} rawAliases
|
|
91
|
+
* @returns {Aliases}
|
|
92
|
+
*/
|
|
93
|
+
const createAliases = (rawAliases) => {
|
|
94
|
+
return Object.keys(rawAliases)
|
|
95
|
+
.sort((a, b) => b.length - a.length)
|
|
96
|
+
.map((alias) => ({ alias, path: rawAliases[alias] }));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// @level 1
|
|
100
|
+
/**
|
|
101
|
+
* Replace an alias into the corresponding path
|
|
102
|
+
* @param {string} cwd
|
|
103
|
+
* @param {string} fileDir
|
|
104
|
+
* @param {Aliases} aliases
|
|
105
|
+
*/
|
|
106
|
+
const replaceAlias = (cwd, fileDir, aliases) => {
|
|
107
|
+
/**
|
|
108
|
+
* @param {string} moduleSource
|
|
109
|
+
*/
|
|
110
|
+
return (moduleSource) => {
|
|
111
|
+
const { alias, path } =
|
|
112
|
+
aliases.find(({ alias }) => moduleSource.startsWith(alias)) || {};
|
|
113
|
+
if (!alias) return moduleSource;
|
|
114
|
+
const modulePath = resolvePath(cwd, moduleSource.replace(alias, path));
|
|
115
|
+
return toRelative(fileDir, modulePath);
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
84
119
|
module.exports = {
|
|
85
120
|
toRelative,
|
|
86
121
|
toSegments,
|
|
@@ -92,4 +127,6 @@ module.exports = {
|
|
|
92
127
|
equal,
|
|
93
128
|
readArray,
|
|
94
129
|
reducedPaths,
|
|
130
|
+
createAliases,
|
|
131
|
+
replaceAlias,
|
|
95
132
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { replaceAlias, resolvePath, parsePath } = require("./common");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} contextFileSource
|
|
7
|
+
*/
|
|
8
|
+
const parseFileSource = (contextFileSource) => {
|
|
9
|
+
const fileSource = resolvePath(contextFileSource);
|
|
10
|
+
const parsedFileSource = parsePath(fileSource);
|
|
11
|
+
const fileDir = resolvePath(parsedFileSource.dir);
|
|
12
|
+
return { fileDir, fileSource };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} cwd
|
|
17
|
+
* @param {string} fileDir
|
|
18
|
+
* @param {import("./common").Aliases} aliases
|
|
19
|
+
*/
|
|
20
|
+
const createModulePath = (cwd, fileDir, aliases) => {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} moduleSourceWithAlias
|
|
23
|
+
*/
|
|
24
|
+
return (moduleSourceWithAlias) => {
|
|
25
|
+
const moduleSource = replaceAlias(
|
|
26
|
+
cwd,
|
|
27
|
+
fileDir,
|
|
28
|
+
aliases
|
|
29
|
+
)(moduleSourceWithAlias);
|
|
30
|
+
const isNodeModule = moduleSource.startsWith(".") === false;
|
|
31
|
+
const modulePath = isNodeModule
|
|
32
|
+
? moduleSource
|
|
33
|
+
: resolvePath(fileDir, moduleSource);
|
|
34
|
+
return modulePath;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = { parseFileSource, createModulePath };
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { minimatch } = require("minimatch");
|
|
4
|
-
const { resolvePath, reducedPaths, parsePath } = require("../common");
|
|
5
4
|
const {
|
|
5
|
+
resolvePath,
|
|
6
|
+
reducedPaths,
|
|
7
|
+
parsePath,
|
|
6
8
|
replaceAlias,
|
|
9
|
+
} = require("../common");
|
|
10
|
+
const {
|
|
7
11
|
findLevel,
|
|
8
12
|
findLayerWithReducedPath,
|
|
9
13
|
readStructure,
|
|
@@ -56,7 +60,7 @@ const parseFileSource = (options, contextFileSource) => {
|
|
|
56
60
|
* @param {string} cwd
|
|
57
61
|
* @param {string[]} excludeImports
|
|
58
62
|
* @param {string} fileDir
|
|
59
|
-
* @param {import(
|
|
63
|
+
* @param {import("../common").Aliases} aliases
|
|
60
64
|
*/
|
|
61
65
|
const createModulePath = (cwd, excludeImports, fileDir, aliases) => {
|
|
62
66
|
/**
|
|
@@ -29,10 +29,6 @@ const {
|
|
|
29
29
|
* @typedef {RawLayer[][]} RawStructure
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {{alias: string, path: string}[]} Aliases
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
32
|
// @level 2
|
|
37
33
|
/**
|
|
38
34
|
* @param {string} fileDir
|
|
@@ -105,37 +101,6 @@ const toStructure = (rawStructure, fileDir) => {
|
|
|
105
101
|
});
|
|
106
102
|
};
|
|
107
103
|
|
|
108
|
-
// @level 2
|
|
109
|
-
/**
|
|
110
|
-
* @param {Record<string, string>} rawAliases
|
|
111
|
-
* @returns {Aliases}
|
|
112
|
-
*/
|
|
113
|
-
const createAliases = (rawAliases) => {
|
|
114
|
-
return Object.keys(rawAliases)
|
|
115
|
-
.sort((a, b) => b.length - a.length)
|
|
116
|
-
.map((alias) => ({ alias, path: rawAliases[alias] }));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// @level 2
|
|
120
|
-
/**
|
|
121
|
-
* Replace an alias into the corresponding path
|
|
122
|
-
* @param {string} cwd
|
|
123
|
-
* @param {string} fileDir
|
|
124
|
-
* @param {Aliases} aliases
|
|
125
|
-
*/
|
|
126
|
-
const replaceAlias = (cwd, fileDir, aliases) => {
|
|
127
|
-
/**
|
|
128
|
-
* @param {string} moduleSource
|
|
129
|
-
*/
|
|
130
|
-
return (moduleSource) => {
|
|
131
|
-
const { alias, path } =
|
|
132
|
-
aliases.find(({ alias }) => moduleSource.startsWith(alias)) || {};
|
|
133
|
-
if (!alias) return moduleSource;
|
|
134
|
-
const modulePath = resolvePath(cwd, moduleSource.replace(alias, path));
|
|
135
|
-
return toRelative(fileDir, modulePath);
|
|
136
|
-
};
|
|
137
|
-
};
|
|
138
|
-
|
|
139
104
|
// @level 2
|
|
140
105
|
/**
|
|
141
106
|
* Find the layer level for a module
|
|
@@ -238,8 +203,6 @@ const readStructure = (dir) => {
|
|
|
238
203
|
module.exports = {
|
|
239
204
|
readRawStructure,
|
|
240
205
|
toStructure,
|
|
241
|
-
createAliases,
|
|
242
|
-
replaceAlias,
|
|
243
206
|
findLevel,
|
|
244
207
|
findLayerWithReducedPath,
|
|
245
208
|
isNotRegisteredNodeModule,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ...
|
|
3
|
+
* @author Hodoug Joung
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const { minimatch } = require("minimatch");
|
|
8
|
+
const helpers = require("../helpers/noDisallowedImports");
|
|
9
|
+
const { createAliases } = require("../helpers/common");
|
|
10
|
+
|
|
11
|
+
//------------------------------------------------------------------------------
|
|
12
|
+
// Rule Definition
|
|
13
|
+
//------------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('eslint').Rule.Node} Node
|
|
17
|
+
* @typedef {import('eslint').AST.Token} Token
|
|
18
|
+
* @typedef {import('eslint').SourceCode} SourceCode
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* import: { member: string[], from: string },
|
|
21
|
+
* allow: string[],
|
|
22
|
+
* disallow: string[],
|
|
23
|
+
* }} ImportPath
|
|
24
|
+
* @typedef {{
|
|
25
|
+
* imports: ImportPath[],
|
|
26
|
+
* aliases: Record<string, string>
|
|
27
|
+
* }} Options
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const DEFAULT = "default";
|
|
31
|
+
|
|
32
|
+
const importSchema = {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
import: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
member: {
|
|
39
|
+
type: "array",
|
|
40
|
+
items: [{ type: "string" }], // 'default' means default specifier
|
|
41
|
+
},
|
|
42
|
+
from: { type: "string" },
|
|
43
|
+
},
|
|
44
|
+
required: ["member", "from"],
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
allow: {
|
|
48
|
+
type: "array",
|
|
49
|
+
items: [{ type: "string" }],
|
|
50
|
+
},
|
|
51
|
+
disallow: {
|
|
52
|
+
type: "array",
|
|
53
|
+
items: [{ type: "string" }],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["import"],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const aliasesSchema = {
|
|
61
|
+
type: "object",
|
|
62
|
+
patternProperties: {
|
|
63
|
+
["^.+$"]: {
|
|
64
|
+
type: "string",
|
|
65
|
+
pattern: "^\\.{1,2}(/[^/]+)*/?$",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
72
|
+
module.exports = {
|
|
73
|
+
meta: {
|
|
74
|
+
type: "problem",
|
|
75
|
+
fixable: "code",
|
|
76
|
+
schema: {
|
|
77
|
+
type: "array",
|
|
78
|
+
items: [
|
|
79
|
+
{
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
imports: {
|
|
83
|
+
type: "array",
|
|
84
|
+
items: [importSchema],
|
|
85
|
+
},
|
|
86
|
+
aliases: aliasesSchema,
|
|
87
|
+
},
|
|
88
|
+
required: ["imports"],
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
additionalItems: false,
|
|
93
|
+
},
|
|
94
|
+
messages: {
|
|
95
|
+
"no-disallowed-imports": "'{{member}}' should NOT be imported",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
create(context) {
|
|
99
|
+
/**
|
|
100
|
+
* @type {Options}
|
|
101
|
+
*/
|
|
102
|
+
const options = {
|
|
103
|
+
imports: [],
|
|
104
|
+
aliases: {},
|
|
105
|
+
...(context.options[0] || {}),
|
|
106
|
+
};
|
|
107
|
+
const { fileDir, fileSource } = helpers.parseFileSource(context.filename);
|
|
108
|
+
const createModulePath = helpers.createModulePath(
|
|
109
|
+
context.cwd,
|
|
110
|
+
fileDir,
|
|
111
|
+
createAliases(options.aliases)
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
ImportDeclaration(node) {
|
|
115
|
+
const modulePath = createModulePath(node.source.value);
|
|
116
|
+
/**
|
|
117
|
+
* @param {[string | undefined, ImportPath | undefined]} param0
|
|
118
|
+
* @param {ImportPath} importSpec
|
|
119
|
+
*/
|
|
120
|
+
const searchTheImportPath = (
|
|
121
|
+
[theMember, theImportSpec],
|
|
122
|
+
importSpec
|
|
123
|
+
) => {
|
|
124
|
+
if (theMember && theImportSpec) {
|
|
125
|
+
return [theMember, theImportSpec];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// const { member, from } = importSpec.import;
|
|
129
|
+
|
|
130
|
+
const importedSpecifiers = node.specifiers.map((specifier) => {
|
|
131
|
+
if (specifier.type === "ImportSpecifier")
|
|
132
|
+
return specifier.imported.name;
|
|
133
|
+
if (specifier.type === "ImportDefaultSpecifier") return DEFAULT;
|
|
134
|
+
return "";
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const member = importSpec.import.member.find((specifier) =>
|
|
138
|
+
importedSpecifiers.some((sp) => sp === specifier)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (member && minimatch(modulePath, importSpec.import.from)) {
|
|
142
|
+
if (member === DEFAULT) {
|
|
143
|
+
return [
|
|
144
|
+
node.specifiers.find(
|
|
145
|
+
({ type }) => type === "ImportDefaultSpecifier"
|
|
146
|
+
).local.name,
|
|
147
|
+
importSpec,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
return [member, importSpec];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return [undefined, undefined];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const [theMember, theImportSpec] = options.imports.reduce(
|
|
157
|
+
searchTheImportPath,
|
|
158
|
+
[undefined, undefined]
|
|
159
|
+
);
|
|
160
|
+
if (!theMember || !theImportSpec) return;
|
|
161
|
+
|
|
162
|
+
const { allow, disallow } = theImportSpec;
|
|
163
|
+
const isAllowed =
|
|
164
|
+
(allow
|
|
165
|
+
? Boolean(allow.find((pattern) => minimatch(fileSource, pattern)))
|
|
166
|
+
: true) &&
|
|
167
|
+
(disallow
|
|
168
|
+
? !disallow.find((pattern) => minimatch(fileSource, pattern))
|
|
169
|
+
: true);
|
|
170
|
+
if (isAllowed) return;
|
|
171
|
+
|
|
172
|
+
context.report({
|
|
173
|
+
node,
|
|
174
|
+
messageId: "no-disallowed-imports",
|
|
175
|
+
data: { member: theMember },
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
};
|
|
@@ -11,6 +11,8 @@ const path = require("path");
|
|
|
11
11
|
// Rule Definition
|
|
12
12
|
//------------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
+
// FIXME: const Body = styled.div``; const Body2 = styled(Body)`` (TaggedTemplateExpression)
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* @typedef {import('eslint').Rule.Node} Node
|
|
16
18
|
* @typedef {import('eslint').AST.Token} Token
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
7
|
|
|
8
|
-
const { parsePath } = require("../helpers/common");
|
|
9
8
|
//------------------------------------------------------------------------------
|
|
10
9
|
// Requirements
|
|
11
10
|
//------------------------------------------------------------------------------
|
|
12
11
|
|
|
12
|
+
const { parsePath, createAliases } = require("../helpers/common");
|
|
13
13
|
const helper = require("../helpers/stratifiedImports");
|
|
14
14
|
|
|
15
15
|
//------------------------------------------------------------------------------
|
|
@@ -87,7 +87,7 @@ module.exports = {
|
|
|
87
87
|
context.cwd,
|
|
88
88
|
options.excludeImports,
|
|
89
89
|
fileDir,
|
|
90
|
-
|
|
90
|
+
createAliases(options.aliases)
|
|
91
91
|
);
|
|
92
92
|
|
|
93
93
|
const isInChildren = helper.isInChildren(fileDir);
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview test for helpers/common.js
|
|
3
|
+
* @author Hodoug Joung
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
//------------------------------------------------------------------------------
|
|
8
|
+
// Requirements
|
|
9
|
+
//------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const assert = require("assert");
|
|
12
|
+
const { createAliases, replaceAlias } = require("../../../lib/helpers/common");
|
|
13
|
+
|
|
14
|
+
//------------------------------------------------------------------------------
|
|
15
|
+
// Tests
|
|
16
|
+
//------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
describe("helpers/common", () => {
|
|
19
|
+
describe("createAliases()", () => {
|
|
20
|
+
const testCases = [
|
|
21
|
+
{
|
|
22
|
+
rawAliases: { "@/": "./src/" },
|
|
23
|
+
aliases: [{ alias: "@/", path: "./src/" }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
rawAliases: { "@/": "./src/", "@layer/": "./src/layer/" },
|
|
27
|
+
aliases: [
|
|
28
|
+
{ alias: "@layer/", path: "./src/layer/" },
|
|
29
|
+
{ alias: "@/", path: "./src/" },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
testCases.forEach(({ rawAliases, aliases }) => {
|
|
34
|
+
it(`${JSON.stringify(rawAliases)} -> ${JSON.stringify(aliases)}`, () => {
|
|
35
|
+
assert.deepEqual(createAliases(rawAliases), aliases);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("replaceAlias()", () => {
|
|
41
|
+
const cwd = "/proj";
|
|
42
|
+
const fileDir = "/proj/src/layerA";
|
|
43
|
+
const aliases = [{ alias: "@/", path: "./src/" }];
|
|
44
|
+
const testCases = [
|
|
45
|
+
{ moduleSource: "@/layerA/layerAA", relPath: "./layerAA" },
|
|
46
|
+
{ moduleSource: "nodeModule", relPath: "nodeModule" },
|
|
47
|
+
];
|
|
48
|
+
testCases.forEach(({ moduleSource, relPath }) => {
|
|
49
|
+
it(`${moduleSource} -> ${relPath}`, () => {
|
|
50
|
+
assert.equal(
|
|
51
|
+
replaceAlias(cwd, fileDir, aliases)(moduleSource),
|
|
52
|
+
relPath
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -13,8 +13,6 @@ const {
|
|
|
13
13
|
findLevel,
|
|
14
14
|
findLayerWithReducedPath,
|
|
15
15
|
toStructure,
|
|
16
|
-
createAliases,
|
|
17
|
-
replaceAlias,
|
|
18
16
|
validateRawStructure,
|
|
19
17
|
isInParent,
|
|
20
18
|
isInChildren,
|
|
@@ -52,45 +50,6 @@ describe("helpers/stratified-imports", () => {
|
|
|
52
50
|
});
|
|
53
51
|
});
|
|
54
52
|
|
|
55
|
-
describe("createAliases()", () => {
|
|
56
|
-
const testCases = [
|
|
57
|
-
{
|
|
58
|
-
rawAliases: { "@/": "./src/" },
|
|
59
|
-
aliases: [{ alias: "@/", path: "./src/" }],
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
rawAliases: { "@/": "./src/", "@layer/": "./src/layer/" },
|
|
63
|
-
aliases: [
|
|
64
|
-
{ alias: "@layer/", path: "./src/layer/" },
|
|
65
|
-
{ alias: "@/", path: "./src/" },
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
testCases.forEach(({ rawAliases, aliases }) => {
|
|
70
|
-
it(`${JSON.stringify(rawAliases)} -> ${JSON.stringify(aliases)}`, () => {
|
|
71
|
-
assert.deepEqual(createAliases(rawAliases), aliases);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("replaceAlias()", () => {
|
|
77
|
-
const cwd = "/proj";
|
|
78
|
-
const fileDir = "/proj/src/layerA";
|
|
79
|
-
const aliases = [{ alias: "@/", path: "./src/" }];
|
|
80
|
-
const testCases = [
|
|
81
|
-
{ moduleSource: "@/layerA/layerAA", relPath: "./layerAA" },
|
|
82
|
-
{ moduleSource: "nodeModule", relPath: "nodeModule" },
|
|
83
|
-
];
|
|
84
|
-
testCases.forEach(({ moduleSource, relPath }) => {
|
|
85
|
-
it(`${moduleSource} -> ${relPath}`, () => {
|
|
86
|
-
assert.equal(
|
|
87
|
-
replaceAlias(cwd, fileDir, aliases)(moduleSource),
|
|
88
|
-
relPath
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
53
|
describe("findLevel()", () => {
|
|
95
54
|
const structure = [[{ name: "/src/layerA" }], [{ name: "/src/layerB" }]];
|
|
96
55
|
const testCases = [
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Allow or disallow importing specified modules
|
|
3
|
+
* @author Hodoug Joung
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
//------------------------------------------------------------------------------
|
|
8
|
+
// Requirements
|
|
9
|
+
//------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const rule = require("../../../lib/rules/no-disallowed-imports");
|
|
12
|
+
const RuleTester = require("eslint").RuleTester;
|
|
13
|
+
|
|
14
|
+
//------------------------------------------------------------------------------
|
|
15
|
+
// Tests
|
|
16
|
+
//------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const ruleTester = new RuleTester({
|
|
19
|
+
parserOptions: {
|
|
20
|
+
ecmaVersion: 2022,
|
|
21
|
+
sourceType: "module",
|
|
22
|
+
ecmaFeatures: {
|
|
23
|
+
jsx: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
ruleTester.run("no-disallowed-imports", rule, {
|
|
29
|
+
valid: [
|
|
30
|
+
{
|
|
31
|
+
code: "import { foo } from './fileA'",
|
|
32
|
+
filename: "./src/fileB.js",
|
|
33
|
+
options: [],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: "import { foo } from './fileA'",
|
|
37
|
+
filename: "./src/fileB.js",
|
|
38
|
+
options: [
|
|
39
|
+
{
|
|
40
|
+
imports: [{ import: { member: ["foo"], from: "**/src/fileA" } }],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: "import { foo } from './fileA'",
|
|
46
|
+
filename: "./src/fileB.js",
|
|
47
|
+
options: [
|
|
48
|
+
{
|
|
49
|
+
imports: [
|
|
50
|
+
{
|
|
51
|
+
import: { member: ["baz"], from: "**/src/fileC" },
|
|
52
|
+
allow: ["**/src/**/*.js"],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
import: { member: ["foo", "bar"], from: "**/src/fileA" },
|
|
56
|
+
allow: ["**/src/**/*.js"],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
code: "import { foo } from './fileA'",
|
|
64
|
+
filename: "./src/fileB.js",
|
|
65
|
+
options: [
|
|
66
|
+
{
|
|
67
|
+
imports: [
|
|
68
|
+
{
|
|
69
|
+
import: { member: ["foo", "bar"], from: "**/src/fileA" },
|
|
70
|
+
disallow: ["**/src/**/*.test.js"],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
code: "import { foo } from './fileA'",
|
|
78
|
+
filename: "./src/fileB.js",
|
|
79
|
+
options: [
|
|
80
|
+
{
|
|
81
|
+
imports: [
|
|
82
|
+
{
|
|
83
|
+
import: { member: ["foo", "bar"], from: "**/src/fileA" },
|
|
84
|
+
allow: ["**/src/**/*.js"],
|
|
85
|
+
disallow: ["**/src/**/*.test.js"],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
code: "import { foo } from '@/fileA'",
|
|
93
|
+
filename: "./src/fileB.js",
|
|
94
|
+
options: [
|
|
95
|
+
{
|
|
96
|
+
imports: [
|
|
97
|
+
{
|
|
98
|
+
import: { member: ["foo"], from: "**/src/fileA" },
|
|
99
|
+
allow: ["**/src/**/*.js"],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
aliases: { "@/": "./src/" },
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
code: "import foo from './fileA'",
|
|
108
|
+
filename: "./src/fileB.js",
|
|
109
|
+
options: [
|
|
110
|
+
{
|
|
111
|
+
imports: [
|
|
112
|
+
{
|
|
113
|
+
import: { member: ["default"], from: "**/src/fileA" },
|
|
114
|
+
allow: ["**/src/**/*.js"],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
invalid: [
|
|
122
|
+
{
|
|
123
|
+
code: "import { foo } from './fileA'",
|
|
124
|
+
filename: "./src/fileB.js",
|
|
125
|
+
options: [
|
|
126
|
+
{
|
|
127
|
+
imports: [
|
|
128
|
+
{
|
|
129
|
+
import: { member: ["foo"], from: "**/src/fileA" },
|
|
130
|
+
allow: ["**/src/**/*.test.js"],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
errors: [{ messageId: "no-disallowed-imports", data: { member: "foo" } }],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
code: "import { foo } from './fileA'",
|
|
139
|
+
filename: "./src/fileB.js",
|
|
140
|
+
options: [
|
|
141
|
+
{
|
|
142
|
+
imports: [
|
|
143
|
+
{
|
|
144
|
+
import: { member: ["foo"], from: "**/src/fileA" },
|
|
145
|
+
disallow: ["**/src/**/*.js"],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
errors: [{ messageId: "no-disallowed-imports", data: { member: "foo" } }],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
code: "import { foo } from './fileA'",
|
|
154
|
+
filename: "./src/fileB.js",
|
|
155
|
+
options: [
|
|
156
|
+
{
|
|
157
|
+
imports: [
|
|
158
|
+
{
|
|
159
|
+
import: { member: ["foo"], from: "**/src/fileA" },
|
|
160
|
+
allow: ["**/src/**/*.js"],
|
|
161
|
+
disallow: ["**/src/**/fileB.*"],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
errors: [{ messageId: "no-disallowed-imports", data: { member: "foo" } }],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
code: "import foo from './fileA'",
|
|
170
|
+
filename: "./src/fileB.js",
|
|
171
|
+
options: [
|
|
172
|
+
{
|
|
173
|
+
imports: [
|
|
174
|
+
{
|
|
175
|
+
import: { member: ["default"], from: "**/src/fileA" },
|
|
176
|
+
disallow: ["**/src/**/*.js"],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
errors: [{ messageId: "no-disallowed-imports", data: { member: "foo" } }],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
});
|