@boundaries/eslint-plugin 5.4.0 → 6.0.0-beta.2
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/README.md +9 -9
- package/dist/Config/Config.d.ts +6 -3
- package/dist/Config/Config.js +18 -7
- package/dist/Config/Recommended.d.ts +1 -1
- package/dist/Config/Recommended.js +4 -8
- package/dist/Config/Strict.d.ts +1 -1
- package/dist/Config/Strict.js +2 -2
- package/dist/Debug/Debug.d.ts +34 -0
- package/dist/Debug/Debug.js +285 -0
- package/dist/Debug/index.d.ts +1 -0
- package/dist/{Support → Debug}/index.js +0 -1
- package/dist/Elements/Elements.d.ts +9 -7
- package/dist/Elements/Elements.js +12 -7
- package/dist/Elements/Elements.types.d.ts +1 -0
- package/dist/Messages/CustomMessages.d.ts +44 -0
- package/dist/Messages/CustomMessages.js +156 -0
- package/dist/Messages/CustomMessages.types.d.ts +25 -0
- package/dist/Messages/CustomMessages.types.js +2 -0
- package/dist/Messages/Messages.d.ts +42 -13
- package/dist/Messages/Messages.js +400 -177
- package/dist/Messages/index.d.ts +2 -0
- package/dist/Messages/index.js +2 -0
- package/dist/Public/Config.types.d.ts +2 -2
- package/dist/Public/Config.types.js +2 -2
- package/dist/Public/Rules.types.d.ts +5 -4
- package/dist/Public/Rules.types.js +5 -6
- package/dist/Public/Settings.types.d.ts +3 -2
- package/dist/Public/Settings.types.js +4 -3
- package/dist/Public/index.d.ts +1 -0
- package/dist/Rules/Dependencies.d.ts +59 -0
- package/dist/Rules/Dependencies.js +439 -0
- package/dist/Rules/EntryPoint.js +44 -94
- package/dist/Rules/External.js +93 -68
- package/dist/Rules/NoIgnored.js +4 -4
- package/dist/Rules/NoPrivate.js +18 -5
- package/dist/Rules/NoUnknown.js +5 -5
- package/dist/Rules/NoUnknownFiles.js +4 -3
- package/dist/Rules/Support/DependencyRule.d.ts +9 -1
- package/dist/Rules/Support/DependencyRule.js +15 -6
- package/dist/Rules/Support/DependencyRule.types.d.ts +1 -1
- package/dist/Rules/Support/Helpers.d.ts +6 -2
- package/dist/Rules/Support/Helpers.js +7 -31
- package/dist/Settings/Helpers.d.ts +83 -1
- package/dist/Settings/Helpers.js +197 -7
- package/dist/Settings/Settings.d.ts +19 -2
- package/dist/Settings/Settings.js +20 -11
- package/dist/Settings/Validations.d.ts +11958 -43
- package/dist/Settings/Validations.js +783 -157
- package/dist/Settings/index.d.ts +0 -1
- package/dist/Settings/index.js +0 -1
- package/dist/{Settings → Shared}/Settings.types.d.ts +137 -37
- package/dist/{Settings → Shared}/Settings.types.js +30 -6
- package/dist/{Support/Common.d.ts → Shared/TypeHelpers.d.ts} +18 -0
- package/dist/{Support/Common.js → Shared/TypeHelpers.js} +28 -1
- package/dist/Shared/index.d.ts +2 -0
- package/dist/Shared/index.js +18 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -14
- package/package.json +9 -8
- package/dist/Rules/ElementTypes.d.ts +0 -25
- package/dist/Rules/ElementTypes.js +0 -279
- package/dist/Support/Debug.d.ts +0 -5
- package/dist/Support/Debug.js +0 -54
- package/dist/Support/index.d.ts +0 -2
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isRuleName = exports.isRuleShortName = exports.
|
|
3
|
+
exports.isRuleName = exports.isRuleShortName = exports.isRulePolicy = exports.RULE_NAMES_MAP = exports.RULE_SHORT_NAMES_MAP = exports.RULE_POLICIES_MAP = exports.isElementsSelector = exports.isElementSelector = void 0;
|
|
4
4
|
var elements_1 = require("@boundaries/elements");
|
|
5
5
|
Object.defineProperty(exports, "isElementSelector", { enumerable: true, get: function () { return elements_1.isElementSelector; } });
|
|
6
6
|
Object.defineProperty(exports, "isElementsSelector", { enumerable: true, get: function () { return elements_1.isElementsSelector; } });
|
|
7
|
-
|
|
8
|
-
Object.defineProperty(exports, "
|
|
7
|
+
var Shared_1 = require("../Shared");
|
|
8
|
+
Object.defineProperty(exports, "RULE_POLICIES_MAP", { enumerable: true, get: function () { return Shared_1.RULE_POLICIES_MAP; } });
|
|
9
|
+
Object.defineProperty(exports, "RULE_SHORT_NAMES_MAP", { enumerable: true, get: function () { return Shared_1.RULE_SHORT_NAMES_MAP; } });
|
|
10
|
+
Object.defineProperty(exports, "RULE_NAMES_MAP", { enumerable: true, get: function () { return Shared_1.RULE_NAMES_MAP; } });
|
|
9
11
|
var Settings_1 = require("../Settings");
|
|
10
|
-
Object.defineProperty(exports, "RULE_POLICIES_MAP", { enumerable: true, get: function () { return Settings_1.RULE_POLICIES_MAP; } });
|
|
11
12
|
Object.defineProperty(exports, "isRulePolicy", { enumerable: true, get: function () { return Settings_1.isRulePolicy; } });
|
|
12
|
-
Object.defineProperty(exports, "RULE_SHORT_NAMES_MAP", { enumerable: true, get: function () { return Settings_1.RULE_SHORT_NAMES_MAP; } });
|
|
13
|
-
Object.defineProperty(exports, "RULE_NAMES_MAP", { enumerable: true, get: function () { return Settings_1.RULE_NAMES_MAP; } });
|
|
14
13
|
Object.defineProperty(exports, "isRuleShortName", { enumerable: true, get: function () { return Settings_1.isRuleShortName; } });
|
|
15
14
|
Object.defineProperty(exports, "isRuleName", { enumerable: true, get: function () { return Settings_1.isRuleName; } });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { DEPENDENCY_KINDS_MAP, isDependencyKind } from "@boundaries/elements";
|
|
2
2
|
export type { ElementDescriptorMode, ElementDescriptor, ElementDescriptors, } from "@boundaries/elements";
|
|
3
3
|
export { ELEMENT_DESCRIPTOR_MODES_MAP, isElementDescriptorMode, } from "@boundaries/elements";
|
|
4
|
-
export type { Settings, IgnoreSetting, IncludeSetting, RootPathSetting, SettingsKey, DependencyNodeKey, DependencyNodeSelector, AliasSetting, } from "../
|
|
4
|
+
export type { Settings, IgnoreSetting, IncludeSetting, RootPathSetting, DebugSetting, DebugFilterSetting, SettingsKey, DependencyNodeKey, DependencyNodeSelector, AliasSetting, } from "../Shared";
|
|
5
|
+
export { DEPENDENCY_NODE_KEYS_MAP, SETTINGS_KEYS_MAP } from "../Shared";
|
|
5
6
|
/**
|
|
6
7
|
* Map of the kinds of import, either a type import or a value import.
|
|
7
8
|
* @deprecated Use DEPENDENCY_KINDS_MAP instead
|
|
@@ -11,4 +12,4 @@ export declare const IMPORT_KINDS_MAP: {
|
|
|
11
12
|
readonly VALUE: "value";
|
|
12
13
|
readonly TYPE_OF: "typeof";
|
|
13
14
|
};
|
|
14
|
-
export { isImportKind,
|
|
15
|
+
export { isImportKind, isDependencyNodeKey, isSettingsKey } from "../Settings";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isSettingsKey = exports.
|
|
3
|
+
exports.isSettingsKey = exports.isDependencyNodeKey = exports.isImportKind = exports.IMPORT_KINDS_MAP = exports.SETTINGS_KEYS_MAP = exports.DEPENDENCY_NODE_KEYS_MAP = exports.isElementDescriptorMode = exports.ELEMENT_DESCRIPTOR_MODES_MAP = exports.isDependencyKind = exports.DEPENDENCY_KINDS_MAP = void 0;
|
|
4
4
|
var elements_1 = require("@boundaries/elements");
|
|
5
5
|
Object.defineProperty(exports, "DEPENDENCY_KINDS_MAP", { enumerable: true, get: function () { return elements_1.DEPENDENCY_KINDS_MAP; } });
|
|
6
6
|
Object.defineProperty(exports, "isDependencyKind", { enumerable: true, get: function () { return elements_1.isDependencyKind; } });
|
|
@@ -8,6 +8,9 @@ const elements_2 = require("@boundaries/elements");
|
|
|
8
8
|
var elements_3 = require("@boundaries/elements");
|
|
9
9
|
Object.defineProperty(exports, "ELEMENT_DESCRIPTOR_MODES_MAP", { enumerable: true, get: function () { return elements_3.ELEMENT_DESCRIPTOR_MODES_MAP; } });
|
|
10
10
|
Object.defineProperty(exports, "isElementDescriptorMode", { enumerable: true, get: function () { return elements_3.isElementDescriptorMode; } });
|
|
11
|
+
var Shared_1 = require("../Shared");
|
|
12
|
+
Object.defineProperty(exports, "DEPENDENCY_NODE_KEYS_MAP", { enumerable: true, get: function () { return Shared_1.DEPENDENCY_NODE_KEYS_MAP; } });
|
|
13
|
+
Object.defineProperty(exports, "SETTINGS_KEYS_MAP", { enumerable: true, get: function () { return Shared_1.SETTINGS_KEYS_MAP; } });
|
|
11
14
|
/**
|
|
12
15
|
* Map of the kinds of import, either a type import or a value import.
|
|
13
16
|
* @deprecated Use DEPENDENCY_KINDS_MAP instead
|
|
@@ -15,7 +18,5 @@ Object.defineProperty(exports, "isElementDescriptorMode", { enumerable: true, ge
|
|
|
15
18
|
exports.IMPORT_KINDS_MAP = elements_2.DEPENDENCY_KINDS_MAP;
|
|
16
19
|
var Settings_1 = require("../Settings");
|
|
17
20
|
Object.defineProperty(exports, "isImportKind", { enumerable: true, get: function () { return Settings_1.isImportKind; } });
|
|
18
|
-
Object.defineProperty(exports, "DEPENDENCY_NODE_KEYS_MAP", { enumerable: true, get: function () { return Settings_1.DEPENDENCY_NODE_KEYS_MAP; } });
|
|
19
21
|
Object.defineProperty(exports, "isDependencyNodeKey", { enumerable: true, get: function () { return Settings_1.isDependencyNodeKey; } });
|
|
20
|
-
Object.defineProperty(exports, "SETTINGS_KEYS_MAP", { enumerable: true, get: function () { return Settings_1.SETTINGS_KEYS_MAP; } });
|
|
21
22
|
Object.defineProperty(exports, "isSettingsKey", { enumerable: true, get: function () { return Settings_1.isSettingsKey; } });
|
package/dist/Public/index.d.ts
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DependencyDescription, Matcher, DependencyMatchResult } from "@boundaries/elements";
|
|
2
|
+
import type { Rule } from "eslint";
|
|
3
|
+
import type { EslintLiteralNode } from "../Elements";
|
|
4
|
+
import type { RuleOptionsWithRules, DependenciesRuleOptions, DependenciesRule, SettingsNormalized, RuleName } from "../Shared";
|
|
5
|
+
export type EvaluateRulesResult = {
|
|
6
|
+
allowed: true;
|
|
7
|
+
} | {
|
|
8
|
+
allowed: false;
|
|
9
|
+
/** Index into `rules[]` of the rule that produced the result, or null if no rule matched. */
|
|
10
|
+
ruleIndex: number | null;
|
|
11
|
+
/** The match result from the selector that triggered the outcome, or null when no rule matched. */
|
|
12
|
+
matchResult: DependencyMatchResult | null;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Evaluates the configured rules against a dependency description using a simplified path
|
|
16
|
+
* that delegates all matching entirely to the elements package.
|
|
17
|
+
*
|
|
18
|
+
* For each rule, the outer `from`/`to`/`dependency` fields are merged with each entry in
|
|
19
|
+
* `allow`/`disallow` to build the final {@link DependencySelector}. Rules follow
|
|
20
|
+
* **last-write-wins** semantics: the last rule that produces a match determines the outcome.
|
|
21
|
+
* Within a single rule, `disallow`/`deny` takes precedence over `allow` — `allow` is not
|
|
22
|
+
* evaluated when `disallow` already matched.
|
|
23
|
+
*/
|
|
24
|
+
export declare function evaluateRules(rules: DependenciesRule[], dep: DependencyDescription, matcher: Matcher, settings: SettingsNormalized): EvaluateRulesResult;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the custom error message for a violation, preferring the rule-specific message
|
|
27
|
+
* over the global options message.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveCustomMessage(ruleIndex: number | null, ruleOptions: DependenciesRuleOptions): string | undefined;
|
|
30
|
+
type BuildErrorMessageParams = {
|
|
31
|
+
matchResult: DependencyMatchResult | null;
|
|
32
|
+
ruleIndex: number | null;
|
|
33
|
+
customMessage: string | undefined;
|
|
34
|
+
dependency: DependencyDescription;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Builds the error message for a dependency violation.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildErrorMessage({ matchResult, ruleIndex, customMessage, dependency, }: BuildErrorMessageParams): string;
|
|
40
|
+
/**
|
|
41
|
+
* Evaluates configured rules for a dependency and reports ESLint violations.
|
|
42
|
+
*
|
|
43
|
+
* @param params - Rule evaluation context and dependency information.
|
|
44
|
+
*/
|
|
45
|
+
export declare function evaluateRulesAndReport({ rules, dependency, settings, context, node, options, }: {
|
|
46
|
+
rules: DependenciesRule[];
|
|
47
|
+
settings: SettingsNormalized;
|
|
48
|
+
context: Rule.RuleContext;
|
|
49
|
+
node: EslintLiteralNode;
|
|
50
|
+
options?: RuleOptionsWithRules;
|
|
51
|
+
dependency: DependencyDescription;
|
|
52
|
+
}): void;
|
|
53
|
+
/**
|
|
54
|
+
* Returns an ESLint rule definition for the dependencies rule, which checks dependencies between elements based on configured rules.
|
|
55
|
+
* @param customRuleName - Optional custom name for the rule.
|
|
56
|
+
* @returns ESLint rule definition.
|
|
57
|
+
*/
|
|
58
|
+
export default function getDependencyRule(ruleName?: RuleName): Rule.RuleModule;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateRules = evaluateRules;
|
|
4
|
+
exports.resolveCustomMessage = resolveCustomMessage;
|
|
5
|
+
exports.buildErrorMessage = buildErrorMessage;
|
|
6
|
+
exports.evaluateRulesAndReport = evaluateRulesAndReport;
|
|
7
|
+
exports.default = getDependencyRule;
|
|
8
|
+
const elements_1 = require("@boundaries/elements");
|
|
9
|
+
const Debug_1 = require("../Debug");
|
|
10
|
+
const Elements_1 = require("../Elements");
|
|
11
|
+
const Messages_1 = require("../Messages");
|
|
12
|
+
const Settings_1 = require("../Settings");
|
|
13
|
+
const Shared_1 = require("../Shared");
|
|
14
|
+
const Support_1 = require("./Support");
|
|
15
|
+
/**
|
|
16
|
+
* Merges the legacy `importKind` field from the rule level into the `kind` field of the dependency selector, if applicable.
|
|
17
|
+
* This is needed to support the legacy `importKind` rule option, which allowed specifying the import kind at the rule level instead of the selector level.
|
|
18
|
+
* @param dependencyMetadata - The dependency metadata selector data to merge the `importKind` into.
|
|
19
|
+
* @param importKind - The legacy import kind specified at the rule level, if any.
|
|
20
|
+
* @returns The merged dependency metadata selector data with the `importKind` merged into the `kind` field.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated This function exists only for backward compatibility with the `importKind` rule
|
|
23
|
+
* option deprecated in v6. Remove in next major version once `importKind` is no longer supported.
|
|
24
|
+
*/
|
|
25
|
+
function mergeImportKindToDependencyMetadata(dependencyMetadata, importKind) {
|
|
26
|
+
if (!dependencyMetadata.kind) {
|
|
27
|
+
return {
|
|
28
|
+
kind: importKind,
|
|
29
|
+
...dependencyMetadata,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if ((0, Shared_1.isArray)(dependencyMetadata.kind)) {
|
|
33
|
+
return {
|
|
34
|
+
...dependencyMetadata,
|
|
35
|
+
kind: [...dependencyMetadata.kind, importKind],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return dependencyMetadata;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Merges a dependency selector by adding the `importKind` field from the rule level to the `dependency.kind` field in the selector, if applicable.
|
|
42
|
+
* This is needed to support the legacy `importKind` rule option, which allowed specifying the import kind at the rule level instead of the selector level.
|
|
43
|
+
* @param dependencySelector - The dependency selector object to add the `importKind` to.
|
|
44
|
+
* @param importKind - The legacy import kind specified at the rule level, if any.
|
|
45
|
+
*
|
|
46
|
+
* @deprecated This function exists only for backward compatibility with the `importKind` rule
|
|
47
|
+
* option deprecated in v6. Remove in next major version once `importKind` is no longer supported.
|
|
48
|
+
*/
|
|
49
|
+
function mergeImportKind(dependencySelector, importKind) {
|
|
50
|
+
if (!importKind) {
|
|
51
|
+
return dependencySelector;
|
|
52
|
+
}
|
|
53
|
+
if (!dependencySelector.dependency) {
|
|
54
|
+
return {
|
|
55
|
+
...dependencySelector,
|
|
56
|
+
dependency: {
|
|
57
|
+
kind: importKind,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if ((0, elements_1.isDependencyDataSelectorData)(dependencySelector.dependency)) {
|
|
62
|
+
return {
|
|
63
|
+
...dependencySelector,
|
|
64
|
+
dependency: mergeImportKindToDependencyMetadata(dependencySelector.dependency, importKind),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/* istanbul ignore next - Defensive check, it should be always an array, because options are validated */
|
|
68
|
+
if (!(0, Shared_1.isArray)(dependencySelector.dependency)) {
|
|
69
|
+
return dependencySelector;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...dependencySelector,
|
|
73
|
+
dependency: dependencySelector.dependency.map((dep) => {
|
|
74
|
+
return mergeImportKindToDependencyMetadata(dep, importKind);
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wraps matcher.getDependencySelectorMatchingDescription catching any errors to avoid breaking the rule.
|
|
80
|
+
*/
|
|
81
|
+
function safeMatch(dep, matcher, selector, extraTemplateData) {
|
|
82
|
+
try {
|
|
83
|
+
return matcher.getDependencySelectorMatchingDescription(dep, selector, {
|
|
84
|
+
extraTemplateData,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) /* istanbul ignore next - Defensive check */ {
|
|
88
|
+
(0, Debug_1.warnOnce)(`Error occurred while matching dependency. Please report it at: ${Shared_1.PLUGIN_ISSUES_URL}`, `${JSON.stringify({
|
|
89
|
+
error: (0, Shared_1.isObject)(error)
|
|
90
|
+
? { message: error.message, stack: error.stack }
|
|
91
|
+
: String(error),
|
|
92
|
+
dependency: dep,
|
|
93
|
+
selector,
|
|
94
|
+
extraTemplateData,
|
|
95
|
+
})}.`);
|
|
96
|
+
return { isMatch: false, from: null, to: null, dependency: null };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Builds template data for legacy `${...}` placeholders from captured values.
|
|
101
|
+
*
|
|
102
|
+
* @param rule - Rule currently being evaluated.
|
|
103
|
+
* @param dep - Dependency description under evaluation.
|
|
104
|
+
* @param legacyTemplates - Whether legacy template mode is enabled.
|
|
105
|
+
* @returns Template data object consumed by matcher template rendering.
|
|
106
|
+
*/
|
|
107
|
+
function getCapturedTemplateData(rule, dep, legacyTemplates) {
|
|
108
|
+
if (!legacyTemplates)
|
|
109
|
+
return {};
|
|
110
|
+
const targetDir = rule.from ? "from" : "to";
|
|
111
|
+
return targetDir === "from"
|
|
112
|
+
? { ...dep.from.captured, from: dep.from.captured, to: dep.to.captured }
|
|
113
|
+
: { ...dep.to.captured, from: dep.from.captured, to: dep.to.captured };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Normalizes a policy value to an array of individual selector entries.
|
|
117
|
+
* For arrays that contain objects (new format), each element is a separate entry.
|
|
118
|
+
* For legacy string/array selectors the whole value is a single entry.
|
|
119
|
+
*/
|
|
120
|
+
function getPolicyEntries(policy) {
|
|
121
|
+
if ((0, Shared_1.isString)(policy)) {
|
|
122
|
+
return [policy];
|
|
123
|
+
}
|
|
124
|
+
if ((0, elements_1.isElementSelectorWithLegacyOptions)(policy)) {
|
|
125
|
+
return [policy];
|
|
126
|
+
}
|
|
127
|
+
if (!(0, Shared_1.isArray)(policy))
|
|
128
|
+
return [policy];
|
|
129
|
+
return policy;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Merges two objects by combining their properties, with entry properties taking precedence over outer properties.
|
|
133
|
+
* If both objects have a property that is also an object, they are merged to avoid losing any nested properties.
|
|
134
|
+
* @param outer The outer object.
|
|
135
|
+
* @param entry The entry object.
|
|
136
|
+
* @returns The merged object.
|
|
137
|
+
*/
|
|
138
|
+
function mergeProperties(outer, entry) {
|
|
139
|
+
if ((0, Shared_1.isObject)(outer) && (0, Shared_1.isObject)(entry)) {
|
|
140
|
+
return { ...outer, ...entry };
|
|
141
|
+
}
|
|
142
|
+
return (0, Shared_1.isUndefined)(entry) ? outer : entry;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Merges two element selectors by merging their fields, with entry fields taking precedence over outer fields.
|
|
146
|
+
* If both selectors have a `captured` field, they are merged separately to avoid losing any captured values.
|
|
147
|
+
*/
|
|
148
|
+
function mergeElementSelectorData(outer, entry) {
|
|
149
|
+
const result = { ...outer, ...entry };
|
|
150
|
+
const captured = mergeProperties(outer.captured, entry.captured);
|
|
151
|
+
if (!(0, Shared_1.isUndefined)(captured)) {
|
|
152
|
+
result.captured = captured;
|
|
153
|
+
}
|
|
154
|
+
const parent = mergeProperties(outer.parent, entry.parent);
|
|
155
|
+
if (!(0, Shared_1.isUndefined)(parent)) {
|
|
156
|
+
result.parent = parent;
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function mergeDependencySelectorData(outer, entry) {
|
|
161
|
+
const result = { ...outer, ...entry };
|
|
162
|
+
const relationship = mergeProperties(outer.relationship, entry.relationship);
|
|
163
|
+
if (!(0, Shared_1.isUndefined)(relationship)) {
|
|
164
|
+
result.relationship = relationship;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Merges the outer selector with the entry selector(s) for a policy, composing the final
|
|
170
|
+
* selector(s) by merging all fields in all combinations.
|
|
171
|
+
* For example, a rule with `to: { type: "helper" }` and an dependency entry with `allow: { to: [{ internalPath: "index.js" }, { internalPath: "foo.js" }] }`,
|
|
172
|
+
* it would produce the merged selectors `[{ to: { type: "helper", internalPath: "index.js" } }, { to: { type: "helper", internalPath: "foo.js" } }]`.
|
|
173
|
+
* @param outerElementSelector The outer selector defined at the rule level.
|
|
174
|
+
* @param entryElementSelector The entry selector(s) defined at the policy level.
|
|
175
|
+
* @returns The merged selector(s) as an array of `BaseElementSelectorData` objects.
|
|
176
|
+
*/
|
|
177
|
+
function mergeElementsSelector(outerElementSelector, entryElementSelector) {
|
|
178
|
+
if (!entryElementSelector) {
|
|
179
|
+
return outerElementSelector;
|
|
180
|
+
}
|
|
181
|
+
if (!outerElementSelector) {
|
|
182
|
+
return (0, elements_1.normalizeElementsSelector)(entryElementSelector);
|
|
183
|
+
}
|
|
184
|
+
const normalizedEntrySelector = (0, elements_1.normalizeElementsSelector)(entryElementSelector);
|
|
185
|
+
return outerElementSelector?.flatMap((outerSelector) => {
|
|
186
|
+
return normalizedEntrySelector.map((entrySelector) => mergeElementSelectorData(outerSelector, entrySelector));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Merges the outer dependency selector with the entry dependency selector, composing the final selector by merging all fields in all combinations.
|
|
191
|
+
* For example, a rule with `dependency: { kind: "import" }` and an dependency entry with `allow: { dependency: [{ module: "react" }, { module: "lodash" }] }`,
|
|
192
|
+
* it would produce the merged selectors `[{ dependency: { kind: "import", module: "react" } }, { dependency: { kind: "import", module: "lodash" } }]`.
|
|
193
|
+
* @param outerDependencySelector The outer dependency selector defined at the rule level.
|
|
194
|
+
* @param entryDependencySelector The entry dependency selector(s) defined at the policy level.
|
|
195
|
+
* @returns The merged dependency selector(s) as an array of `DependencyDataSelector` objects.
|
|
196
|
+
*/
|
|
197
|
+
function mergeDependencyDataSelectors(outerDependencySelector, entryDependencySelector) {
|
|
198
|
+
if (!entryDependencySelector) {
|
|
199
|
+
return outerDependencySelector;
|
|
200
|
+
}
|
|
201
|
+
if (!outerDependencySelector) {
|
|
202
|
+
return entryDependencySelector;
|
|
203
|
+
}
|
|
204
|
+
const outer = (0, Shared_1.isArray)(outerDependencySelector)
|
|
205
|
+
? outerDependencySelector
|
|
206
|
+
: [outerDependencySelector];
|
|
207
|
+
const entry = (0, Shared_1.isArray)(entryDependencySelector)
|
|
208
|
+
? entryDependencySelector
|
|
209
|
+
: [entryDependencySelector];
|
|
210
|
+
return outer.flatMap((outerSelector) => {
|
|
211
|
+
return entry.map((entrySelector) => mergeDependencySelectorData(outerSelector, entrySelector));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Builds the final {@link DependencySelector} by merging the outer rule-level fields
|
|
216
|
+
* (`from`, `to`, `dependency`) with a single policy entry.
|
|
217
|
+
*
|
|
218
|
+
* - **Object entry** — each of `from`, `to` and `dependency` is merged field by field,
|
|
219
|
+
* with entry fields taking precedence over outer fields. This allows composing criteria:
|
|
220
|
+
* `{ to: { type: "helper" }, allow: { to: { internalPath: "index.js" } } }` →
|
|
221
|
+
* selector `{ to: { type: "helper", internalPath: "index.js" } }`.
|
|
222
|
+
* - **Legacy entry** (string or array without objects) — interpreted as the element selector
|
|
223
|
+
* for the "other" direction (opposite to the direction defined at the rule level).
|
|
224
|
+
*/
|
|
225
|
+
function buildEntrySelector(outerFrom, outerTo, outerDependency, entry) {
|
|
226
|
+
if ((0, elements_1.isDependencySelector)(entry)) {
|
|
227
|
+
const entryObj = entry;
|
|
228
|
+
const mergedFrom = mergeElementsSelector(outerFrom, entryObj.from ? (0, elements_1.normalizeElementsSelector)(entryObj.from) : undefined);
|
|
229
|
+
const mergedTo = mergeElementsSelector(outerTo, entryObj.to ? (0, elements_1.normalizeElementsSelector)(entryObj.to) : undefined);
|
|
230
|
+
const mergedDependency = mergeDependencyDataSelectors(outerDependency, entryObj.dependency);
|
|
231
|
+
const selectorResult = {};
|
|
232
|
+
if (!(0, Shared_1.isUndefined)(mergedFrom))
|
|
233
|
+
selectorResult.from = mergedFrom;
|
|
234
|
+
if (!(0, Shared_1.isUndefined)(mergedTo))
|
|
235
|
+
selectorResult.to = mergedTo;
|
|
236
|
+
if (!(0, Shared_1.isUndefined)(mergedDependency))
|
|
237
|
+
selectorResult.dependency = mergedDependency;
|
|
238
|
+
return selectorResult;
|
|
239
|
+
}
|
|
240
|
+
// Legacy entry: string or legacy array → becomes the "other direction" element selector
|
|
241
|
+
// They are not merged because legacy entries are not objects but strings/arrays, so they
|
|
242
|
+
// can not express criteria in the same direction as the outer selector but only in the opposite direction
|
|
243
|
+
const hasFrom = !(0, Shared_1.isUndefined)(outerFrom);
|
|
244
|
+
const result = {};
|
|
245
|
+
if (hasFrom) {
|
|
246
|
+
result.from = outerFrom;
|
|
247
|
+
result.to = entry;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
result.to = outerTo;
|
|
251
|
+
result.from = entry;
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Iterates the entries of a single policy value returning the first match, or null.
|
|
257
|
+
* @param options - The rule options object containing the policy entries and outer selectors.
|
|
258
|
+
* @param options.policy - The policy value, which can be a single entry or an array of entries.
|
|
259
|
+
* @param options.outerFrom - The outer `from` selector defined at the rule level, to be merged with each entry.
|
|
260
|
+
* @param options.outerTo - The outer `to` selector defined at the rule level, to be merged with each entry.
|
|
261
|
+
* @param options.outerDependency - The outer `dependency` selector defined at the rule level, to be merged with each entry.
|
|
262
|
+
* @param options.dep - The dependency description under evaluation.
|
|
263
|
+
* @param options.matcher - The elements matcher instance used to evaluate matches.
|
|
264
|
+
* @param options.templateData - The template data object containing captured values for template rendering.
|
|
265
|
+
* @param options.legacyImportKind - The legacy import kind at rule level, if applicable.
|
|
266
|
+
* @returns The match result from the first matching entry, or null if no entries matched.
|
|
267
|
+
*/
|
|
268
|
+
function evaluatePolicyEntries({ policy, outerFrom, outerTo, outerDependency, dep, matcher, templateData, legacyImportKind, }) {
|
|
269
|
+
for (const entry of getPolicyEntries(policy)) {
|
|
270
|
+
const selector = buildEntrySelector(outerFrom ? (0, elements_1.normalizeElementsSelector)(outerFrom) : undefined, outerTo ? (0, elements_1.normalizeElementsSelector)(outerTo) : undefined, outerDependency, entry);
|
|
271
|
+
const selectorWithImportKind = mergeImportKind(selector, legacyImportKind);
|
|
272
|
+
const result = safeMatch(dep, matcher, selectorWithImportKind, templateData);
|
|
273
|
+
if (result.isMatch)
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Evaluates the configured rules against a dependency description using a simplified path
|
|
280
|
+
* that delegates all matching entirely to the elements package.
|
|
281
|
+
*
|
|
282
|
+
* For each rule, the outer `from`/`to`/`dependency` fields are merged with each entry in
|
|
283
|
+
* `allow`/`disallow` to build the final {@link DependencySelector}. Rules follow
|
|
284
|
+
* **last-write-wins** semantics: the last rule that produces a match determines the outcome.
|
|
285
|
+
* Within a single rule, `disallow`/`deny` takes precedence over `allow` — `allow` is not
|
|
286
|
+
* evaluated when `disallow` already matched.
|
|
287
|
+
*/
|
|
288
|
+
function evaluateRules(rules, dep, matcher, settings) {
|
|
289
|
+
let allowed = null;
|
|
290
|
+
let ruleIndex = null;
|
|
291
|
+
let matchResult = null;
|
|
292
|
+
for (let i = 0; i < rules.length; i++) {
|
|
293
|
+
const rule = rules[i];
|
|
294
|
+
const templateData = getCapturedTemplateData(rule, dep, settings.legacyTemplates);
|
|
295
|
+
const outerFrom = rule.from;
|
|
296
|
+
const outerTo = rule.to;
|
|
297
|
+
const outerDependency = rule.dependency;
|
|
298
|
+
let denyMatched = false;
|
|
299
|
+
if (rule.disallow) {
|
|
300
|
+
const denyMatch = evaluatePolicyEntries({
|
|
301
|
+
policy: rule.disallow,
|
|
302
|
+
outerFrom,
|
|
303
|
+
outerTo,
|
|
304
|
+
outerDependency,
|
|
305
|
+
dep,
|
|
306
|
+
matcher,
|
|
307
|
+
templateData,
|
|
308
|
+
legacyImportKind: rule.importKind /* legacy importKind for backward compatibility */,
|
|
309
|
+
});
|
|
310
|
+
if (denyMatch) {
|
|
311
|
+
allowed = false;
|
|
312
|
+
ruleIndex = i;
|
|
313
|
+
matchResult = denyMatch;
|
|
314
|
+
denyMatched = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Allow is only evaluated when disallow/deny did not match for this rule
|
|
318
|
+
if (!denyMatched && rule.allow) {
|
|
319
|
+
const allowMatch = evaluatePolicyEntries({
|
|
320
|
+
policy: rule.allow,
|
|
321
|
+
outerFrom,
|
|
322
|
+
outerTo,
|
|
323
|
+
outerDependency,
|
|
324
|
+
dep,
|
|
325
|
+
matcher,
|
|
326
|
+
templateData,
|
|
327
|
+
legacyImportKind: rule.importKind /* legacy importKind for backward compatibility */,
|
|
328
|
+
});
|
|
329
|
+
if (allowMatch) {
|
|
330
|
+
allowed = true;
|
|
331
|
+
ruleIndex = i;
|
|
332
|
+
matchResult = allowMatch;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (allowed === true)
|
|
337
|
+
return { allowed: true };
|
|
338
|
+
return { allowed: false, ruleIndex, matchResult };
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Resolves the custom error message for a violation, preferring the rule-specific message
|
|
342
|
+
* over the global options message.
|
|
343
|
+
*/
|
|
344
|
+
function resolveCustomMessage(ruleIndex, ruleOptions) {
|
|
345
|
+
const ruleMessage = (0, Shared_1.isNull)(ruleIndex)
|
|
346
|
+
? undefined
|
|
347
|
+
: ruleOptions.rules?.[ruleIndex]?.message;
|
|
348
|
+
return ruleMessage ?? ruleOptions.message;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Builds the error message for a dependency violation.
|
|
352
|
+
*/
|
|
353
|
+
function buildErrorMessage({ matchResult, ruleIndex, customMessage, dependency, }) {
|
|
354
|
+
if (customMessage) {
|
|
355
|
+
return (0, Messages_1.customErrorMessage)(customMessage, dependency, ruleIndex, matchResult);
|
|
356
|
+
}
|
|
357
|
+
return (0, Messages_1.dependenciesRuleDefaultErrorMessage)(matchResult, ruleIndex, dependency);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Evaluates configured rules for a dependency and reports ESLint violations.
|
|
361
|
+
*
|
|
362
|
+
* @param params - Rule evaluation context and dependency information.
|
|
363
|
+
*/
|
|
364
|
+
function evaluateRulesAndReport({ rules, dependency, settings, context, node, options, }) {
|
|
365
|
+
const matcher = (0, Elements_1.getElementsMatcher)(settings);
|
|
366
|
+
const result = evaluateRules(rules, dependency, matcher, settings);
|
|
367
|
+
const defaultAllowed = options?.default === "allow";
|
|
368
|
+
let finalAllowed = false;
|
|
369
|
+
if (result.allowed === true) {
|
|
370
|
+
finalAllowed = true;
|
|
371
|
+
}
|
|
372
|
+
else if ((0, Shared_1.isNull)(result.ruleIndex)) {
|
|
373
|
+
finalAllowed = defaultAllowed;
|
|
374
|
+
}
|
|
375
|
+
if (!finalAllowed && result.allowed === false) {
|
|
376
|
+
(0, Debug_1.printDependenciesRuleResult)(result.matchResult, result.ruleIndex, dependency, settings, matcher);
|
|
377
|
+
/* istanbul ignore next - Defensive check, should not happen because options are validated */
|
|
378
|
+
const optionsForMessage = options ?? {};
|
|
379
|
+
context.report({
|
|
380
|
+
message: buildErrorMessage({
|
|
381
|
+
matchResult: result.matchResult,
|
|
382
|
+
ruleIndex: result.ruleIndex,
|
|
383
|
+
customMessage: resolveCustomMessage(result.ruleIndex, optionsForMessage),
|
|
384
|
+
dependency,
|
|
385
|
+
}),
|
|
386
|
+
node,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Returns an ESLint rule definition for the dependencies rule, which checks dependencies between elements based on configured rules.
|
|
392
|
+
* @param customRuleName - Optional custom name for the rule.
|
|
393
|
+
* @returns ESLint rule definition.
|
|
394
|
+
*/
|
|
395
|
+
function getDependencyRule(ruleName = Shared_1.RULE_NAMES_MAP.DEPENDENCIES) {
|
|
396
|
+
return (0, Support_1.dependencyRule)({
|
|
397
|
+
ruleName,
|
|
398
|
+
description: `Check dependencies between elements`,
|
|
399
|
+
schema: (0, Settings_1.rulesOptionsSchema)({
|
|
400
|
+
extraOptionsSchema: {
|
|
401
|
+
checkAllOrigins: {
|
|
402
|
+
type: "boolean",
|
|
403
|
+
description: "Whether to check dependencies from all origins (including external and core) or only from local elements. Default to false (only local).",
|
|
404
|
+
},
|
|
405
|
+
checkUnknownLocals: {
|
|
406
|
+
type: "boolean",
|
|
407
|
+
description: "Whether to check local dependencies with unknown elements (not matching any element descriptor) or to ignore them. Default to false (ignore).",
|
|
408
|
+
},
|
|
409
|
+
checkInternals: {
|
|
410
|
+
type: "boolean",
|
|
411
|
+
description: "Whether to check internal dependencies (dependencies within files in the same element). Default to false (ignore).",
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
}, function ({ dependency, node, context, settings, options }) {
|
|
416
|
+
if (ruleName === Shared_1.RULE_NAMES_MAP.ELEMENT_TYPES) {
|
|
417
|
+
(0, Debug_1.warnOnce)(`Rule name "${Shared_1.RULE_NAMES_MAP.ELEMENT_TYPES}" is deprecated. Use "${Shared_1.RULE_NAMES_MAP.DEPENDENCIES}" instead.`, (0, Settings_1.migrationToV6GuideLink)("rule-element-types-renamed-to-dependencies"));
|
|
418
|
+
}
|
|
419
|
+
// Validate and warn about legacy selector syntax
|
|
420
|
+
(0, Settings_1.validateAndWarnRuleOptions)(options, ruleName, "from");
|
|
421
|
+
const checkAllOrigins = options?.checkAllOrigins ?? false;
|
|
422
|
+
const checkUnknownLocals = options?.checkUnknownLocals ?? false;
|
|
423
|
+
const checkInternals = options?.checkInternals ?? false;
|
|
424
|
+
if (!(0, elements_1.isIgnoredElement)(dependency.to) &&
|
|
425
|
+
(checkAllOrigins || (0, elements_1.isLocalElement)(dependency.to)) &&
|
|
426
|
+
(checkUnknownLocals || !(0, elements_1.isUnknownLocalElement)(dependency.to)) &&
|
|
427
|
+
(checkInternals || !(0, elements_1.isInternalDependency)(dependency))) {
|
|
428
|
+
const rules = options?.rules ?? [];
|
|
429
|
+
evaluateRulesAndReport({
|
|
430
|
+
rules,
|
|
431
|
+
settings,
|
|
432
|
+
context,
|
|
433
|
+
node,
|
|
434
|
+
options,
|
|
435
|
+
dependency,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|