@open-xchange/linter-presets 0.1.6 → 0.1.7
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/CHANGELOG.md +7 -1
- package/dist/eslint/env/project.d.ts +11 -1
- package/dist/eslint/env/project.js +9 -1
- package/dist/eslint/rules/no-invalid-hierarchy.d.ts +25 -0
- package/dist/eslint/rules/no-invalid-hierarchy.js +85 -0
- package/dist/eslint/rules/no-invalid-modules.d.ts +2 -30
- package/dist/eslint/rules/no-invalid-modules.js +19 -128
- package/dist/eslint/shared/rule-utils.d.ts +93 -28
- package/dist/eslint/shared/rule-utils.js +165 -31
- package/doc/eslint/env/project.md +26 -33
- package/package.json +1 -1
- package/test/src/project1/file1.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.7] - 2024-07-23
|
|
4
|
+
|
|
5
|
+
- changed: [ESLint] reverted option `modules` for `env.project` (introduced in 0.1.6)
|
|
6
|
+
- changed: [ESLint] `env.project`: renamed option `packages` to `hierarchy`
|
|
7
|
+
- chore: [ESLint] split rule `env-project/no-invalid-modules` (added `env-project/no-invalid-hierarchy`)
|
|
8
|
+
|
|
3
9
|
## [0.1.6] - 2024-07-23
|
|
4
10
|
|
|
5
11
|
- changed: [ESLint] moved rule options for `env.project` into own `modules` option
|
|
@@ -31,7 +37,7 @@
|
|
|
31
37
|
|
|
32
38
|
## [0.0.6] - 2024-07-10
|
|
33
39
|
|
|
34
|
-
- fixed: [ESLint] type error in `env-project/no-invalid-modules`
|
|
40
|
+
- fixed: [ESLint] type error in rule `env-project/no-invalid-modules`
|
|
35
41
|
|
|
36
42
|
## [0.0.5] - 2024-07-10
|
|
37
43
|
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import type { TSESLint } from "@typescript-eslint/utils";
|
|
2
2
|
import type { EnvBaseOptions } from "../shared/env-utils.js";
|
|
3
|
+
import type { SharedRuleSettings } from "../shared/rule-utils.js";
|
|
3
4
|
import { type RuleNoInvalidModulesOptions } from "../rules/no-invalid-modules.js";
|
|
5
|
+
import { type RuleNoInvalidHierarchyOptions } from "../rules/no-invalid-hierarchy.js";
|
|
4
6
|
/**
|
|
5
7
|
* Configuration options for the environment preset "env.project".
|
|
6
8
|
*/
|
|
7
9
|
export interface EnvProjectOptions extends EnvBaseOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Maps all alias prefixes to actual paths in the project.
|
|
12
|
+
*/
|
|
13
|
+
alias?: SharedRuleSettings["alias"];
|
|
8
14
|
/**
|
|
9
15
|
* Options to be passed to the rule "env-project/no-invalid-modules".
|
|
10
16
|
*/
|
|
11
|
-
|
|
17
|
+
external?: RuleNoInvalidModulesOptions["external"];
|
|
18
|
+
/**
|
|
19
|
+
* Options to be passed to the rule "env-project/no-invalid-hierarchy".
|
|
20
|
+
*/
|
|
21
|
+
hierarchy?: RuleNoInvalidHierarchyOptions;
|
|
12
22
|
}
|
|
13
23
|
/**
|
|
14
24
|
* Adds custom linter rules for checking project setup and module hierarchy.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { concatConfigs, createConfig, customRules } from "../shared/env-utils.js";
|
|
2
2
|
import noAmdModuleDirective from "../rules/no-amd-module-directive.js";
|
|
3
3
|
import noInvalidModules from "../rules/no-invalid-modules.js";
|
|
4
|
+
import noInvalidHierarchy from "../rules/no-invalid-hierarchy.js";
|
|
4
5
|
// exports ====================================================================
|
|
5
6
|
/**
|
|
6
7
|
* Adds custom linter rules for checking project setup and module hierarchy.
|
|
@@ -20,13 +21,20 @@ export default function project(envOptions) {
|
|
|
20
21
|
rules: {
|
|
21
22
|
"no-amd-module-directive": noAmdModuleDirective,
|
|
22
23
|
"no-invalid-modules": noInvalidModules,
|
|
24
|
+
"no-invalid-hierarchy": noInvalidHierarchy,
|
|
23
25
|
},
|
|
24
26
|
},
|
|
25
27
|
},
|
|
28
|
+
settings: {
|
|
29
|
+
"env-project": {
|
|
30
|
+
alias: envOptions.alias,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
26
33
|
}),
|
|
27
34
|
// custom rules
|
|
28
35
|
customRules(envOptions, {
|
|
29
36
|
"env-project/no-amd-module-directive": "error",
|
|
30
|
-
"env-project/no-invalid-modules": ["error", envOptions.
|
|
37
|
+
"env-project/no-invalid-modules": ["error", { external: envOptions.external }],
|
|
38
|
+
"env-project/no-invalid-hierarchy": envOptions.hierarchy ? ["error", envOptions.hierarchy] : "off",
|
|
31
39
|
}));
|
|
32
40
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
export interface RuleNoInvalidHierarchyPackage {
|
|
3
|
+
/**
|
|
4
|
+
* Glob patterns selecting all source files that are part of the package.
|
|
5
|
+
*/
|
|
6
|
+
files: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Specifies the names of all packages (keys of the `packages` dictionary)
|
|
9
|
+
* this package depends on.
|
|
10
|
+
*/
|
|
11
|
+
extends?: string | string[];
|
|
12
|
+
/**
|
|
13
|
+
* Set to `true` to mark an optional package that may be missing in an
|
|
14
|
+
* installation. Such a package cannot be imported statically (with
|
|
15
|
+
* `import` statement) from a non-optional package, but can only be loaded
|
|
16
|
+
* dynamically at runtime (by calling `import()`). The rule will mark all
|
|
17
|
+
* static imports of optional code as an error. Default value is `false`.
|
|
18
|
+
*/
|
|
19
|
+
optional?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type RuleNoInvalidHierarchyOptions = Record<string, RuleNoInvalidHierarchyPackage>;
|
|
22
|
+
type RuleOptions = [RuleNoInvalidHierarchyOptions];
|
|
23
|
+
type RuleMessageIds = "UNEXPECTED_OPTIONAL_STATIC" | "INVALID_PACKAGE_HIERARCHY";
|
|
24
|
+
declare const _default: ESLintUtils.RuleModule<RuleMessageIds, RuleOptions, ESLintUtils.RuleListener>;
|
|
25
|
+
export default _default;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AST_NODE_TYPES as NodeType, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { Schema, makeArray, ProjectContext } from "../shared/rule-utils.js";
|
|
3
|
+
// exports ====================================================================
|
|
4
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
schema: [
|
|
8
|
+
// single options object
|
|
9
|
+
Schema.dictionary(Schema.options({
|
|
10
|
+
files: Schema.maybeArray(Schema.stringNE()),
|
|
11
|
+
extends: Schema.maybeArray(Schema.stringNE()),
|
|
12
|
+
optional: Schema.boolean(),
|
|
13
|
+
}, ["files"])),
|
|
14
|
+
],
|
|
15
|
+
messages: {
|
|
16
|
+
UNEXPECTED_OPTIONAL_STATIC: "Unexpected static import of optional package.",
|
|
17
|
+
INVALID_PACKAGE_HIERARCHY: "Low-level package cannot import modules from higher-level package.",
|
|
18
|
+
},
|
|
19
|
+
fixable: "code",
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [{}],
|
|
22
|
+
create(context, [options]) {
|
|
23
|
+
// create the project context with aliases, root path, own module name, etc.
|
|
24
|
+
const projectContext = new ProjectContext(context);
|
|
25
|
+
const hierarchyMap = new Map();
|
|
26
|
+
for (const [key, settings] of Object.entries(options)) {
|
|
27
|
+
hierarchyMap.set(key, {
|
|
28
|
+
...settings,
|
|
29
|
+
extends: makeArray(settings.extends),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// collect all globs for package members
|
|
33
|
+
const packagesGlobs = Array.from(hierarchyMap.values(), settings => settings.files).flat();
|
|
34
|
+
return {
|
|
35
|
+
"ImportDeclaration, TSExternalModuleReference, ImportExpression, ExportAllDeclaration, ExportNamedDeclaration"(node) {
|
|
36
|
+
// import/export statement must contain string literal
|
|
37
|
+
const importWrapper = projectContext.resolveImportNode(node);
|
|
38
|
+
if (!importWrapper) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// check dependencies if the imported module is covered in the configuration
|
|
42
|
+
if (!importWrapper.matchModuleName(packagesGlobs)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// check package dependencies
|
|
46
|
+
// whether an invalid static import has been found
|
|
47
|
+
let invalidStatic = false;
|
|
48
|
+
// recursively checks the imported module for dependency violations
|
|
49
|
+
const checkDependencies = (settings, root) => {
|
|
50
|
+
// only allow static imports from specified package, if it is not optional
|
|
51
|
+
if (importWrapper.matchModuleName(settings.files)) {
|
|
52
|
+
if (!root && (node.type !== NodeType.ImportExpression) && settings.optional) {
|
|
53
|
+
invalidStatic = true;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// do not traverse into dependencies of optional modules
|
|
58
|
+
if (!settings.extends.length || (!root && settings.optional)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// allow imports from any of the dependent packages
|
|
62
|
+
return settings.extends.some(key => {
|
|
63
|
+
const package2 = hierarchyMap.get(key);
|
|
64
|
+
return !!package2 && checkDependencies(package2, false);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
// check dependencies of all configured packages (not for anonymous modules)
|
|
68
|
+
let invalidDeps = false;
|
|
69
|
+
for (const settings of hierarchyMap.values()) {
|
|
70
|
+
if (projectContext.matchModuleName(settings.files) && !checkDependencies(settings, true)) {
|
|
71
|
+
invalidDeps = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// report package hierarchy errors
|
|
76
|
+
if (invalidStatic) {
|
|
77
|
+
context.report({ messageId: "UNEXPECTED_OPTIONAL_STATIC", node: importWrapper.sourceNode });
|
|
78
|
+
}
|
|
79
|
+
else if (invalidDeps) {
|
|
80
|
+
context.report({ messageId: "INVALID_PACKAGE_HIERARCHY", node: importWrapper.sourceNode });
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
});
|
|
@@ -1,38 +1,10 @@
|
|
|
1
1
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
export interface RuleNoInvalidModulesPackage {
|
|
3
|
-
/**
|
|
4
|
-
* Glob patterns selecting all source files that are part of the package.
|
|
5
|
-
*/
|
|
6
|
-
src: string | string[];
|
|
7
|
-
/**
|
|
8
|
-
* Specifies the names of all packages (keys of the `packages` dictionary)
|
|
9
|
-
* this package depends on.
|
|
10
|
-
*/
|
|
11
|
-
extends?: string | string[];
|
|
12
|
-
/**
|
|
13
|
-
* Set to `true` to mark an optional package that may be missing in an
|
|
14
|
-
* installation. Such a package cannot be imported statically (with
|
|
15
|
-
* `import` statement) from a non-optional package, but can only be loaded
|
|
16
|
-
* dynamically at runtime (by calling `import()`). The rule will mark all
|
|
17
|
-
* static imports of optional code as an error. Default value is `false`.
|
|
18
|
-
*/
|
|
19
|
-
optional?: boolean;
|
|
20
|
-
}
|
|
21
2
|
export interface RuleNoInvalidModulesOptions {
|
|
22
|
-
/**
|
|
23
|
-
* Maps all alias prefixes to actual paths in the project.
|
|
24
|
-
*/
|
|
25
|
-
alias?: Record<string, string>;
|
|
26
3
|
/**
|
|
27
4
|
* Specifies glob patterns for external modules.
|
|
28
5
|
*/
|
|
29
|
-
external?: string
|
|
30
|
-
/**
|
|
31
|
-
* Allows to separate the source files into virtual packages.
|
|
32
|
-
*/
|
|
33
|
-
packages?: Record<string, RuleNoInvalidModulesPackage>;
|
|
6
|
+
external?: string[];
|
|
34
7
|
}
|
|
35
8
|
type RuleOptions = [Required<RuleNoInvalidModulesOptions>];
|
|
36
|
-
|
|
37
|
-
declare const _default: ESLintUtils.RuleModule<RuleMessageIds, RuleOptions, ESLintUtils.RuleListener>;
|
|
9
|
+
declare const _default: ESLintUtils.RuleModule<"SOURCE_FILE_NOT_FOUND", RuleOptions, ESLintUtils.RuleListener>;
|
|
38
10
|
export default _default;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { posix, dirname, extname } from "node:path";
|
|
3
|
-
import { findUpSync } from "find-up";
|
|
4
1
|
import { AST_NODE_TYPES as NodeType, ESLintUtils } from "@typescript-eslint/utils";
|
|
5
|
-
import { Schema,
|
|
6
|
-
// constants ==================================================================
|
|
7
|
-
const FILE_EXTENSIONS = ["js", "ts", "d.ts"];
|
|
2
|
+
import { Schema, ProjectContext } from "../shared/rule-utils.js";
|
|
8
3
|
// exports ====================================================================
|
|
9
4
|
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
10
5
|
meta: {
|
|
@@ -12,152 +7,48 @@ export default ESLintUtils.RuleCreator.withoutDocs({
|
|
|
12
7
|
schema: [
|
|
13
8
|
// single options object
|
|
14
9
|
Schema.options({
|
|
15
|
-
|
|
16
|
-
external: Schema.maybeArray(Schema.stringNE()),
|
|
17
|
-
packages: Schema.dictionary(Schema.options({
|
|
18
|
-
src: Schema.maybeArray(Schema.stringNE()),
|
|
19
|
-
extends: Schema.maybeArray(Schema.stringNE()),
|
|
20
|
-
optional: Schema.boolean(),
|
|
21
|
-
}, ["src"])),
|
|
10
|
+
external: Schema.array(Schema.stringNE()),
|
|
22
11
|
}),
|
|
23
12
|
],
|
|
24
13
|
messages: {
|
|
25
14
|
SOURCE_FILE_NOT_FOUND: "Source file for '{{moduleName}}' not found.",
|
|
26
|
-
UNEXPECTED_OPTIONAL_STATIC: "Unexpected static import of optional package.",
|
|
27
|
-
INVALID_PACKAGE_HIERARCHY: "Low-level package cannot import modules from higher-level package.",
|
|
28
15
|
},
|
|
29
16
|
fixable: "code",
|
|
30
17
|
},
|
|
31
18
|
defaultOptions: [{
|
|
32
|
-
alias: {},
|
|
33
19
|
external: [],
|
|
34
|
-
packages: {},
|
|
35
20
|
}],
|
|
36
21
|
create(context, [options]) {
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
const packagesMap = new Map();
|
|
40
|
-
for (const [key, settings] of Object.entries(options.packages)) {
|
|
41
|
-
packagesMap.set(key, {
|
|
42
|
-
...settings,
|
|
43
|
-
src: makeArray(settings.src),
|
|
44
|
-
extends: makeArray(settings.extends),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
// collect all globs for package members
|
|
48
|
-
const packagesGlobs = Array.from(packagesMap.values(), settings => settings.src).flat();
|
|
49
|
-
// resolve file name
|
|
50
|
-
const configPath = findUpSync("eslint.config.js");
|
|
51
|
-
const rootDir = configPath && toPosixPath(dirname(configPath));
|
|
52
|
-
const fileName = toPosixPath(context.filename);
|
|
53
|
-
if (!rootDir || !fileName.startsWith(rootDir + "/")) {
|
|
54
|
-
throw new Error("invalid root directory");
|
|
55
|
-
}
|
|
56
|
-
// path of current module (slice rootDir with "/" from start, and extension with "." from end)
|
|
57
|
-
const fileExt = extname(fileName);
|
|
58
|
-
const selfModulePath = fileName.slice(rootDir.length + 1, -fileExt.length);
|
|
59
|
-
if (!selfModulePath) {
|
|
60
|
-
throw new Error("invalid own module path");
|
|
61
|
-
}
|
|
62
|
-
// replace alias path with alias key
|
|
63
|
-
let selfModuleName = selfModulePath;
|
|
64
|
-
for (const [aliasKey, aliasPath] of aliasMap) {
|
|
65
|
-
if (selfModulePath.startsWith(aliasPath + "/")) {
|
|
66
|
-
selfModuleName = aliasKey + "/" + selfModulePath.slice(aliasPath.length + 1);
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// returns an existing alias key used by the passed module name
|
|
71
|
-
const resolveAlias = (moduleName) => {
|
|
72
|
-
const [key, ...rest] = moduleName.split("/");
|
|
73
|
-
const aliasPath = (key && rest[0]) ? aliasMap.get(key) : undefined;
|
|
74
|
-
const aliasKey = aliasPath ? key : "";
|
|
75
|
-
const modulePath = aliasPath ? posix.join(aliasPath, ...rest) : moduleName;
|
|
76
|
-
return [aliasKey, modulePath];
|
|
77
|
-
};
|
|
78
|
-
// returns whether a source file exists for the specified module
|
|
79
|
-
const fileExists = (resolvedName) => {
|
|
80
|
-
// check modules with explicit extension
|
|
81
|
-
const resolvedPath = posix.join(rootDir, resolvedName);
|
|
82
|
-
if (extname(resolvedName)) {
|
|
83
|
-
return isFile(resolvedPath);
|
|
84
|
-
}
|
|
85
|
-
// search for a file with a known extension
|
|
86
|
-
return FILE_EXTENSIONS.some(ext => isFile(resolvedPath + "." + ext));
|
|
87
|
-
};
|
|
88
|
-
// returns whether the passed module name is an installed NPM package
|
|
89
|
-
const requireModule = createRequire(configPath);
|
|
90
|
-
const packageExists = (moduleName) => {
|
|
91
|
-
try {
|
|
92
|
-
requireModule.resolve(moduleName);
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
22
|
+
// create the project context with aliases, root path, own module name, etc.
|
|
23
|
+
const projectContext = new ProjectContext(context);
|
|
99
24
|
return {
|
|
100
25
|
"ImportDeclaration, TSExternalModuleReference, ImportExpression, ExportAllDeclaration, ExportNamedDeclaration"(node) {
|
|
101
26
|
// import/export statement must contain string literal
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
27
|
+
const importWrapper = projectContext.resolveImportNode(node);
|
|
28
|
+
if (!importWrapper) {
|
|
104
29
|
return;
|
|
105
30
|
}
|
|
106
31
|
// skip glob patterns in TypeScript type imports
|
|
107
|
-
if ((node.type === NodeType.ImportDeclaration) && (node.importKind === "type") && moduleName.includes("*")) {
|
|
32
|
+
if ((node.type === NodeType.ImportDeclaration) && (node.importKind === "type") && importWrapper.moduleName.includes("*")) {
|
|
108
33
|
return;
|
|
109
34
|
}
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
// whether the import is a known external module
|
|
113
|
-
const isExternal = matchModuleName(moduleName, options.external);
|
|
114
|
-
// whether the import is an installed NPM package
|
|
115
|
-
const isPackage = !isExternal && !aliasKey && packageExists(moduleName);
|
|
116
|
-
// check existence of source file
|
|
117
|
-
if (!isExternal && !isPackage && !fileExists(modulePath)) {
|
|
118
|
-
context.report({ messageId: "SOURCE_FILE_NOT_FOUND", node: sourceNode, data: { moduleName } });
|
|
35
|
+
// accept known external module
|
|
36
|
+
if (importWrapper.matchModuleName(options.external)) {
|
|
119
37
|
return;
|
|
120
38
|
}
|
|
121
|
-
//
|
|
122
|
-
|
|
39
|
+
// extract alias key, replace with alias path
|
|
40
|
+
const [aliasKey, modulePath] = projectContext.resolveAlias(importWrapper.moduleName);
|
|
41
|
+
// accept installed NPM package
|
|
42
|
+
if (!aliasKey && projectContext.packageExists(modulePath)) {
|
|
123
43
|
return;
|
|
124
44
|
}
|
|
125
|
-
// check
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (matchModuleName(moduleName, settings.src)) {
|
|
132
|
-
if (!root && (node.type !== NodeType.ImportExpression) && settings.optional) {
|
|
133
|
-
invalidStatic = true;
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
// do not traverse into dependencies of optional modules
|
|
138
|
-
if (!settings.extends.length || (!root && settings.optional)) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
// allow imports from any of the dependent packages
|
|
142
|
-
return settings.extends.some(key => {
|
|
143
|
-
const package2 = packagesMap.get(key);
|
|
144
|
-
return !!package2 && checkDependencies(package2, false);
|
|
45
|
+
// check existence of source file
|
|
46
|
+
if (!projectContext.fileExists(modulePath)) {
|
|
47
|
+
context.report({
|
|
48
|
+
messageId: "SOURCE_FILE_NOT_FOUND",
|
|
49
|
+
node: importWrapper.sourceNode,
|
|
50
|
+
data: { moduleName: importWrapper.moduleName },
|
|
145
51
|
});
|
|
146
|
-
};
|
|
147
|
-
// check dependencies of all configured packages (not for anonymous modules)
|
|
148
|
-
let invalidDeps = false;
|
|
149
|
-
for (const settings of packagesMap.values()) {
|
|
150
|
-
if (matchModuleName(selfModuleName, settings.src) && !checkDependencies(settings, true)) {
|
|
151
|
-
invalidDeps = true;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// report package hierarchy errors
|
|
156
|
-
if (invalidStatic) {
|
|
157
|
-
context.report({ messageId: "UNEXPECTED_OPTIONAL_STATIC", node: sourceNode });
|
|
158
|
-
}
|
|
159
|
-
else if (invalidDeps) {
|
|
160
|
-
context.report({ messageId: "INVALID_PACKAGE_HIERARCHY", node: sourceNode });
|
|
161
52
|
}
|
|
162
53
|
},
|
|
163
54
|
};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { JSONSchema, TSESTree } from "@typescript-eslint/utils";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type Glob } from "picomatch";
|
|
2
|
+
import type { JSONSchema, TSESTree, TSESLint } from "@typescript-eslint/utils";
|
|
3
|
+
/**
|
|
4
|
+
* Shared settings used by multiple rules.
|
|
5
|
+
*/
|
|
6
|
+
export interface SharedRuleSettings {
|
|
7
|
+
/**
|
|
8
|
+
* Maps all alias prefixes to actual paths in the project.
|
|
9
|
+
*/
|
|
10
|
+
alias?: Record<string, string>;
|
|
7
11
|
}
|
|
12
|
+
export type ImportExportNode = TSESTree.ImportDeclaration | TSESTree.ImportExpression | TSESTree.ExportAllDeclaration | TSESTree.ExportNamedDeclaration | TSESTree.TSExternalModuleReference;
|
|
8
13
|
/**
|
|
9
14
|
* Helper functions to build a JSON schema for custom ESLint rules.
|
|
10
15
|
*/
|
|
@@ -49,28 +54,88 @@ export declare function toPosixPath(path: string): string;
|
|
|
49
54
|
* existing directories).
|
|
50
55
|
*/
|
|
51
56
|
export declare function isFile(path: string): boolean;
|
|
57
|
+
export declare class ImportNodeWrapper {
|
|
58
|
+
/** The string literal node containing the name of the imported module. */
|
|
59
|
+
readonly sourceNode: TSESTree.StringLiteral;
|
|
60
|
+
/** The original name of the imported module (with alias key). */
|
|
61
|
+
readonly moduleName: string;
|
|
62
|
+
constructor(node: TSESTree.StringLiteral);
|
|
63
|
+
/**
|
|
64
|
+
* Returns whether the name of the imported module matches the specified
|
|
65
|
+
* glob patterns.
|
|
66
|
+
*
|
|
67
|
+
* @param patterns
|
|
68
|
+
* The glob patterns to be matched against the module name.
|
|
69
|
+
*
|
|
70
|
+
* @returns
|
|
71
|
+
* Whether the name of the wrapped module matches at least one glob
|
|
72
|
+
* pattern.
|
|
73
|
+
*/
|
|
74
|
+
matchModuleName(patterns: Glob): boolean;
|
|
75
|
+
}
|
|
52
76
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @param moduleName
|
|
56
|
-
* The module name to be checked.
|
|
57
|
-
*
|
|
58
|
-
* @param patterns
|
|
59
|
-
* The glob patterns to be matched against the module name.
|
|
60
|
-
*
|
|
61
|
-
* @returns
|
|
62
|
-
* Whether the module name matches at least one glob pattern.
|
|
63
|
-
*/
|
|
64
|
-
export declare const matchModuleName: typeof pm.isMatch;
|
|
65
|
-
/**
|
|
66
|
-
* Extracts the source node and module name of an import/export statement node,
|
|
67
|
-
* or a dynamic import expression node.
|
|
77
|
+
* A custom context helper for the linter rules of the preset environment
|
|
78
|
+
* "env.project".
|
|
68
79
|
*
|
|
69
|
-
* @param
|
|
70
|
-
* The
|
|
71
|
-
*
|
|
72
|
-
* @returns
|
|
73
|
-
* The string literal node containing the module name, and the resulting
|
|
74
|
-
* extracted module name.
|
|
80
|
+
* @param context
|
|
81
|
+
* The rule context of the module currently linted.
|
|
75
82
|
*/
|
|
76
|
-
export declare
|
|
83
|
+
export declare class ProjectContext {
|
|
84
|
+
#private;
|
|
85
|
+
constructor(context: Readonly<TSESLint.RuleContext<any, any>>);
|
|
86
|
+
/**
|
|
87
|
+
* Extracts the source node and module name of an import/export statement
|
|
88
|
+
* node, or a dynamic import expression node.
|
|
89
|
+
*
|
|
90
|
+
* @param node
|
|
91
|
+
* The import/export statement node, or dynamic import expression node.
|
|
92
|
+
*
|
|
93
|
+
* @returns
|
|
94
|
+
* The string literal node containing the module name, and the resulting
|
|
95
|
+
* extracted module name.
|
|
96
|
+
*/
|
|
97
|
+
resolveImportNode(node: ImportExportNode): ImportNodeWrapper | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Extracts an existing alias key, and resolves the module path of a module
|
|
100
|
+
* name.
|
|
101
|
+
*
|
|
102
|
+
* @param moduleName
|
|
103
|
+
* The module name to extract an alias key from.
|
|
104
|
+
*
|
|
105
|
+
* @returns
|
|
106
|
+
* A pair containing the alias key (empty string if no alias found), and
|
|
107
|
+
* the resolved module path (passed module name if no alias found).
|
|
108
|
+
*/
|
|
109
|
+
resolveAlias(moduleName: string): [string, string];
|
|
110
|
+
/**
|
|
111
|
+
* Returns whether the name of the module currently linted matches the
|
|
112
|
+
* specified glob patterns.
|
|
113
|
+
*
|
|
114
|
+
* @param patterns
|
|
115
|
+
* The glob patterns to be matched against the current module name.
|
|
116
|
+
*
|
|
117
|
+
* @returns
|
|
118
|
+
* Whether the current module name matches at least one glob pattern.
|
|
119
|
+
*/
|
|
120
|
+
matchModuleName(patterns: Glob): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Returns whether a source file exists for the specified module.
|
|
123
|
+
*
|
|
124
|
+
* @param modulePath
|
|
125
|
+
* The resolved module path (alias key replaced with path).
|
|
126
|
+
*
|
|
127
|
+
* @returns
|
|
128
|
+
* Whether a source file exists for the specified module.
|
|
129
|
+
*/
|
|
130
|
+
fileExists(modulePath: string): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Returns whether the passed module name is an installed NPM package.
|
|
133
|
+
*
|
|
134
|
+
* @param moduleName
|
|
135
|
+
* The module name to be checked.
|
|
136
|
+
*
|
|
137
|
+
* @returns
|
|
138
|
+
* Whether the passed module name is an installed NPM package.
|
|
139
|
+
*/
|
|
140
|
+
packageExists(moduleName: string): boolean;
|
|
141
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { posix, sep } from "node:path";
|
|
1
|
+
import { posix, sep, dirname, extname } from "node:path";
|
|
2
2
|
import { lstatSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import pm from "picomatch";
|
|
5
|
+
import { findUpSync } from "find-up";
|
|
4
6
|
import { AST_NODE_TYPES as NodeType } from "@typescript-eslint/utils";
|
|
7
|
+
// constants ==================================================================
|
|
8
|
+
const FILE_EXTENSIONS = ["js", "ts", "d.ts"];
|
|
5
9
|
// Schema =====================================================================
|
|
6
10
|
/**
|
|
7
11
|
* Helper functions to build a JSON schema for custom ESLint rules.
|
|
@@ -73,37 +77,167 @@ export function isFile(path) {
|
|
|
73
77
|
return false;
|
|
74
78
|
}
|
|
75
79
|
}
|
|
80
|
+
// class ImportNodeWrapper ====================================================
|
|
81
|
+
export class ImportNodeWrapper {
|
|
82
|
+
/** The string literal node containing the name of the imported module. */
|
|
83
|
+
sourceNode;
|
|
84
|
+
/** The original name of the imported module (with alias key). */
|
|
85
|
+
moduleName;
|
|
86
|
+
// constructor ------------------------------------------------------------
|
|
87
|
+
constructor(node) {
|
|
88
|
+
this.sourceNode = node;
|
|
89
|
+
// strip URL query strings from module name
|
|
90
|
+
this.moduleName = node.value.replace(/\?.*$/, "");
|
|
91
|
+
}
|
|
92
|
+
// public methods ---------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Returns whether the name of the imported module matches the specified
|
|
95
|
+
* glob patterns.
|
|
96
|
+
*
|
|
97
|
+
* @param patterns
|
|
98
|
+
* The glob patterns to be matched against the module name.
|
|
99
|
+
*
|
|
100
|
+
* @returns
|
|
101
|
+
* Whether the name of the wrapped module matches at least one glob
|
|
102
|
+
* pattern.
|
|
103
|
+
*/
|
|
104
|
+
matchModuleName(patterns) {
|
|
105
|
+
return pm.isMatch(this.moduleName, patterns);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// class ProjectContext =======================================================
|
|
76
109
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* @param moduleName
|
|
80
|
-
* The module name to be checked.
|
|
81
|
-
*
|
|
82
|
-
* @param patterns
|
|
83
|
-
* The glob patterns to be matched against the module name.
|
|
110
|
+
* A custom context helper for the linter rules of the preset environment
|
|
111
|
+
* "env.project".
|
|
84
112
|
*
|
|
85
|
-
* @
|
|
86
|
-
*
|
|
113
|
+
* @param context
|
|
114
|
+
* The rule context of the module currently linted.
|
|
87
115
|
*/
|
|
88
|
-
export
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
export class ProjectContext {
|
|
117
|
+
/** Maps alias keys to alias paths. */
|
|
118
|
+
#aliasMap;
|
|
119
|
+
/** Root directory containing the linter configuration file. */
|
|
120
|
+
#rootDir;
|
|
121
|
+
/** Absolute path to the linter configuration file. */
|
|
122
|
+
#configPath;
|
|
123
|
+
/** The name of the linted module (with alias prefix if available). */
|
|
124
|
+
#moduleName;
|
|
125
|
+
/** Resolver for NPM packages. */
|
|
126
|
+
#requireModule;
|
|
127
|
+
// constructor ------------------------------------------------------------
|
|
128
|
+
constructor(context) {
|
|
129
|
+
// convert "alias" option to map
|
|
130
|
+
const sharedSettings = context.settings["env-project"];
|
|
131
|
+
this.#aliasMap = new Map(Object.entries(sharedSettings?.alias ?? {}));
|
|
132
|
+
// resolve root directory
|
|
133
|
+
const configPath = findUpSync("eslint.config.js");
|
|
134
|
+
const rootDir = configPath && toPosixPath(dirname(configPath));
|
|
135
|
+
const fileName = toPosixPath(context.filename);
|
|
136
|
+
if (!rootDir || !fileName.startsWith(rootDir + "/")) {
|
|
137
|
+
throw new Error("invalid root directory");
|
|
138
|
+
}
|
|
139
|
+
this.#rootDir = rootDir;
|
|
140
|
+
this.#configPath = configPath;
|
|
141
|
+
// path of current module (slice rootDir with "/" from start, and extension with "." from end)
|
|
142
|
+
const fileExt = extname(fileName);
|
|
143
|
+
const selfModulePath = fileName.slice(rootDir.length + 1, -fileExt.length);
|
|
144
|
+
if (!selfModulePath) {
|
|
145
|
+
throw new Error("invalid own module path");
|
|
146
|
+
}
|
|
147
|
+
// replace alias path with alias key
|
|
148
|
+
this.#moduleName = selfModulePath;
|
|
149
|
+
for (const [aliasKey, aliasPath] of this.#aliasMap) {
|
|
150
|
+
if (selfModulePath.startsWith(aliasPath + "/")) {
|
|
151
|
+
this.#moduleName = aliasKey + "/" + selfModulePath.slice(aliasPath.length + 1);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// public methods ---------------------------------------------------------
|
|
157
|
+
/**
|
|
158
|
+
* Extracts the source node and module name of an import/export statement
|
|
159
|
+
* node, or a dynamic import expression node.
|
|
160
|
+
*
|
|
161
|
+
* @param node
|
|
162
|
+
* The import/export statement node, or dynamic import expression node.
|
|
163
|
+
*
|
|
164
|
+
* @returns
|
|
165
|
+
* The string literal node containing the module name, and the resulting
|
|
166
|
+
* extracted module name.
|
|
167
|
+
*/
|
|
168
|
+
resolveImportNode(node) {
|
|
169
|
+
// TSExternalModuleReference: module name in property "expression", otherwise "source"
|
|
170
|
+
const sourceNodeRaw = (node.type === NodeType.TSExternalModuleReference) ? node.expression : node.source;
|
|
171
|
+
// module name is expected to be a string literal
|
|
172
|
+
const isStringLiteral = (sourceNodeRaw?.type === NodeType.Literal) && (typeof sourceNodeRaw.value === "string");
|
|
173
|
+
return isStringLiteral ? new ImportNodeWrapper(sourceNodeRaw) : undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Extracts an existing alias key, and resolves the module path of a module
|
|
177
|
+
* name.
|
|
178
|
+
*
|
|
179
|
+
* @param moduleName
|
|
180
|
+
* The module name to extract an alias key from.
|
|
181
|
+
*
|
|
182
|
+
* @returns
|
|
183
|
+
* A pair containing the alias key (empty string if no alias found), and
|
|
184
|
+
* the resolved module path (passed module name if no alias found).
|
|
185
|
+
*/
|
|
186
|
+
resolveAlias(moduleName) {
|
|
187
|
+
const [key, ...rest] = moduleName.split("/");
|
|
188
|
+
const aliasPath = (key && rest[0]) ? this.#aliasMap.get(key) : undefined;
|
|
189
|
+
const aliasKey = aliasPath ? key : "";
|
|
190
|
+
const modulePath = aliasPath ? posix.join(aliasPath, ...rest) : moduleName;
|
|
191
|
+
return [aliasKey, modulePath];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Returns whether the name of the module currently linted matches the
|
|
195
|
+
* specified glob patterns.
|
|
196
|
+
*
|
|
197
|
+
* @param patterns
|
|
198
|
+
* The glob patterns to be matched against the current module name.
|
|
199
|
+
*
|
|
200
|
+
* @returns
|
|
201
|
+
* Whether the current module name matches at least one glob pattern.
|
|
202
|
+
*/
|
|
203
|
+
matchModuleName(patterns) {
|
|
204
|
+
return pm.isMatch(this.#moduleName, patterns);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Returns whether a source file exists for the specified module.
|
|
208
|
+
*
|
|
209
|
+
* @param modulePath
|
|
210
|
+
* The resolved module path (alias key replaced with path).
|
|
211
|
+
*
|
|
212
|
+
* @returns
|
|
213
|
+
* Whether a source file exists for the specified module.
|
|
214
|
+
*/
|
|
215
|
+
fileExists(modulePath) {
|
|
216
|
+
// check modules with explicit extension
|
|
217
|
+
const resolvedPath = posix.join(this.#rootDir, modulePath);
|
|
218
|
+
if (extname(modulePath)) {
|
|
219
|
+
return isFile(resolvedPath);
|
|
220
|
+
}
|
|
221
|
+
// search for a file with a known extension
|
|
222
|
+
return FILE_EXTENSIONS.some(ext => isFile(resolvedPath + "." + ext));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Returns whether the passed module name is an installed NPM package.
|
|
226
|
+
*
|
|
227
|
+
* @param moduleName
|
|
228
|
+
* The module name to be checked.
|
|
229
|
+
*
|
|
230
|
+
* @returns
|
|
231
|
+
* Whether the passed module name is an installed NPM package.
|
|
232
|
+
*/
|
|
233
|
+
packageExists(moduleName) {
|
|
234
|
+
this.#requireModule ??= createRequire(this.#configPath);
|
|
235
|
+
try {
|
|
236
|
+
this.#requireModule.resolve(moduleName);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
109
243
|
}
|
|
@@ -20,10 +20,9 @@ function project(options: EnvProjectOptions): Linter.FlatConfig[]
|
|
|
20
20
|
| - | - | - | - |
|
|
21
21
|
| `files` | `string[]` | _required_ | Glob patterns for source files to be included. |
|
|
22
22
|
| `ignores` | `string[]` | `[]` | Glob patterns for source files matching `files` to be ignored. |
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `modules.packages` | `Record<string, EnvProjectPackage>` | `{}` | Allows to separate the source files into virtual packages (see below). |
|
|
23
|
+
| `alias` | `Record<string, string>` | `{}` | Maps all alias prefixes to actual paths in the project (see below). |
|
|
24
|
+
| `external` | `string \| string[]` | `[]` | Specifies glob patterns for external modules (see below). |
|
|
25
|
+
| `hierarchy` | `Record<string, RuleNoInvalidHierarchyPackage>` | `{}` | Allows to separate the source files into virtual packages (see below). |
|
|
27
26
|
| `rules` | `Linter.RulesRecord` | `{}` | Additional linter rules to be added to the configuration. |
|
|
28
27
|
|
|
29
28
|
### Option `alias`
|
|
@@ -43,10 +42,8 @@ export default [
|
|
|
43
42
|
...eslint.configure({ /* ... */ }),
|
|
44
43
|
...eslint.env.project({
|
|
45
44
|
files: ["src/**/*.{js,ts}"],
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"@": "src",
|
|
49
|
-
},
|
|
45
|
+
alias: {
|
|
46
|
+
"@": "src",
|
|
50
47
|
},
|
|
51
48
|
}),
|
|
52
49
|
]
|
|
@@ -57,7 +54,7 @@ export default [
|
|
|
57
54
|
|
|
58
55
|
### Option `external`
|
|
59
56
|
|
|
60
|
-
- Type: `string
|
|
57
|
+
- Type: `string[]`
|
|
61
58
|
- Default: `[]`
|
|
62
59
|
|
|
63
60
|
Specifies glob patterns for modules that can be imported although there is no module file available, e.g. due to build tool configuration.
|
|
@@ -72,10 +69,8 @@ export default [
|
|
|
72
69
|
...eslint.configure({ /* ... */ }),
|
|
73
70
|
...eslint.env.project({
|
|
74
71
|
files: ["src/**/*.{js,ts}"],
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
external: ["virtual:*", "@/special/lib/**/*.js"],
|
|
78
|
-
},
|
|
72
|
+
alias: { "@": "src" },
|
|
73
|
+
external: ["virtual:*", "@/special/lib/**/*.js"],
|
|
79
74
|
}),
|
|
80
75
|
]
|
|
81
76
|
```
|
|
@@ -83,19 +78,19 @@ export default [
|
|
|
83
78
|
- All imports starting with `"virtual:"` will not be checked.
|
|
84
79
|
- All imports of `.js` files deeply inside `src/special/lib/` will not be checked.
|
|
85
80
|
|
|
86
|
-
### Option `
|
|
81
|
+
### Option `hierarchy`
|
|
87
82
|
|
|
88
|
-
- Type: `Record<string,
|
|
83
|
+
- Type: `Record<string, RuleNoInvalidHierarchyPackage>`
|
|
89
84
|
- Default: `{}`
|
|
90
85
|
|
|
91
86
|
Allows to separate the source files into several virtual packages. This option is completely independent from any actual packaging performed in later steps of the build process. Its solely purpose is to impose dependencies between the source files to prevent importing files between specific packages.
|
|
92
87
|
|
|
93
|
-
Each key in the `
|
|
88
|
+
Each key in the `hierarchy` record is the arbitrary unique name of a package. The record values must be objects satisfying the interface `RuleNoInvalidHierarchyPackage`:
|
|
94
89
|
|
|
95
90
|
| Name | Type | Default | Description |
|
|
96
91
|
| - | - | - | - |
|
|
97
|
-
| `
|
|
98
|
-
| `extends` | `string\|string[]` | `[]` | Specifies the names of all packages (dictionary keys of the option `
|
|
92
|
+
| `files` | `string[]` | _required_ | Glob patterns selecting all source files that are part of the package. |
|
|
93
|
+
| `extends` | `string\|string[]` | `[]` | Specifies the names of all packages (dictionary keys of the option `hierarchy`) this package depends on. |
|
|
99
94
|
| `optional` | `boolean` | `false` | Set to `true` to mark an optional package that may be missing in an installation. Such a package cannot be imported statically (with `import` statement) from a non-optional package, but can only be loaded dynamically at runtime (by calling `import()`). The rule will mark all static imports of optional code as an error. |
|
|
100
95
|
|
|
101
96
|
Modules that are part of such a package may import any modules from the same package, or from packages it depends on (also recursively from their dependent packages).
|
|
@@ -110,21 +105,19 @@ export default [
|
|
|
110
105
|
...eslint.configure({ /* ... */ }),
|
|
111
106
|
...eslint.env.project({
|
|
112
107
|
files: ["src/**/*.{js,ts}"],
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
extends: ["base", "debug"],
|
|
127
|
-
},
|
|
108
|
+
alias: { "@": "src" },
|
|
109
|
+
hierarchy: {
|
|
110
|
+
base: {
|
|
111
|
+
files: ["@/base/**/*", "@/tools/**/*"],
|
|
112
|
+
},
|
|
113
|
+
debug: {
|
|
114
|
+
files: ["@/debug/**/*"],
|
|
115
|
+
extends: "base",
|
|
116
|
+
optional: true,
|
|
117
|
+
},
|
|
118
|
+
special: {
|
|
119
|
+
files: ["@/my/special/**/*"],
|
|
120
|
+
extends: ["base", "debug"],
|
|
128
121
|
},
|
|
129
122
|
},
|
|
130
123
|
}),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@open-xchange/linter-presets",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"description": "Configuration presets for ESLint and StyleLint",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://gitlab.open-xchange.com/fspd/npm-packages/linter-presets"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// eslint-disable-next-line env-project/no-amd-module-directive
|
|
3
3
|
/// <amd-module name="file1"/>
|
|
4
4
|
|
|
5
|
-
// valid imports
|
|
5
|
+
// valid static imports
|
|
6
6
|
import "find-up";
|
|
7
7
|
import "$/external/module";
|
|
8
8
|
import "@/project1/generated";
|
|
@@ -11,16 +11,19 @@ import "@/project1/generated";
|
|
|
11
11
|
import "invalid-package";
|
|
12
12
|
// eslint-disable-next-line env-project/no-invalid-modules
|
|
13
13
|
import "@/project1/invalid";
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line env-project/no-invalid-hierarchy
|
|
15
16
|
import "@/project2/file2";
|
|
16
|
-
// eslint-disable-next-line env-project/no-invalid-
|
|
17
|
+
// eslint-disable-next-line env-project/no-invalid-hierarchy
|
|
17
18
|
import "@/project3/file3";
|
|
18
19
|
|
|
20
|
+
// valid dynamic imports
|
|
19
21
|
await import("find-up");
|
|
20
22
|
await import("@/project1/generated");
|
|
21
23
|
await import("@/project3/file3");
|
|
22
24
|
|
|
23
25
|
// eslint-disable-next-line env-project/no-invalid-modules
|
|
24
26
|
await import("invalid-package");
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line env-project/no-invalid-hierarchy
|
|
26
29
|
await import("@/project2/file2");
|