@morriconeluca/eslint-plugin-faf 0.1.1 → 0.3.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 +24 -10
- package/dist/main.cjs +185 -54
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +185 -54
- package/dist/main.js.map +1 -1
- package/package.json +7 -2
package/dist/main.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@morriconeluca/eslint-plugin-faf",
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.3.0",
|
|
5
5
|
description: "An ESLint plugin for FAF projects.",
|
|
6
|
+
repository: {
|
|
7
|
+
type: "git",
|
|
8
|
+
url: "git+https://github.com/morriconeluca/eslint-plugin-faf.git"
|
|
9
|
+
},
|
|
6
10
|
main: "./dist/main.cjs",
|
|
7
11
|
module: "./dist/main.js",
|
|
8
12
|
types: "./dist/main.d.ts",
|
|
@@ -14,7 +18,8 @@ var package_default = {
|
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
publishConfig: {
|
|
17
|
-
access: "public"
|
|
21
|
+
access: "public",
|
|
22
|
+
provenance: true
|
|
18
23
|
},
|
|
19
24
|
imports: {
|
|
20
25
|
"#_rules@shared/*": "./src/_rules/_rules@shared/*",
|
|
@@ -315,6 +320,9 @@ var rule = {
|
|
|
315
320
|
if (catConfig) {
|
|
316
321
|
const ext = path5.extname(fileName);
|
|
317
322
|
const isAllowedAsset = catConfig.allowedExtensions?.includes(ext) ?? false;
|
|
323
|
+
if (!isAllowedAsset && catConfig.allowedExtensions && catConfig.allowedExtensions.length > 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
318
326
|
if (!catConfig.allowSingleFiles && !isAllowedAsset) {
|
|
319
327
|
context.report({
|
|
320
328
|
message: `Category "${folderName}" cannot contain file nodes directly. File "${fileName}" must be encapsulated inside a Fragment.`,
|
|
@@ -434,6 +442,17 @@ function getProjectRoot() {
|
|
|
434
442
|
|
|
435
443
|
// src/_rules/naming-conventions/naming-conventions.rule.ts
|
|
436
444
|
var KEBAB_CASE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
445
|
+
var DEFAULT_HTTP_METHODS = [
|
|
446
|
+
"connect",
|
|
447
|
+
"delete",
|
|
448
|
+
"get",
|
|
449
|
+
"head",
|
|
450
|
+
"options",
|
|
451
|
+
"patch",
|
|
452
|
+
"post",
|
|
453
|
+
"put",
|
|
454
|
+
"trace"
|
|
455
|
+
];
|
|
437
456
|
var rule3 = {
|
|
438
457
|
create(context) {
|
|
439
458
|
return {
|
|
@@ -597,15 +616,6 @@ var rule3 = {
|
|
|
597
616
|
if (parentType === "invalid-fragment") {
|
|
598
617
|
return;
|
|
599
618
|
}
|
|
600
|
-
if (fileName === "index.ts" || fileName === "index.js") {
|
|
601
|
-
if (parentType !== "fragment") {
|
|
602
|
-
context.report({
|
|
603
|
-
message: `Access Nodes ("index.ts/index.js") are exclusive to Fragments. Found index file directly inside "${folderName}" (classified as ${parentType}).`,
|
|
604
|
-
node
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
619
|
const rfConfig = getRootFragmentConfig(relDir, config);
|
|
610
620
|
if (rfConfig) {
|
|
611
621
|
const flatRootNodes = rfConfig.rootNodes.flat();
|
|
@@ -621,11 +631,34 @@ var rule3 = {
|
|
|
621
631
|
return;
|
|
622
632
|
}
|
|
623
633
|
}
|
|
634
|
+
if (fileName === "index.ts" || fileName === "index.js") {
|
|
635
|
+
if (parentType !== "fragment") {
|
|
636
|
+
context.report({
|
|
637
|
+
message: `Access Nodes ("index.ts/index.js") are exclusive to Fragments. Found index file directly inside "${folderName}" (classified as ${parentType}).`,
|
|
638
|
+
node
|
|
639
|
+
});
|
|
640
|
+
} else {
|
|
641
|
+
const contents = readDirCached(relDir);
|
|
642
|
+
const hasFragmentNode = contents.files.some(
|
|
643
|
+
(f) => f !== "index.ts" && f !== "index.js" && f !== "README.md" && f !== "package.json" && f !== "tsconfig.json"
|
|
644
|
+
);
|
|
645
|
+
if (!hasFragmentNode) {
|
|
646
|
+
context.report({
|
|
647
|
+
message: `Fragment "${folderName}" has no Master Node. Every Fragment must contain at least one Fragment Node establishing its Role.`,
|
|
648
|
+
node
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
624
654
|
if (fileName === "README.md" || fileName === "package.json" || fileName === "tsconfig.json") {
|
|
625
655
|
return;
|
|
626
656
|
}
|
|
627
657
|
const role = getFileRole(fileName, config);
|
|
628
658
|
const ext = path7.extname(fileName);
|
|
659
|
+
const allRoleNames = new Set(config.roles.flat());
|
|
660
|
+
const roleSegments = fileName.slice(0, fileName.length - ext.length).split(".").slice(1).filter((part) => allRoleNames.has(part));
|
|
661
|
+
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.`;
|
|
629
662
|
if (parentType === "fragment") {
|
|
630
663
|
const matchedRoute = config.routeHierarchies?.find(
|
|
631
664
|
(rh) => rh.paths.some((rp) => relDir === rp || relDir.startsWith(rp + "/"))
|
|
@@ -639,6 +672,16 @@ var rule3 = {
|
|
|
639
672
|
node
|
|
640
673
|
});
|
|
641
674
|
}
|
|
675
|
+
const httpMethods = matchedRoute.httpMethods ?? DEFAULT_HTTP_METHODS;
|
|
676
|
+
const routeNameRegex = new RegExp(
|
|
677
|
+
`^(${httpMethods.join("|")})-[a-z0-9]+(-[a-z0-9]+)*$`
|
|
678
|
+
);
|
|
679
|
+
if (!routeNameRegex.test(folderName)) {
|
|
680
|
+
context.report({
|
|
681
|
+
message: `Route terminal Fragment "${folderName}" must follow the "<method>-<object>" pattern with a lowercase HTTP method (${httpMethods.join(", ")}).`,
|
|
682
|
+
node
|
|
683
|
+
});
|
|
684
|
+
}
|
|
642
685
|
}
|
|
643
686
|
let currentParent = path7.dirname(relDir).replace(/\\/g, "/");
|
|
644
687
|
let categoryConfig = null;
|
|
@@ -677,6 +720,19 @@ var rule3 = {
|
|
|
677
720
|
message: `File "${fileName}" inside Fragment "${folderName}" must have an explicit role suffix (e.g. "${folderName}.style${ext}").`,
|
|
678
721
|
node
|
|
679
722
|
});
|
|
723
|
+
} else if (roleSegments.length > 1) {
|
|
724
|
+
context.report({ message: multiRoleMessage, node });
|
|
725
|
+
} else {
|
|
726
|
+
const contents = readDirCached(relDir);
|
|
727
|
+
const duplicate = contents.files.find(
|
|
728
|
+
(f) => f !== fileName && f !== "index.ts" && f !== "index.js" && getFileRole(f, config) === role
|
|
729
|
+
);
|
|
730
|
+
if (duplicate) {
|
|
731
|
+
context.report({
|
|
732
|
+
message: `Fragment Node "${fileName}" shares Role "${role}" with sibling "${duplicate}". Each Fragment Node must have a unique Role within its Fragment.`,
|
|
733
|
+
node
|
|
734
|
+
});
|
|
735
|
+
}
|
|
680
736
|
}
|
|
681
737
|
}
|
|
682
738
|
if (parentType === "category") {
|
|
@@ -707,6 +763,8 @@ var rule3 = {
|
|
|
707
763
|
message: `Role "${role}" for file "${fileName}" does not match the expected Category role "${catConfig.role}".`,
|
|
708
764
|
node
|
|
709
765
|
});
|
|
766
|
+
} else if (roleSegments.length > 1) {
|
|
767
|
+
context.report({ message: multiRoleMessage, node });
|
|
710
768
|
}
|
|
711
769
|
} else {
|
|
712
770
|
if (role && role !== catConfig.role) {
|
|
@@ -714,6 +772,8 @@ var rule3 = {
|
|
|
714
772
|
message: `Role "${role}" for asset "${fileName}" does not match the expected Category role "${catConfig.role}".`,
|
|
715
773
|
node
|
|
716
774
|
});
|
|
775
|
+
} else if (role && roleSegments.length > 1) {
|
|
776
|
+
context.report({ message: multiRoleMessage, node });
|
|
717
777
|
}
|
|
718
778
|
}
|
|
719
779
|
}
|
|
@@ -783,6 +843,16 @@ function _resolveImportPath(importPath, currentFileRelative, settings) {
|
|
|
783
843
|
}
|
|
784
844
|
return finalizeResolvedPath(relResolved);
|
|
785
845
|
}
|
|
846
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
847
|
+
"cjs",
|
|
848
|
+
"cts",
|
|
849
|
+
"js",
|
|
850
|
+
"jsx",
|
|
851
|
+
"mjs",
|
|
852
|
+
"mts",
|
|
853
|
+
"ts",
|
|
854
|
+
"tsx"
|
|
855
|
+
]);
|
|
786
856
|
function finalizeResolvedPath(relResolved) {
|
|
787
857
|
const resolvedDir = path8.dirname(relResolved).replace(/\\/g, "/");
|
|
788
858
|
const resolvedBase = path8.basename(relResolved);
|
|
@@ -796,9 +866,12 @@ function finalizeResolvedPath(relResolved) {
|
|
|
796
866
|
const parentContents = readDirCached(resolvedDir);
|
|
797
867
|
const exactMatch = parentContents.files.find((f) => f === resolvedBase);
|
|
798
868
|
if (!exactMatch) {
|
|
799
|
-
const
|
|
869
|
+
const candidates = parentContents.files.filter(
|
|
800
870
|
(f) => f.startsWith(resolvedBase + ".")
|
|
801
871
|
);
|
|
872
|
+
const matchWithExt = candidates.find(
|
|
873
|
+
(f) => SCRIPT_EXTENSIONS.has(f.slice(resolvedBase.length + 1))
|
|
874
|
+
) ?? candidates[0];
|
|
802
875
|
if (matchWithExt) {
|
|
803
876
|
return (resolvedDir === "." ? "" : resolvedDir + "/") + matchWithExt;
|
|
804
877
|
}
|
|
@@ -867,7 +940,7 @@ var rule4 = {
|
|
|
867
940
|
if (importedFolderType === "fragment") {
|
|
868
941
|
const isAccessNode = importedFile === "index.ts" || importedFile === "index.js";
|
|
869
942
|
if (!isAccessNode) {
|
|
870
|
-
const isInside = relPath === importedDir
|
|
943
|
+
const isInside = path9.dirname(relPath) === importedDir;
|
|
871
944
|
if (!isInside) {
|
|
872
945
|
context.report({
|
|
873
946
|
message: `Direct import of Fragment internal file "${importedFile}" is forbidden. You must import through its Access Node (index file) in "${importedDir}".`,
|
|
@@ -902,43 +975,56 @@ var rule4 = {
|
|
|
902
975
|
var no_direct_fragment_import_rule_default = rule4;
|
|
903
976
|
|
|
904
977
|
// src/_rules/no-fractal-branch-leak/no-fractal-branch-leak.rule.ts
|
|
905
|
-
import
|
|
978
|
+
import path12 from "path";
|
|
906
979
|
|
|
907
|
-
// src/_rules/_rules@shared/_utils/
|
|
980
|
+
// src/_rules/_rules@shared/_utils/_primitives/get-fractal-branch-owner/get-fractal-branch-owner.util.ts
|
|
908
981
|
import path10 from "path";
|
|
909
|
-
function
|
|
982
|
+
function getFractalBranchOwner(relPath) {
|
|
910
983
|
let current = relPath;
|
|
911
984
|
while (current && current !== "." && current !== "/") {
|
|
985
|
+
const folderName = path10.basename(current);
|
|
986
|
+
if (folderName.startsWith("_") && folderName.includes("@shared")) {
|
|
987
|
+
return path10.dirname(current).replace(/\\/g, "/");
|
|
988
|
+
}
|
|
912
989
|
const parent = path10.dirname(current).replace(/\\/g, "/");
|
|
913
990
|
if (parent === current) {
|
|
914
991
|
break;
|
|
915
992
|
}
|
|
916
|
-
const currentType = classifyFolder(current, config);
|
|
917
|
-
const parentType = classifyFolder(parent, config);
|
|
918
|
-
if (currentType === "category" && (parentType === "fragment" || parentType === "root-fragment")) {
|
|
919
|
-
return parent;
|
|
920
|
-
}
|
|
921
993
|
current = parent;
|
|
922
994
|
}
|
|
923
995
|
return null;
|
|
924
996
|
}
|
|
925
997
|
|
|
926
|
-
// src/_rules/
|
|
927
|
-
function
|
|
998
|
+
// src/_rules/_rules@shared/_utils/_primitives/get-immediate-sub-domain/get-immediate-sub-domain.util.ts
|
|
999
|
+
function getImmediateSubDomain(relPath, privateCategoryPath) {
|
|
1000
|
+
if (relPath !== privateCategoryPath && !relPath.startsWith(privateCategoryPath + "/")) {
|
|
1001
|
+
return "";
|
|
1002
|
+
}
|
|
1003
|
+
const relative = relPath.substring(privateCategoryPath.length + 1);
|
|
1004
|
+
const firstSegment = relative.split("/")[0];
|
|
1005
|
+
return firstSegment ? privateCategoryPath + "/" + firstSegment : privateCategoryPath;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/_rules/_rules@shared/_utils/_systems/get-private-category-info/get-private-category-info.util.ts
|
|
1009
|
+
import path11 from "path";
|
|
1010
|
+
function getPrivateCategoryInfo(relPath, config) {
|
|
928
1011
|
let current = relPath;
|
|
929
1012
|
while (current && current !== "." && current !== "/") {
|
|
930
|
-
const folderName = path11.basename(current);
|
|
931
|
-
if (folderName.startsWith("_") && folderName.includes("@shared")) {
|
|
932
|
-
return path11.dirname(current).replace(/\\/g, "/");
|
|
933
|
-
}
|
|
934
1013
|
const parent = path11.dirname(current).replace(/\\/g, "/");
|
|
935
1014
|
if (parent === current) {
|
|
936
1015
|
break;
|
|
937
1016
|
}
|
|
1017
|
+
const currentType = classifyFolder(current, config);
|
|
1018
|
+
const parentType = classifyFolder(parent, config);
|
|
1019
|
+
if (currentType === "category" && (parentType === "fragment" || parentType === "root-fragment")) {
|
|
1020
|
+
return { owner: parent, privateCategoryPath: current };
|
|
1021
|
+
}
|
|
938
1022
|
current = parent;
|
|
939
1023
|
}
|
|
940
1024
|
return null;
|
|
941
1025
|
}
|
|
1026
|
+
|
|
1027
|
+
// src/_rules/no-fractal-branch-leak/no-fractal-branch-leak.rule.ts
|
|
942
1028
|
var rule5 = {
|
|
943
1029
|
create(context) {
|
|
944
1030
|
const absPath = context.filename;
|
|
@@ -960,22 +1046,33 @@ var rule5 = {
|
|
|
960
1046
|
if (!resolvedRelPath) {
|
|
961
1047
|
return;
|
|
962
1048
|
}
|
|
963
|
-
const importedDir =
|
|
964
|
-
const
|
|
965
|
-
if (
|
|
966
|
-
const isDescendant = relPath ===
|
|
1049
|
+
const importedDir = path12.dirname(resolvedRelPath);
|
|
1050
|
+
const fractalBranchOwner = getFractalBranchOwner(importedDir);
|
|
1051
|
+
if (fractalBranchOwner) {
|
|
1052
|
+
const isDescendant = relPath === fractalBranchOwner || relPath.startsWith(fractalBranchOwner + "/");
|
|
967
1053
|
if (!isDescendant) {
|
|
968
1054
|
context.report({
|
|
969
|
-
message: `Importing from Fractal Branch is forbidden. The imported resource is private to "${
|
|
1055
|
+
message: `Importing from Fractal Branch is forbidden. The imported resource is private to "${fractalBranchOwner}" and its descendants.`,
|
|
970
1056
|
node
|
|
971
1057
|
});
|
|
972
1058
|
} else {
|
|
973
|
-
const
|
|
974
|
-
if (
|
|
975
|
-
const
|
|
976
|
-
|
|
1059
|
+
const info = getPrivateCategoryInfo(importedDir, config);
|
|
1060
|
+
if (info) {
|
|
1061
|
+
const { owner, privateCategoryPath } = info;
|
|
1062
|
+
const isPrivateDirectChild = path12.dirname(relPath) === owner;
|
|
1063
|
+
const importerSubDomain = getImmediateSubDomain(
|
|
1064
|
+
relPath,
|
|
1065
|
+
privateCategoryPath
|
|
1066
|
+
);
|
|
1067
|
+
const importedSubDomain = getImmediateSubDomain(
|
|
1068
|
+
resolvedRelPath,
|
|
1069
|
+
privateCategoryPath
|
|
1070
|
+
);
|
|
1071
|
+
const isInsideSameSubDomain = importerSubDomain !== "" && importerSubDomain === importedSubDomain;
|
|
1072
|
+
const isFractalBranchAllowed = fractalBranchOwner === privateCategoryPath || fractalBranchOwner.startsWith(privateCategoryPath + "/");
|
|
1073
|
+
if (!isPrivateDirectChild && !isInsideSameSubDomain && !isFractalBranchAllowed) {
|
|
977
1074
|
context.report({
|
|
978
|
-
message: `Importing from Private Category is forbidden. The imported resource is private to "${
|
|
1075
|
+
message: `Importing from Private Category is forbidden. The imported resource is private to "${owner}".`,
|
|
979
1076
|
node
|
|
980
1077
|
});
|
|
981
1078
|
}
|
|
@@ -1008,7 +1105,7 @@ var rule5 = {
|
|
|
1008
1105
|
var no_fractal_branch_leak_rule_default = rule5;
|
|
1009
1106
|
|
|
1010
1107
|
// src/_rules/no-peer-dependency/no-peer-dependency.rule.ts
|
|
1011
|
-
import
|
|
1108
|
+
import path13 from "path";
|
|
1012
1109
|
|
|
1013
1110
|
// src/_rules/_rules@shared/_utils/_primitives/find-tree-config-including-excluded/find-tree-config-including-excluded.util.ts
|
|
1014
1111
|
function findTreeConfigIncludingExcluded(relPath, settings) {
|
|
@@ -1086,7 +1183,7 @@ function resolveHorizontalHierarchy(lcaPath, subA, subB, config) {
|
|
|
1086
1183
|
|
|
1087
1184
|
// src/_rules/no-peer-dependency/no-peer-dependency.rule.ts
|
|
1088
1185
|
function getDirectoryRole(dirPath, config) {
|
|
1089
|
-
const folderName =
|
|
1186
|
+
const folderName = path13.basename(dirPath);
|
|
1090
1187
|
if (folderName.startsWith("_")) {
|
|
1091
1188
|
return getRoleFromFolderName(folderName, config);
|
|
1092
1189
|
}
|
|
@@ -1104,7 +1201,7 @@ function getRoleFromFolderName(name, config) {
|
|
|
1104
1201
|
return catConfig ? catConfig.role ?? null : null;
|
|
1105
1202
|
}
|
|
1106
1203
|
function getRootNodeIndex(relPath, rfConfig, lcaPath) {
|
|
1107
|
-
const relToLca =
|
|
1204
|
+
const relToLca = path13.relative(lcaPath, relPath).replace(/\\/g, "/");
|
|
1108
1205
|
for (let i = 0; i < rfConfig.rootNodes.length; i++) {
|
|
1109
1206
|
const group = rfConfig.rootNodes[i];
|
|
1110
1207
|
if (group && group.includes(relToLca)) {
|
|
@@ -1116,7 +1213,7 @@ function getRootNodeIndex(relPath, rfConfig, lcaPath) {
|
|
|
1116
1213
|
function isInsidePrivateCategoryOfLca(relPathB, lcaPath, config) {
|
|
1117
1214
|
let current = relPathB;
|
|
1118
1215
|
while (current && current !== "." && current !== "/" && current !== lcaPath) {
|
|
1119
|
-
const parent =
|
|
1216
|
+
const parent = path13.dirname(current).replace(/\\/g, "/");
|
|
1120
1217
|
if (parent === current) {
|
|
1121
1218
|
break;
|
|
1122
1219
|
}
|
|
@@ -1141,7 +1238,7 @@ var rule6 = {
|
|
|
1141
1238
|
if (!config) {
|
|
1142
1239
|
return {};
|
|
1143
1240
|
}
|
|
1144
|
-
const currentDir =
|
|
1241
|
+
const currentDir = path13.dirname(relPath);
|
|
1145
1242
|
const isCurrentFileExcluded = config.excludes?.some(
|
|
1146
1243
|
(exc) => relPath === exc || relPath.startsWith(exc + "/")
|
|
1147
1244
|
) ?? false;
|
|
@@ -1174,10 +1271,10 @@ var rule6 = {
|
|
|
1174
1271
|
if (isCurrentFileExcluded || isImportedFileExcluded) {
|
|
1175
1272
|
return;
|
|
1176
1273
|
}
|
|
1177
|
-
const importedDir =
|
|
1274
|
+
const importedDir = path13.dirname(resolvedRelPath);
|
|
1178
1275
|
if (currentDir === importedDir) {
|
|
1179
|
-
const fileA =
|
|
1180
|
-
const fileB =
|
|
1276
|
+
const fileA = path13.basename(relPath);
|
|
1277
|
+
const fileB = path13.basename(resolvedRelPath);
|
|
1181
1278
|
const roleA = getFileRole(fileA, config);
|
|
1182
1279
|
const roleB = getFileRole(fileB, config);
|
|
1183
1280
|
if (roleA && roleB) {
|
|
@@ -1206,6 +1303,10 @@ var rule6 = {
|
|
|
1206
1303
|
return;
|
|
1207
1304
|
}
|
|
1208
1305
|
if (subB === "" && classifyFolder(lca, config) === "fragment") {
|
|
1306
|
+
context.report({
|
|
1307
|
+
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.`,
|
|
1308
|
+
node
|
|
1309
|
+
});
|
|
1209
1310
|
return;
|
|
1210
1311
|
}
|
|
1211
1312
|
const rfConfig = getRootFragmentConfig(lca, config);
|
|
@@ -1215,13 +1316,22 @@ var rule6 = {
|
|
|
1215
1316
|
if (idxRootA !== -1 && idxRootB !== -1) {
|
|
1216
1317
|
if (idxRootB >= idxRootA) {
|
|
1217
1318
|
context.report({
|
|
1218
|
-
message: `Root Node import violation: "${
|
|
1319
|
+
message: `Root Node import violation: "${path13.basename(relPath)}" cannot import from "${path13.basename(resolvedRelPath)}" under Root Fragment "${lca}".`,
|
|
1219
1320
|
node
|
|
1220
1321
|
});
|
|
1221
1322
|
}
|
|
1222
1323
|
return;
|
|
1223
1324
|
}
|
|
1224
1325
|
}
|
|
1326
|
+
const isSubARootFragment = classifyFolder(path13.posix.join(lca, subA), config) === "root-fragment";
|
|
1327
|
+
const isSubBRootFragment = classifyFolder(path13.posix.join(lca, subB), config) === "root-fragment";
|
|
1328
|
+
if (isSubARootFragment || isSubBRootFragment) {
|
|
1329
|
+
context.report({
|
|
1330
|
+
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.`,
|
|
1331
|
+
node
|
|
1332
|
+
});
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1225
1335
|
const { allowed, defined: hasDefinedHierarchy } = resolveHorizontalHierarchy(lca, subA, subB, config);
|
|
1226
1336
|
if (hasDefinedHierarchy) {
|
|
1227
1337
|
if (!allowed) {
|
|
@@ -1231,8 +1341,8 @@ var rule6 = {
|
|
|
1231
1341
|
});
|
|
1232
1342
|
}
|
|
1233
1343
|
} else {
|
|
1234
|
-
const roleA = getDirectoryRole(
|
|
1235
|
-
const roleB = getDirectoryRole(
|
|
1344
|
+
const roleA = getDirectoryRole(path13.posix.join(lca, subA), config);
|
|
1345
|
+
const roleB = getDirectoryRole(path13.posix.join(lca, subB), config);
|
|
1236
1346
|
const idxA = roleA ? getRoleHierarchyIndex(roleA, config) : -1;
|
|
1237
1347
|
const idxB = roleB ? getRoleHierarchyIndex(roleB, config) : -1;
|
|
1238
1348
|
if (idxA !== -1 && idxB !== -1) {
|
|
@@ -1275,7 +1385,7 @@ var rule6 = {
|
|
|
1275
1385
|
var no_peer_dependency_rule_default = rule6;
|
|
1276
1386
|
|
|
1277
1387
|
// src/_rules/no-private-category-leak/no-private-category-leak.rule.ts
|
|
1278
|
-
import
|
|
1388
|
+
import path14 from "path";
|
|
1279
1389
|
var rule7 = {
|
|
1280
1390
|
create(context) {
|
|
1281
1391
|
const absPath = context.filename;
|
|
@@ -1297,11 +1407,32 @@ var rule7 = {
|
|
|
1297
1407
|
if (!resolvedRelPath) {
|
|
1298
1408
|
return;
|
|
1299
1409
|
}
|
|
1300
|
-
const importedDir =
|
|
1301
|
-
const
|
|
1302
|
-
if (
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1410
|
+
const importedDir = path14.dirname(resolvedRelPath);
|
|
1411
|
+
const info = getPrivateCategoryInfo(importedDir, config);
|
|
1412
|
+
if (info) {
|
|
1413
|
+
const { owner, privateCategoryPath } = info;
|
|
1414
|
+
let isFractalBranchInPrivateCategory = false;
|
|
1415
|
+
let isFractalBranchAllowed = false;
|
|
1416
|
+
const fbOwner = getFractalBranchOwner(importedDir);
|
|
1417
|
+
if (fbOwner) {
|
|
1418
|
+
const isFbInsidePrivateCategory = fbOwner === privateCategoryPath || fbOwner.startsWith(privateCategoryPath + "/");
|
|
1419
|
+
if (isFbInsidePrivateCategory) {
|
|
1420
|
+
isFractalBranchInPrivateCategory = true;
|
|
1421
|
+
isFractalBranchAllowed = relPath === fbOwner || relPath.startsWith(fbOwner + "/");
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
const isDirectChild = path14.dirname(relPath) === owner;
|
|
1425
|
+
const importerSubDomain = getImmediateSubDomain(
|
|
1426
|
+
relPath,
|
|
1427
|
+
privateCategoryPath
|
|
1428
|
+
);
|
|
1429
|
+
const importedSubDomain = getImmediateSubDomain(
|
|
1430
|
+
resolvedRelPath,
|
|
1431
|
+
privateCategoryPath
|
|
1432
|
+
);
|
|
1433
|
+
const isInsideSameSubDomain = importerSubDomain !== "" && importerSubDomain === importedSubDomain;
|
|
1434
|
+
const isAllowed = isFractalBranchInPrivateCategory ? isFractalBranchAllowed : isDirectChild || isInsideSameSubDomain;
|
|
1435
|
+
if (!isAllowed) {
|
|
1305
1436
|
context.report({
|
|
1306
1437
|
message: `Importing from Private Category is forbidden. The imported resource is private to "${owner}".`,
|
|
1307
1438
|
node
|