@morriconeluca/eslint-plugin-faf 0.2.0 → 0.4.0
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 +19 -5
- package/dist/main.cjs +114 -71
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +114 -71
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,15 @@ This plugin is designed for complex TypeScript/JavaScript codebases to ensure **
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## Companion Tooling
|
|
19
|
+
|
|
20
|
+
`eslint-plugin-faf` does not detect dependency cycles. FAF requires the dependency graph to be a Directed Acyclic Graph, but enforcing that is intentionally left to dedicated tools, configured separately in your project:
|
|
21
|
+
|
|
22
|
+
- [`eslint-plugin-import-x`](https://github.com/un-ts/eslint-plugin-import-x)'s `import-x/no-cycle` rule.
|
|
23
|
+
- [`dependency-cruiser`](https://github.com/sverweij/dependency-cruiser), for cycle detection and other custom dependency fitness functions.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
18
27
|
## Installation
|
|
19
28
|
|
|
20
29
|
Install the plugin along with its peer dependencies:
|
|
@@ -213,8 +222,12 @@ export default {
|
|
|
213
222
|
{
|
|
214
223
|
// Subtree where intermediate folders are classified as organizational Layers,
|
|
215
224
|
// and the terminal Fragment must contain a Master Node with the specified role (e.g. '.api.ts')
|
|
225
|
+
// and a name following the "<method>-<object>" pattern (e.g. 'get-todo')
|
|
216
226
|
paths: ['src/_src@shared/_network/_apis'],
|
|
217
227
|
role: 'api',
|
|
228
|
+
// Optional: restricts which HTTP methods are valid as the Fragment name prefix.
|
|
229
|
+
// Defaults to all standard HTTP methods (get, post, put, patch, delete, head, options, connect, trace).
|
|
230
|
+
httpMethods: ['get', 'post', 'put', 'patch', 'delete'],
|
|
218
231
|
},
|
|
219
232
|
],
|
|
220
233
|
},
|
|
@@ -264,8 +277,9 @@ Verifies folders, files, and roles comply with FAF taxonomy:
|
|
|
264
277
|
|
|
265
278
|
- Enforces `kebab-case` naming for all folders and files in the logical domain.
|
|
266
279
|
- Validates that internal Fragment files share their parent Fragment's name prefix (e.g. files in folder `button/` must be named `button.<role>.<ext>`).
|
|
267
|
-
- Enforces role suffix validation on Fragment Nodes and Logical Nodes.
|
|
268
|
-
-
|
|
280
|
+
- Enforces role suffix validation on Fragment Nodes and Logical Nodes, restricted to a single Role segment per file name.
|
|
281
|
+
- Requires every Fragment to contain at least one Fragment Node (its Master Node), and forbids two Fragment Nodes from sharing the same Role.
|
|
282
|
+
- Assures route terminal Fragments contain the appropriate Master Node (e.g. a `.api.ts` file under `_apis`) and that the Fragment name follows the `<method>-<object>` pattern (e.g. `get-todo`), with the HTTP method restricted to `routeHierarchies[].httpMethods` or, by default, all standard HTTP methods.
|
|
269
283
|
- Enforces FAF logical domain containment rules:
|
|
270
284
|
- Root Fragments can only reside directly in the Root Container or another Root Fragment.
|
|
271
285
|
- Layers cannot reside inside Fragments.
|
|
@@ -289,7 +303,7 @@ Applies **Fragment Encapsulation**:
|
|
|
289
303
|
|
|
290
304
|
Applies **Private Category Encapsulation**:
|
|
291
305
|
|
|
292
|
-
- Restricts consumption of elements inside a Private Category (e.g. `_components/` nested inside a Fragment) to
|
|
306
|
+
- Restricts consumption of elements inside a Private Category (e.g. `_components/` nested inside a Fragment) to the direct child Fragment Nodes of the owning Fragment/Root Fragment, and to sibling nodes within the same immediate sub-domain of the Private Category. Access does not extend to other sub-domains, nor to a Fractal Branch nested inside the Private Category (governed instead by its own encapsulation rule).
|
|
293
307
|
|
|
294
308
|
### 5. `faf/no-fractal-branch-leak`
|
|
295
309
|
|
|
@@ -303,8 +317,8 @@ Applies **Peer Isolation**:
|
|
|
303
317
|
|
|
304
318
|
- Prevents sibling files and folders from importing each other unless an explicit hierarchy is configured:
|
|
305
319
|
- **Inside a Fragment**: Flow is governed by the `roles` array order.
|
|
306
|
-
- **Between folders**: Flow is governed by `localHorizontalHierarchies` or `globalHorizontalHierarchies
|
|
307
|
-
- **Root Nodes**: Flow is governed by the defined index order in `rootFragments.rootNodes`.
|
|
320
|
+
- **Between folders**: Flow is governed exclusively by `localHorizontalHierarchies` or `globalHorizontalHierarchies`; a sibling pair without a matching entry is denied in both directions.
|
|
321
|
+
- **Root Nodes**: Flow is governed exclusively by the defined index order in `rootFragments.rootNodes`. Root Nodes are exempt from the Role naming convention, so relationships between them never fall back to the `roles` scale: any relationship without a matching `rootNodes` entry is denied.
|
|
308
322
|
|
|
309
323
|
### 7. `faf/category-mutually-exclusive`
|
|
310
324
|
|
package/dist/main.cjs
CHANGED
|
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(main_exports);
|
|
|
39
39
|
// package.json
|
|
40
40
|
var package_default = {
|
|
41
41
|
name: "@morriconeluca/eslint-plugin-faf",
|
|
42
|
-
version: "0.
|
|
42
|
+
version: "0.4.0",
|
|
43
43
|
description: "An ESLint plugin for FAF projects.",
|
|
44
44
|
repository: {
|
|
45
45
|
type: "git",
|
|
@@ -480,6 +480,17 @@ function getProjectRoot() {
|
|
|
480
480
|
|
|
481
481
|
// src/_rules/naming-conventions/naming-conventions.rule.ts
|
|
482
482
|
var KEBAB_CASE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
483
|
+
var DEFAULT_HTTP_METHODS = [
|
|
484
|
+
"connect",
|
|
485
|
+
"delete",
|
|
486
|
+
"get",
|
|
487
|
+
"head",
|
|
488
|
+
"options",
|
|
489
|
+
"patch",
|
|
490
|
+
"post",
|
|
491
|
+
"put",
|
|
492
|
+
"trace"
|
|
493
|
+
];
|
|
483
494
|
var rule3 = {
|
|
484
495
|
create(context) {
|
|
485
496
|
return {
|
|
@@ -643,15 +654,6 @@ var rule3 = {
|
|
|
643
654
|
if (parentType === "invalid-fragment") {
|
|
644
655
|
return;
|
|
645
656
|
}
|
|
646
|
-
if (fileName === "index.ts" || fileName === "index.js") {
|
|
647
|
-
if (parentType !== "fragment") {
|
|
648
|
-
context.report({
|
|
649
|
-
message: `Access Nodes ("index.ts/index.js") are exclusive to Fragments. Found index file directly inside "${folderName}" (classified as ${parentType}).`,
|
|
650
|
-
node
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
657
|
const rfConfig = getRootFragmentConfig(relDir, config);
|
|
656
658
|
if (rfConfig) {
|
|
657
659
|
const flatRootNodes = rfConfig.rootNodes.flat();
|
|
@@ -667,11 +669,34 @@ var rule3 = {
|
|
|
667
669
|
return;
|
|
668
670
|
}
|
|
669
671
|
}
|
|
672
|
+
if (fileName === "index.ts" || fileName === "index.js") {
|
|
673
|
+
if (parentType !== "fragment") {
|
|
674
|
+
context.report({
|
|
675
|
+
message: `Access Nodes ("index.ts/index.js") are exclusive to Fragments. Found index file directly inside "${folderName}" (classified as ${parentType}).`,
|
|
676
|
+
node
|
|
677
|
+
});
|
|
678
|
+
} else {
|
|
679
|
+
const contents = readDirCached(relDir);
|
|
680
|
+
const hasFragmentNode = contents.files.some(
|
|
681
|
+
(f) => f !== "index.ts" && f !== "index.js" && f !== "README.md" && f !== "package.json" && f !== "tsconfig.json"
|
|
682
|
+
);
|
|
683
|
+
if (!hasFragmentNode) {
|
|
684
|
+
context.report({
|
|
685
|
+
message: `Fragment "${folderName}" has no Master Node. Every Fragment must contain at least one Fragment Node establishing its Role.`,
|
|
686
|
+
node
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
670
692
|
if (fileName === "README.md" || fileName === "package.json" || fileName === "tsconfig.json") {
|
|
671
693
|
return;
|
|
672
694
|
}
|
|
673
695
|
const role = getFileRole(fileName, config);
|
|
674
696
|
const ext = import_path7.default.extname(fileName);
|
|
697
|
+
const allRoleNames = new Set(config.roles.flat());
|
|
698
|
+
const roleSegments = fileName.slice(0, fileName.length - ext.length).split(".").slice(1).filter((part) => allRoleNames.has(part));
|
|
699
|
+
const multiRoleMessage = `File "${fileName}" composes multiple Roles ("${roleSegments.join('", "')}") in its name. A Logical Node may declare only one Role; promote the detail that needs its own Role to an autonomous Sub-Fragment.`;
|
|
675
700
|
if (parentType === "fragment") {
|
|
676
701
|
const matchedRoute = config.routeHierarchies?.find(
|
|
677
702
|
(rh) => rh.paths.some((rp) => relDir === rp || relDir.startsWith(rp + "/"))
|
|
@@ -685,6 +710,16 @@ var rule3 = {
|
|
|
685
710
|
node
|
|
686
711
|
});
|
|
687
712
|
}
|
|
713
|
+
const httpMethods = matchedRoute.httpMethods ?? DEFAULT_HTTP_METHODS;
|
|
714
|
+
const routeNameRegex = new RegExp(
|
|
715
|
+
`^(${httpMethods.join("|")})-[a-z0-9]+(-[a-z0-9]+)*$`
|
|
716
|
+
);
|
|
717
|
+
if (!routeNameRegex.test(folderName)) {
|
|
718
|
+
context.report({
|
|
719
|
+
message: `Route terminal Fragment "${folderName}" must follow the "<method>-<object>" pattern with a lowercase HTTP method (${httpMethods.join(", ")}).`,
|
|
720
|
+
node
|
|
721
|
+
});
|
|
722
|
+
}
|
|
688
723
|
}
|
|
689
724
|
let currentParent = import_path7.default.dirname(relDir).replace(/\\/g, "/");
|
|
690
725
|
let categoryConfig = null;
|
|
@@ -723,6 +758,19 @@ var rule3 = {
|
|
|
723
758
|
message: `File "${fileName}" inside Fragment "${folderName}" must have an explicit role suffix (e.g. "${folderName}.style${ext}").`,
|
|
724
759
|
node
|
|
725
760
|
});
|
|
761
|
+
} else if (roleSegments.length > 1) {
|
|
762
|
+
context.report({ message: multiRoleMessage, node });
|
|
763
|
+
} else {
|
|
764
|
+
const contents = readDirCached(relDir);
|
|
765
|
+
const duplicate = contents.files.find(
|
|
766
|
+
(f) => f !== fileName && f !== "index.ts" && f !== "index.js" && getFileRole(f, config) === role
|
|
767
|
+
);
|
|
768
|
+
if (duplicate) {
|
|
769
|
+
context.report({
|
|
770
|
+
message: `Fragment Node "${fileName}" shares Role "${role}" with sibling "${duplicate}". Each Fragment Node must have a unique Role within its Fragment.`,
|
|
771
|
+
node
|
|
772
|
+
});
|
|
773
|
+
}
|
|
726
774
|
}
|
|
727
775
|
}
|
|
728
776
|
if (parentType === "category") {
|
|
@@ -753,6 +801,8 @@ var rule3 = {
|
|
|
753
801
|
message: `Role "${role}" for file "${fileName}" does not match the expected Category role "${catConfig.role}".`,
|
|
754
802
|
node
|
|
755
803
|
});
|
|
804
|
+
} else if (roleSegments.length > 1) {
|
|
805
|
+
context.report({ message: multiRoleMessage, node });
|
|
756
806
|
}
|
|
757
807
|
} else {
|
|
758
808
|
if (role && role !== catConfig.role) {
|
|
@@ -760,6 +810,8 @@ var rule3 = {
|
|
|
760
810
|
message: `Role "${role}" for asset "${fileName}" does not match the expected Category role "${catConfig.role}".`,
|
|
761
811
|
node
|
|
762
812
|
});
|
|
813
|
+
} else if (role && roleSegments.length > 1) {
|
|
814
|
+
context.report({ message: multiRoleMessage, node });
|
|
763
815
|
}
|
|
764
816
|
}
|
|
765
817
|
}
|
|
@@ -829,6 +881,16 @@ function _resolveImportPath(importPath, currentFileRelative, settings) {
|
|
|
829
881
|
}
|
|
830
882
|
return finalizeResolvedPath(relResolved);
|
|
831
883
|
}
|
|
884
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
885
|
+
"cjs",
|
|
886
|
+
"cts",
|
|
887
|
+
"js",
|
|
888
|
+
"jsx",
|
|
889
|
+
"mjs",
|
|
890
|
+
"mts",
|
|
891
|
+
"ts",
|
|
892
|
+
"tsx"
|
|
893
|
+
]);
|
|
832
894
|
function finalizeResolvedPath(relResolved) {
|
|
833
895
|
const resolvedDir = import_path8.default.dirname(relResolved).replace(/\\/g, "/");
|
|
834
896
|
const resolvedBase = import_path8.default.basename(relResolved);
|
|
@@ -842,9 +904,12 @@ function finalizeResolvedPath(relResolved) {
|
|
|
842
904
|
const parentContents = readDirCached(resolvedDir);
|
|
843
905
|
const exactMatch = parentContents.files.find((f) => f === resolvedBase);
|
|
844
906
|
if (!exactMatch) {
|
|
845
|
-
const
|
|
907
|
+
const candidates = parentContents.files.filter(
|
|
846
908
|
(f) => f.startsWith(resolvedBase + ".")
|
|
847
909
|
);
|
|
910
|
+
const matchWithExt = candidates.find(
|
|
911
|
+
(f) => SCRIPT_EXTENSIONS.has(f.slice(resolvedBase.length + 1))
|
|
912
|
+
) ?? candidates[0];
|
|
848
913
|
if (matchWithExt) {
|
|
849
914
|
return (resolvedDir === "." ? "" : resolvedDir + "/") + matchWithExt;
|
|
850
915
|
}
|
|
@@ -913,7 +978,7 @@ var rule4 = {
|
|
|
913
978
|
if (importedFolderType === "fragment") {
|
|
914
979
|
const isAccessNode = importedFile === "index.ts" || importedFile === "index.js";
|
|
915
980
|
if (!isAccessNode) {
|
|
916
|
-
const isInside = relPath === importedDir
|
|
981
|
+
const isInside = import_path9.default.dirname(relPath) === importedDir;
|
|
917
982
|
if (!isInside) {
|
|
918
983
|
context.report({
|
|
919
984
|
message: `Direct import of Fragment internal file "${importedFile}" is forbidden. You must import through its Access Node (index file) in "${importedDir}".`,
|
|
@@ -1020,19 +1085,19 @@ var rule5 = {
|
|
|
1020
1085
|
return;
|
|
1021
1086
|
}
|
|
1022
1087
|
const importedDir = import_path12.default.dirname(resolvedRelPath);
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1025
|
-
const isDescendant = relPath ===
|
|
1088
|
+
const fractalBranchOwner = getFractalBranchOwner(importedDir);
|
|
1089
|
+
if (fractalBranchOwner) {
|
|
1090
|
+
const isDescendant = relPath === fractalBranchOwner || relPath.startsWith(fractalBranchOwner + "/");
|
|
1026
1091
|
if (!isDescendant) {
|
|
1027
1092
|
context.report({
|
|
1028
|
-
message: `Importing from Fractal Branch is forbidden. The imported resource is private to "${
|
|
1093
|
+
message: `Importing from Fractal Branch is forbidden. The imported resource is private to "${fractalBranchOwner}" and its descendants.`,
|
|
1029
1094
|
node
|
|
1030
1095
|
});
|
|
1031
1096
|
} else {
|
|
1032
1097
|
const info = getPrivateCategoryInfo(importedDir, config);
|
|
1033
1098
|
if (info) {
|
|
1034
|
-
const { owner
|
|
1035
|
-
const isPrivateDirectChild = import_path12.default.dirname(relPath) ===
|
|
1099
|
+
const { owner, privateCategoryPath } = info;
|
|
1100
|
+
const isPrivateDirectChild = import_path12.default.dirname(relPath) === owner;
|
|
1036
1101
|
const importerSubDomain = getImmediateSubDomain(
|
|
1037
1102
|
relPath,
|
|
1038
1103
|
privateCategoryPath
|
|
@@ -1042,17 +1107,10 @@ var rule5 = {
|
|
|
1042
1107
|
privateCategoryPath
|
|
1043
1108
|
);
|
|
1044
1109
|
const isInsideSameSubDomain = importerSubDomain !== "" && importerSubDomain === importedSubDomain;
|
|
1045
|
-
|
|
1046
|
-
const fbOwner = getFractalBranchOwner(importedDir);
|
|
1047
|
-
if (fbOwner) {
|
|
1048
|
-
const isFbInsidePrivateCategory = fbOwner === privateCategoryPath || fbOwner.startsWith(privateCategoryPath + "/");
|
|
1049
|
-
if (isFbInsidePrivateCategory) {
|
|
1050
|
-
isFractalBranchAllowed = relPath === fbOwner || relPath.startsWith(fbOwner + "/");
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1110
|
+
const isFractalBranchAllowed = fractalBranchOwner === privateCategoryPath || fractalBranchOwner.startsWith(privateCategoryPath + "/");
|
|
1053
1111
|
if (!isPrivateDirectChild && !isInsideSameSubDomain && !isFractalBranchAllowed) {
|
|
1054
1112
|
context.report({
|
|
1055
|
-
message: `Importing from Private Category is forbidden. The imported resource is private to "${
|
|
1113
|
+
message: `Importing from Private Category is forbidden. The imported resource is private to "${owner}".`,
|
|
1056
1114
|
node
|
|
1057
1115
|
});
|
|
1058
1116
|
}
|
|
@@ -1162,24 +1220,6 @@ function resolveHorizontalHierarchy(lcaPath, subA, subB, config) {
|
|
|
1162
1220
|
}
|
|
1163
1221
|
|
|
1164
1222
|
// src/_rules/no-peer-dependency/no-peer-dependency.rule.ts
|
|
1165
|
-
function getDirectoryRole(dirPath, config) {
|
|
1166
|
-
const folderName = import_path13.default.basename(dirPath);
|
|
1167
|
-
if (folderName.startsWith("_")) {
|
|
1168
|
-
return getRoleFromFolderName(folderName, config);
|
|
1169
|
-
}
|
|
1170
|
-
const contents = readDirCached(dirPath);
|
|
1171
|
-
for (const file of contents.files) {
|
|
1172
|
-
if (file.startsWith(folderName + ".")) {
|
|
1173
|
-
const role = getFileRole(file, config);
|
|
1174
|
-
if (role) return role;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
function getRoleFromFolderName(name, config) {
|
|
1180
|
-
const catConfig = config.categories?.find((c) => c.name === name);
|
|
1181
|
-
return catConfig ? catConfig.role ?? null : null;
|
|
1182
|
-
}
|
|
1183
1223
|
function getRootNodeIndex(relPath, rfConfig, lcaPath) {
|
|
1184
1224
|
const relToLca = import_path13.default.relative(lcaPath, relPath).replace(/\\/g, "/");
|
|
1185
1225
|
for (let i = 0; i < rfConfig.rootNodes.length; i++) {
|
|
@@ -1283,6 +1323,10 @@ var rule6 = {
|
|
|
1283
1323
|
return;
|
|
1284
1324
|
}
|
|
1285
1325
|
if (subB === "" && classifyFolder(lca, config) === "fragment") {
|
|
1326
|
+
context.report({
|
|
1327
|
+
message: `Peer separation violation: "${subA}" cannot import directly from the root of Fragment "${lca}". A node nested inside a Private Category or Fractal Branch has no privileged access to its owning Fragment's other direct children; promote the shared resource to a Fractal Branch if the sharing need is genuine.`,
|
|
1328
|
+
node
|
|
1329
|
+
});
|
|
1286
1330
|
return;
|
|
1287
1331
|
}
|
|
1288
1332
|
const rfConfig = getRootFragmentConfig(lca, config);
|
|
@@ -1299,6 +1343,15 @@ var rule6 = {
|
|
|
1299
1343
|
return;
|
|
1300
1344
|
}
|
|
1301
1345
|
}
|
|
1346
|
+
const isSubARootFragment = classifyFolder(import_path13.default.posix.join(lca, subA), config) === "root-fragment";
|
|
1347
|
+
const isSubBRootFragment = classifyFolder(import_path13.default.posix.join(lca, subB), config) === "root-fragment";
|
|
1348
|
+
if (isSubARootFragment || isSubBRootFragment) {
|
|
1349
|
+
context.report({
|
|
1350
|
+
message: `Root Node import violation: "${relPath}" cannot import from "${resolvedRelPath}" because no "rootNodes" relationship is configured under Root Fragment "${lca}". Relationships between Root Nodes must always be explicitly authorized by the architect.`,
|
|
1351
|
+
node
|
|
1352
|
+
});
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1302
1355
|
const { allowed, defined: hasDefinedHierarchy } = resolveHorizontalHierarchy(lca, subA, subB, config);
|
|
1303
1356
|
if (hasDefinedHierarchy) {
|
|
1304
1357
|
if (!allowed) {
|
|
@@ -1308,23 +1361,10 @@ var rule6 = {
|
|
|
1308
1361
|
});
|
|
1309
1362
|
}
|
|
1310
1363
|
} else {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
if (idxA !== -1 && idxB !== -1) {
|
|
1316
|
-
if (idxB >= idxA) {
|
|
1317
|
-
context.report({
|
|
1318
|
-
message: `Horizontal hierarchy violation (fallback to roles): sibling directory "${subA}" (mapped to role "${roleA}", level ${idxA}) cannot import from "${subB}" (mapped to role "${roleB}", level ${idxB}) under parent "${lca}".`,
|
|
1319
|
-
node
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
} else {
|
|
1323
|
-
context.report({
|
|
1324
|
-
message: `Peer separation violation: sibling directories "${subA}" and "${subB}" cannot import each other because no horizontal hierarchy is defined under parent "${lca}".`,
|
|
1325
|
-
node
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1364
|
+
context.report({
|
|
1365
|
+
message: `Peer separation violation: sibling directories "${subA}" and "${subB}" cannot import each other because no horizontal hierarchy is defined under parent "${lca}".`,
|
|
1366
|
+
node
|
|
1367
|
+
});
|
|
1328
1368
|
}
|
|
1329
1369
|
}
|
|
1330
1370
|
return {
|
|
@@ -1378,6 +1418,16 @@ var rule7 = {
|
|
|
1378
1418
|
const info = getPrivateCategoryInfo(importedDir, config);
|
|
1379
1419
|
if (info) {
|
|
1380
1420
|
const { owner, privateCategoryPath } = info;
|
|
1421
|
+
let isFractalBranchInPrivateCategory = false;
|
|
1422
|
+
let isFractalBranchAllowed = false;
|
|
1423
|
+
const fbOwner = getFractalBranchOwner(importedDir);
|
|
1424
|
+
if (fbOwner) {
|
|
1425
|
+
const isFbInsidePrivateCategory = fbOwner === privateCategoryPath || fbOwner.startsWith(privateCategoryPath + "/");
|
|
1426
|
+
if (isFbInsidePrivateCategory) {
|
|
1427
|
+
isFractalBranchInPrivateCategory = true;
|
|
1428
|
+
isFractalBranchAllowed = relPath === fbOwner || relPath.startsWith(fbOwner + "/");
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1381
1431
|
const isDirectChild = import_path14.default.dirname(relPath) === owner;
|
|
1382
1432
|
const importerSubDomain = getImmediateSubDomain(
|
|
1383
1433
|
relPath,
|
|
@@ -1388,15 +1438,8 @@ var rule7 = {
|
|
|
1388
1438
|
privateCategoryPath
|
|
1389
1439
|
);
|
|
1390
1440
|
const isInsideSameSubDomain = importerSubDomain !== "" && importerSubDomain === importedSubDomain;
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
if (fbOwner) {
|
|
1394
|
-
const isFbInsidePrivateCategory = fbOwner === privateCategoryPath || fbOwner.startsWith(privateCategoryPath + "/");
|
|
1395
|
-
if (isFbInsidePrivateCategory) {
|
|
1396
|
-
isFractalBranchAllowed = relPath === fbOwner || relPath.startsWith(fbOwner + "/");
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
if (!isDirectChild && !isInsideSameSubDomain && !isFractalBranchAllowed) {
|
|
1441
|
+
const isAllowed = isFractalBranchInPrivateCategory ? isFractalBranchAllowed : isDirectChild || isInsideSameSubDomain;
|
|
1442
|
+
if (!isAllowed) {
|
|
1400
1443
|
context.report({
|
|
1401
1444
|
message: `Importing from Private Category is forbidden. The imported resource is private to "${owner}".`,
|
|
1402
1445
|
node
|