@conarti/eslint-plugin-feature-sliced 2.0.0-rc.5 → 2.0.0-rc.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/dist/index.cjs +246 -11
- package/dist/index.d.cts +67 -15
- package/dist/index.d.ts +67 -15
- package/dist/index.js +246 -11
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,8 @@ var RULE_NAMES = {
|
|
|
4
4
|
LAYERS_SLICES: `${PLUGIN_NAME}/layers-slices`,
|
|
5
5
|
ABSOLUTE_RELATIVE: `${PLUGIN_NAME}/absolute-relative`,
|
|
6
6
|
PUBLIC_API: `${PLUGIN_NAME}/public-api`,
|
|
7
|
-
IMPORT_ORDER: `${PLUGIN_NAME}/import-order
|
|
7
|
+
IMPORT_ORDER: `${PLUGIN_NAME}/import-order`,
|
|
8
|
+
NO_CROSS_SEGMENT_REEXPORT: `${PLUGIN_NAME}/no-cross-segment-reexport`
|
|
8
9
|
};
|
|
9
10
|
var layers = [
|
|
10
11
|
"shared",
|
|
@@ -79,7 +80,7 @@ function canLayerContainSlices(layer, config) {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// package.json
|
|
82
|
-
var version = "2.0.0-rc.
|
|
83
|
+
var version = "2.0.0-rc.7";
|
|
83
84
|
|
|
84
85
|
// src/rules/index.ts
|
|
85
86
|
var _eslintpluginimportx = require('eslint-plugin-import-x'); var _eslintpluginimportx2 = _interopRequireDefault(_eslintpluginimportx);
|
|
@@ -840,6 +841,228 @@ var layers_slices_default = createEslintRule({
|
|
|
840
841
|
}
|
|
841
842
|
});
|
|
842
843
|
|
|
844
|
+
// src/rules/no-cross-segment-reexport/config.ts
|
|
845
|
+
var ERROR_MESSAGE_ID3 = {
|
|
846
|
+
NO_CROSS_SEGMENT_REEXPORT: "no-cross-segment-reexport",
|
|
847
|
+
MOVE_TO_SLICE_PUBLIC_API_SUGGESTION: "move-to-slice-public-api-suggestion"
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/rules/no-cross-segment-reexport/model/errors.ts
|
|
851
|
+
function buildSlicePublicApiPath(sourcePath, targetSegment) {
|
|
852
|
+
const parts = sourcePath.split("/");
|
|
853
|
+
const segmentIndex = parts.findIndex((part) => part === targetSegment);
|
|
854
|
+
if (segmentIndex !== -1) {
|
|
855
|
+
return parts.slice(0, segmentIndex).join("/");
|
|
856
|
+
}
|
|
857
|
+
return parts.slice(0, -1).join("/") || "..";
|
|
858
|
+
}
|
|
859
|
+
function reportCrossSegmentReexport(context, node, currentSegment, targetSegment) {
|
|
860
|
+
const sourcePath = node.source.value;
|
|
861
|
+
const suggestedPath = buildSlicePublicApiPath(sourcePath, targetSegment);
|
|
862
|
+
context.report({
|
|
863
|
+
node: node.source,
|
|
864
|
+
messageId: ERROR_MESSAGE_ID3.NO_CROSS_SEGMENT_REEXPORT,
|
|
865
|
+
data: {
|
|
866
|
+
currentSegment,
|
|
867
|
+
targetSegment
|
|
868
|
+
},
|
|
869
|
+
suggest: [
|
|
870
|
+
{
|
|
871
|
+
messageId: ERROR_MESSAGE_ID3.MOVE_TO_SLICE_PUBLIC_API_SUGGESTION,
|
|
872
|
+
data: {
|
|
873
|
+
suggestedPath
|
|
874
|
+
},
|
|
875
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
876
|
+
getSourceRangeWithoutQuotes(node.source.range),
|
|
877
|
+
suggestedPath
|
|
878
|
+
)
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/rules/no-cross-segment-reexport/model/is-cross-segment-reexport.ts
|
|
885
|
+
var NOT_CROSS_SEGMENT = {
|
|
886
|
+
isCrossSegmentReexport: false,
|
|
887
|
+
currentSegment: null,
|
|
888
|
+
targetSegment: null
|
|
889
|
+
};
|
|
890
|
+
var FILE_EXT_REGEXP = /\..+$/;
|
|
891
|
+
var KNOWN_SEGMENTS = segments.map((s) => s.toLowerCase());
|
|
892
|
+
function splitPathParts(path3) {
|
|
893
|
+
return path3.split("/").filter(Boolean);
|
|
894
|
+
}
|
|
895
|
+
function normalizeToDirParts(parts) {
|
|
896
|
+
const INDEX_FILE_REGEXP = /^index\..+$/;
|
|
897
|
+
return parts.reduce((acc, part) => {
|
|
898
|
+
if (INDEX_FILE_REGEXP.test(part))
|
|
899
|
+
return acc;
|
|
900
|
+
if (FILE_EXT_REGEXP.test(part)) {
|
|
901
|
+
acc.push(part.replace(FILE_EXT_REGEXP, ""));
|
|
902
|
+
return acc;
|
|
903
|
+
}
|
|
904
|
+
acc.push(part);
|
|
905
|
+
return acc;
|
|
906
|
+
}, []);
|
|
907
|
+
}
|
|
908
|
+
function findLayerIndex(parts, layersWithSlices2) {
|
|
909
|
+
return parts.findIndex(
|
|
910
|
+
(part) => layersWithSlices2.includes(part.toLowerCase())
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
function extractSegmentAndSlice(pathParts) {
|
|
914
|
+
if (pathParts.length < 2)
|
|
915
|
+
return null;
|
|
916
|
+
const knownSegmentIndex = pathParts.findIndex(
|
|
917
|
+
(part) => KNOWN_SEGMENTS.includes(part.toLowerCase())
|
|
918
|
+
);
|
|
919
|
+
let segmentIndex;
|
|
920
|
+
if (knownSegmentIndex > 0) {
|
|
921
|
+
segmentIndex = knownSegmentIndex;
|
|
922
|
+
} else if (knownSegmentIndex === 0) {
|
|
923
|
+
return null;
|
|
924
|
+
} else {
|
|
925
|
+
segmentIndex = pathParts.length - 1;
|
|
926
|
+
if (segmentIndex < 1)
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const segment = pathParts[segmentIndex];
|
|
930
|
+
const sliceParts = pathParts.slice(0, segmentIndex);
|
|
931
|
+
if (sliceParts.length === 0)
|
|
932
|
+
return null;
|
|
933
|
+
return { segment, sliceParts };
|
|
934
|
+
}
|
|
935
|
+
function findTargetSegmentInSameSlice(targetPathParts, currentSliceParts, currentSegment) {
|
|
936
|
+
if (targetPathParts.length === 0)
|
|
937
|
+
return null;
|
|
938
|
+
if (currentSliceParts.length > targetPathParts.length)
|
|
939
|
+
return null;
|
|
940
|
+
const targetHasSameSlice = currentSliceParts.every(
|
|
941
|
+
(part, i) => part.toLowerCase() === _optionalChain([targetPathParts, 'access', _9 => _9[i], 'optionalAccess', _10 => _10.toLowerCase, 'call', _11 => _11()])
|
|
942
|
+
);
|
|
943
|
+
if (!targetHasSameSlice)
|
|
944
|
+
return null;
|
|
945
|
+
const targetSegmentCandidate = targetPathParts[currentSliceParts.length];
|
|
946
|
+
if (!targetSegmentCandidate)
|
|
947
|
+
return null;
|
|
948
|
+
if (targetSegmentCandidate.toLowerCase() !== currentSegment.toLowerCase()) {
|
|
949
|
+
return targetSegmentCandidate;
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
function isCrossSegmentReexport(normalizedCurrentFilePath, absoluteTargetPath, config) {
|
|
954
|
+
const layersConfig = _nullishCoalesce(config, () => ( normalizeLayersConfig()));
|
|
955
|
+
const layersWithSlices2 = getLayersWithSlices(layersConfig).map((l) => l.toLowerCase());
|
|
956
|
+
const currentParts = splitPathParts(normalizedCurrentFilePath);
|
|
957
|
+
const targetParts = splitPathParts(absoluteTargetPath);
|
|
958
|
+
const currentLayerIndex = findLayerIndex(currentParts, layersWithSlices2);
|
|
959
|
+
if (currentLayerIndex === -1)
|
|
960
|
+
return NOT_CROSS_SEGMENT;
|
|
961
|
+
const currentAfterLayer = currentParts.slice(currentLayerIndex + 1);
|
|
962
|
+
const currentPathParts = normalizeToDirParts(currentAfterLayer);
|
|
963
|
+
const currentInfo = extractSegmentAndSlice(currentPathParts);
|
|
964
|
+
if (!currentInfo)
|
|
965
|
+
return NOT_CROSS_SEGMENT;
|
|
966
|
+
const targetLayerIndex = findLayerIndex(targetParts, layersWithSlices2);
|
|
967
|
+
if (targetLayerIndex === -1)
|
|
968
|
+
return NOT_CROSS_SEGMENT;
|
|
969
|
+
if (currentParts[currentLayerIndex].toLowerCase() !== targetParts[targetLayerIndex].toLowerCase()) {
|
|
970
|
+
return NOT_CROSS_SEGMENT;
|
|
971
|
+
}
|
|
972
|
+
const targetAfterLayer = targetParts.slice(targetLayerIndex + 1);
|
|
973
|
+
const targetPathParts = normalizeToDirParts(targetAfterLayer);
|
|
974
|
+
const targetSegment = findTargetSegmentInSameSlice(
|
|
975
|
+
targetPathParts,
|
|
976
|
+
currentInfo.sliceParts,
|
|
977
|
+
currentInfo.segment
|
|
978
|
+
);
|
|
979
|
+
if (!targetSegment)
|
|
980
|
+
return NOT_CROSS_SEGMENT;
|
|
981
|
+
return {
|
|
982
|
+
isCrossSegmentReexport: true,
|
|
983
|
+
currentSegment: currentInfo.segment,
|
|
984
|
+
targetSegment
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/rules/no-cross-segment-reexport/model/validate-and-report.ts
|
|
989
|
+
function validateAndReport3(node, context, optionsWithDefault, config) {
|
|
990
|
+
if (!hasPath(node))
|
|
991
|
+
return;
|
|
992
|
+
const isIgnored2 = isIgnoredTarget(node, optionsWithDefault) || isIgnoredCurrentFile(context, optionsWithDefault);
|
|
993
|
+
if (isIgnored2)
|
|
994
|
+
return;
|
|
995
|
+
const {
|
|
996
|
+
normalizedCurrentFilePath,
|
|
997
|
+
absoluteTargetPath
|
|
998
|
+
} = extractPaths(node, context);
|
|
999
|
+
const result = isCrossSegmentReexport(
|
|
1000
|
+
normalizedCurrentFilePath,
|
|
1001
|
+
absoluteTargetPath,
|
|
1002
|
+
config
|
|
1003
|
+
);
|
|
1004
|
+
if (!result.isCrossSegmentReexport)
|
|
1005
|
+
return;
|
|
1006
|
+
reportCrossSegmentReexport(
|
|
1007
|
+
context,
|
|
1008
|
+
node,
|
|
1009
|
+
result.currentSegment,
|
|
1010
|
+
result.targetSegment
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/rules/no-cross-segment-reexport/index.ts
|
|
1015
|
+
var no_cross_segment_reexport_default = createEslintRule({
|
|
1016
|
+
name: "no-cross-segment-reexport",
|
|
1017
|
+
meta: {
|
|
1018
|
+
type: "problem",
|
|
1019
|
+
docs: {
|
|
1020
|
+
description: "Checks for cross-segment re-exports within the same slice"
|
|
1021
|
+
},
|
|
1022
|
+
hasSuggestions: true,
|
|
1023
|
+
messages: {
|
|
1024
|
+
[ERROR_MESSAGE_ID3.NO_CROSS_SEGMENT_REEXPORT]: 'Segment "{{ currentSegment }}" should not re-export from sibling segment "{{ targetSegment }}". Move the re-export to the slice public API.',
|
|
1025
|
+
[ERROR_MESSAGE_ID3.MOVE_TO_SLICE_PUBLIC_API_SUGGESTION]: 'Replace import path with slice public API ("{{ suggestedPath }}")'
|
|
1026
|
+
},
|
|
1027
|
+
schema: [
|
|
1028
|
+
{
|
|
1029
|
+
type: "object",
|
|
1030
|
+
properties: {
|
|
1031
|
+
ignoreImports: {
|
|
1032
|
+
type: "array",
|
|
1033
|
+
items: {
|
|
1034
|
+
type: "string"
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
ignoreFiles: {
|
|
1038
|
+
type: "array",
|
|
1039
|
+
items: {
|
|
1040
|
+
type: "string"
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
]
|
|
1046
|
+
},
|
|
1047
|
+
defaultOptions: [
|
|
1048
|
+
{
|
|
1049
|
+
ignoreImports: [],
|
|
1050
|
+
ignoreFiles: []
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
create(context, optionsWithDefault) {
|
|
1054
|
+
const layersConfig = extractLayersConfig(context);
|
|
1055
|
+
return {
|
|
1056
|
+
ExportAllDeclaration(node) {
|
|
1057
|
+
validateAndReport3(node, context, optionsWithDefault, layersConfig);
|
|
1058
|
+
},
|
|
1059
|
+
ExportNamedDeclaration(node) {
|
|
1060
|
+
validateAndReport3(node, context, optionsWithDefault, layersConfig);
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
843
1066
|
// src/rules/public-api/config.ts
|
|
844
1067
|
var MESSAGE_ID = {
|
|
845
1068
|
SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api",
|
|
@@ -963,7 +1186,7 @@ function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig)
|
|
|
963
1186
|
}
|
|
964
1187
|
|
|
965
1188
|
// src/rules/public-api/model/validate-and-report.ts
|
|
966
|
-
function
|
|
1189
|
+
function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
967
1190
|
if (!hasPath(node)) {
|
|
968
1191
|
return;
|
|
969
1192
|
}
|
|
@@ -1053,16 +1276,16 @@ var public_api_default = createEslintRule({
|
|
|
1053
1276
|
const layersConfig = extractLayersConfig(context);
|
|
1054
1277
|
return {
|
|
1055
1278
|
ImportDeclaration(node) {
|
|
1056
|
-
|
|
1279
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1057
1280
|
},
|
|
1058
1281
|
ImportExpression(node) {
|
|
1059
|
-
|
|
1282
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1060
1283
|
},
|
|
1061
1284
|
ExportAllDeclaration(node) {
|
|
1062
|
-
|
|
1285
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1063
1286
|
},
|
|
1064
1287
|
ExportNamedDeclaration(node) {
|
|
1065
|
-
|
|
1288
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1066
1289
|
},
|
|
1067
1290
|
Program(node) {
|
|
1068
1291
|
validateAndReportProgram(node, context, optionsWithDefault);
|
|
@@ -1076,6 +1299,7 @@ var rules = {
|
|
|
1076
1299
|
"absolute-relative": absolute_relative_default,
|
|
1077
1300
|
"import-order": _eslintpluginimportx2.default.rules.order,
|
|
1078
1301
|
"layers-slices": layers_slices_default,
|
|
1302
|
+
"no-cross-segment-reexport": no_cross_segment_reexport_default,
|
|
1079
1303
|
"public-api": public_api_default
|
|
1080
1304
|
};
|
|
1081
1305
|
var rules_default = rules;
|
|
@@ -1154,14 +1378,16 @@ var importOrderRuleConfigs = createImportOrderRuleConfigs();
|
|
|
1154
1378
|
// src/create-plugin.ts
|
|
1155
1379
|
function createPlugin(options = {}) {
|
|
1156
1380
|
const {
|
|
1381
|
+
severity = "error",
|
|
1157
1382
|
layers: layers2,
|
|
1158
1383
|
sortImports = "recommended",
|
|
1159
1384
|
absoluteRelative,
|
|
1160
1385
|
layersSlices,
|
|
1161
|
-
publicApi
|
|
1386
|
+
publicApi,
|
|
1387
|
+
noCrossSegmentReexport
|
|
1162
1388
|
} = options;
|
|
1163
1389
|
const normalizedLayers = normalizeLayersConfig(layers2);
|
|
1164
|
-
const rules2 = defineRules({ absoluteRelative, layersSlices, publicApi, sortImports }, normalizedLayers);
|
|
1390
|
+
const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, noCrossSegmentReexport, sortImports }, normalizedLayers);
|
|
1165
1391
|
return {
|
|
1166
1392
|
name: PLUGIN_NAME,
|
|
1167
1393
|
plugins: {
|
|
@@ -1177,16 +1403,25 @@ function createPlugin(options = {}) {
|
|
|
1177
1403
|
}
|
|
1178
1404
|
function defineRules(options, layersConfig) {
|
|
1179
1405
|
const {
|
|
1406
|
+
severity: globalSeverity = "error",
|
|
1180
1407
|
absoluteRelative = {},
|
|
1181
1408
|
layersSlices = {},
|
|
1182
1409
|
publicApi = {},
|
|
1410
|
+
noCrossSegmentReexport = {},
|
|
1183
1411
|
sortImports = "recommended"
|
|
1184
1412
|
} = options;
|
|
1185
|
-
const createRuleEntry = (ruleOptions) =>
|
|
1413
|
+
const createRuleEntry = (ruleOptions) => {
|
|
1414
|
+
if (ruleOptions === false) {
|
|
1415
|
+
return "off";
|
|
1416
|
+
}
|
|
1417
|
+
const { severity = globalSeverity, ...restOptions } = ruleOptions;
|
|
1418
|
+
return [severity, restOptions];
|
|
1419
|
+
};
|
|
1186
1420
|
const rules2 = {
|
|
1187
1421
|
[RULE_NAMES.LAYERS_SLICES]: createRuleEntry(layersSlices),
|
|
1188
1422
|
[RULE_NAMES.ABSOLUTE_RELATIVE]: createRuleEntry(absoluteRelative),
|
|
1189
|
-
[RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi)
|
|
1423
|
+
[RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi),
|
|
1424
|
+
[RULE_NAMES.NO_CROSS_SEGMENT_REEXPORT]: createRuleEntry(noCrossSegmentReexport)
|
|
1190
1425
|
};
|
|
1191
1426
|
if (sortImports) {
|
|
1192
1427
|
const importOrderConfigs = createImportOrderRuleConfigs(layersConfig);
|
package/dist/index.d.cts
CHANGED
|
@@ -12,6 +12,7 @@ declare const RULE_NAMES: {
|
|
|
12
12
|
readonly ABSOLUTE_RELATIVE: "@conarti/feature-sliced/absolute-relative";
|
|
13
13
|
readonly PUBLIC_API: "@conarti/feature-sliced/public-api";
|
|
14
14
|
readonly IMPORT_ORDER: "@conarti/feature-sliced/import-order";
|
|
15
|
+
readonly NO_CROSS_SEGMENT_REEXPORT: "@conarti/feature-sliced/no-cross-segment-reexport";
|
|
15
16
|
};
|
|
16
17
|
type Layers = ReadonlyArray<'shared' | 'entities' | 'features' | 'widgets' | 'pages' | 'processes' | 'app'>;
|
|
17
18
|
type Layer = Layers[number];
|
|
@@ -62,9 +63,9 @@ declare const VALIDATION_LEVEL: {
|
|
|
62
63
|
readonly SEGMENTS: "segments";
|
|
63
64
|
readonly SLICES: "slices";
|
|
64
65
|
};
|
|
65
|
-
type MessageIds$
|
|
66
|
+
type MessageIds$3 = typeof MESSAGE_ID[keyof typeof MESSAGE_ID];
|
|
66
67
|
type ValidationLevel = typeof VALIDATION_LEVEL[keyof typeof VALIDATION_LEVEL];
|
|
67
|
-
type Options$
|
|
68
|
+
type Options$3 = [
|
|
68
69
|
{
|
|
69
70
|
level: ValidationLevel;
|
|
70
71
|
ignoreImports: string[];
|
|
@@ -72,32 +73,48 @@ type Options$2 = [
|
|
|
72
73
|
}
|
|
73
74
|
];
|
|
74
75
|
|
|
76
|
+
type Severity = 'error' | 'warn';
|
|
75
77
|
interface AbsoluteRelativeOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Severity level for this rule
|
|
80
|
+
* @default uses global severity or 'error'
|
|
81
|
+
*/
|
|
82
|
+
severity?: Severity;
|
|
76
83
|
/**
|
|
77
84
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
78
85
|
*/
|
|
79
|
-
ignoreImports
|
|
86
|
+
ignoreImports?: string[];
|
|
80
87
|
/**
|
|
81
88
|
* Disable the rule in certain files
|
|
82
89
|
*/
|
|
83
|
-
ignoreFiles
|
|
90
|
+
ignoreFiles?: string[];
|
|
84
91
|
}
|
|
85
92
|
interface LayersSlicesOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Severity level for this rule
|
|
95
|
+
* @default uses global severity or 'error'
|
|
96
|
+
*/
|
|
97
|
+
severity?: Severity;
|
|
86
98
|
/**
|
|
87
99
|
* Ignore cross-imports of types
|
|
88
100
|
* @default true
|
|
89
101
|
*/
|
|
90
|
-
allowTypeImports
|
|
102
|
+
allowTypeImports?: boolean;
|
|
91
103
|
/**
|
|
92
104
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
93
105
|
*/
|
|
94
|
-
ignoreImports
|
|
106
|
+
ignoreImports?: string[];
|
|
95
107
|
/**
|
|
96
108
|
* Disable the rule in certain files
|
|
97
109
|
*/
|
|
98
|
-
ignoreFiles
|
|
110
|
+
ignoreFiles?: string[];
|
|
99
111
|
}
|
|
100
112
|
interface PublicApiOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Severity level for this rule
|
|
115
|
+
* @default uses global severity or 'error'
|
|
116
|
+
*/
|
|
117
|
+
severity?: Severity;
|
|
101
118
|
/**
|
|
102
119
|
* Adjusts the depth.
|
|
103
120
|
* 'slices' will check for presence 'index' file at the slice level only,
|
|
@@ -105,17 +122,38 @@ interface PublicApiOptions {
|
|
|
105
122
|
* Default is 'slices', but 'segments' is recommended
|
|
106
123
|
* @default 'slices'
|
|
107
124
|
*/
|
|
108
|
-
level
|
|
125
|
+
level?: ValidationLevel;
|
|
126
|
+
/**
|
|
127
|
+
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
128
|
+
*/
|
|
129
|
+
ignoreImports?: string[];
|
|
130
|
+
/**
|
|
131
|
+
* Disable the rule in certain files
|
|
132
|
+
*/
|
|
133
|
+
ignoreFiles?: string[];
|
|
134
|
+
}
|
|
135
|
+
interface NoCrossSegmentReexportOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Severity level for this rule
|
|
138
|
+
* @default uses global severity or 'error'
|
|
139
|
+
*/
|
|
140
|
+
severity?: Severity;
|
|
109
141
|
/**
|
|
110
142
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
111
143
|
*/
|
|
112
|
-
ignoreImports
|
|
144
|
+
ignoreImports?: string[];
|
|
113
145
|
/**
|
|
114
146
|
* Disable the rule in certain files
|
|
115
147
|
*/
|
|
116
|
-
ignoreFiles
|
|
148
|
+
ignoreFiles?: string[];
|
|
117
149
|
}
|
|
118
150
|
interface ESLintPluginFeatureSlicedOptions {
|
|
151
|
+
/**
|
|
152
|
+
* Global severity level for all rules.
|
|
153
|
+
* Can be overridden per-rule.
|
|
154
|
+
* @default 'error'
|
|
155
|
+
*/
|
|
156
|
+
severity?: Severity;
|
|
119
157
|
/**
|
|
120
158
|
* Custom layers configuration.
|
|
121
159
|
* Supports mixed syntax: strings for layers with slices, objects for customization.
|
|
@@ -130,13 +168,26 @@ interface ESLintPluginFeatureSlicedOptions {
|
|
|
130
168
|
* ]
|
|
131
169
|
*/
|
|
132
170
|
layers?: LayersConfig;
|
|
133
|
-
absoluteRelative?: false | AbsoluteRelativeOptions
|
|
134
|
-
layersSlices?: false | LayersSlicesOptions
|
|
135
|
-
publicApi?: false | PublicApiOptions
|
|
171
|
+
absoluteRelative?: false | Partial<AbsoluteRelativeOptions>;
|
|
172
|
+
layersSlices?: false | Partial<LayersSlicesOptions>;
|
|
173
|
+
publicApi?: false | Partial<PublicApiOptions>;
|
|
174
|
+
noCrossSegmentReexport?: false | Partial<NoCrossSegmentReexportOptions>;
|
|
136
175
|
sortImports?: false | ImportOrderConfigName;
|
|
137
176
|
}
|
|
138
177
|
declare function createPlugin(options?: ESLintPluginFeatureSlicedOptions): TypedFlatConfigItem;
|
|
139
178
|
|
|
179
|
+
declare const ERROR_MESSAGE_ID$2: {
|
|
180
|
+
readonly NO_CROSS_SEGMENT_REEXPORT: "no-cross-segment-reexport";
|
|
181
|
+
readonly MOVE_TO_SLICE_PUBLIC_API_SUGGESTION: "move-to-slice-public-api-suggestion";
|
|
182
|
+
};
|
|
183
|
+
type MessageIds$2 = typeof ERROR_MESSAGE_ID$2[keyof typeof ERROR_MESSAGE_ID$2];
|
|
184
|
+
type Options$2 = [
|
|
185
|
+
{
|
|
186
|
+
ignoreImports: string[];
|
|
187
|
+
ignoreFiles: string[];
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
|
|
140
191
|
declare const ERROR_MESSAGE_ID$1: {
|
|
141
192
|
readonly CAN_NOT_IMPORT: "can-not-import";
|
|
142
193
|
readonly INVALID_CROSS_IMPORT: "invalid-cross-import";
|
|
@@ -171,10 +222,11 @@ declare const plugin: {
|
|
|
171
222
|
'absolute-relative': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds, Options, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
172
223
|
'import-order': _typescript_eslint_utils_ts_eslint.RuleModule<"error" | "order" | "noLineWithinGroup" | "noLineBetweenGroups" | "oneLineBetweenGroups" | "oneLineBetweenTheMultiLineImport" | "oneLineBetweenThisMultiLineImport" | "noLineBetweenSingleLineImport", [(eslint_plugin_import_x_rules_order.Options | undefined)?], eslint_plugin_import_x_utils.ImportXPluginDocs, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
173
224
|
'layers-slices': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$1, Options$1, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
174
|
-
'
|
|
225
|
+
'no-cross-segment-reexport': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$2, Options$2, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
226
|
+
'public-api': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$3, Options$3, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
175
227
|
};
|
|
176
228
|
};
|
|
177
229
|
|
|
178
230
|
// @ts-ignore
|
|
179
231
|
export = createPlugin;
|
|
180
|
-
export { type ImportOrderConfigName, type Layer, PLUGIN_NAME, RULE_NAMES, type Segment, type TypedFlatConfigItem, createPlugin, layers, plugin, segments };
|
|
232
|
+
export { type ImportOrderConfigName, type Layer, PLUGIN_NAME, RULE_NAMES, type Segment, type Severity, type TypedFlatConfigItem, createPlugin, layers, plugin, segments };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ declare const RULE_NAMES: {
|
|
|
12
12
|
readonly ABSOLUTE_RELATIVE: "@conarti/feature-sliced/absolute-relative";
|
|
13
13
|
readonly PUBLIC_API: "@conarti/feature-sliced/public-api";
|
|
14
14
|
readonly IMPORT_ORDER: "@conarti/feature-sliced/import-order";
|
|
15
|
+
readonly NO_CROSS_SEGMENT_REEXPORT: "@conarti/feature-sliced/no-cross-segment-reexport";
|
|
15
16
|
};
|
|
16
17
|
type Layers = ReadonlyArray<'shared' | 'entities' | 'features' | 'widgets' | 'pages' | 'processes' | 'app'>;
|
|
17
18
|
type Layer = Layers[number];
|
|
@@ -62,9 +63,9 @@ declare const VALIDATION_LEVEL: {
|
|
|
62
63
|
readonly SEGMENTS: "segments";
|
|
63
64
|
readonly SLICES: "slices";
|
|
64
65
|
};
|
|
65
|
-
type MessageIds$
|
|
66
|
+
type MessageIds$3 = typeof MESSAGE_ID[keyof typeof MESSAGE_ID];
|
|
66
67
|
type ValidationLevel = typeof VALIDATION_LEVEL[keyof typeof VALIDATION_LEVEL];
|
|
67
|
-
type Options$
|
|
68
|
+
type Options$3 = [
|
|
68
69
|
{
|
|
69
70
|
level: ValidationLevel;
|
|
70
71
|
ignoreImports: string[];
|
|
@@ -72,32 +73,48 @@ type Options$2 = [
|
|
|
72
73
|
}
|
|
73
74
|
];
|
|
74
75
|
|
|
76
|
+
type Severity = 'error' | 'warn';
|
|
75
77
|
interface AbsoluteRelativeOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Severity level for this rule
|
|
80
|
+
* @default uses global severity or 'error'
|
|
81
|
+
*/
|
|
82
|
+
severity?: Severity;
|
|
76
83
|
/**
|
|
77
84
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
78
85
|
*/
|
|
79
|
-
ignoreImports
|
|
86
|
+
ignoreImports?: string[];
|
|
80
87
|
/**
|
|
81
88
|
* Disable the rule in certain files
|
|
82
89
|
*/
|
|
83
|
-
ignoreFiles
|
|
90
|
+
ignoreFiles?: string[];
|
|
84
91
|
}
|
|
85
92
|
interface LayersSlicesOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Severity level for this rule
|
|
95
|
+
* @default uses global severity or 'error'
|
|
96
|
+
*/
|
|
97
|
+
severity?: Severity;
|
|
86
98
|
/**
|
|
87
99
|
* Ignore cross-imports of types
|
|
88
100
|
* @default true
|
|
89
101
|
*/
|
|
90
|
-
allowTypeImports
|
|
102
|
+
allowTypeImports?: boolean;
|
|
91
103
|
/**
|
|
92
104
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
93
105
|
*/
|
|
94
|
-
ignoreImports
|
|
106
|
+
ignoreImports?: string[];
|
|
95
107
|
/**
|
|
96
108
|
* Disable the rule in certain files
|
|
97
109
|
*/
|
|
98
|
-
ignoreFiles
|
|
110
|
+
ignoreFiles?: string[];
|
|
99
111
|
}
|
|
100
112
|
interface PublicApiOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Severity level for this rule
|
|
115
|
+
* @default uses global severity or 'error'
|
|
116
|
+
*/
|
|
117
|
+
severity?: Severity;
|
|
101
118
|
/**
|
|
102
119
|
* Adjusts the depth.
|
|
103
120
|
* 'slices' will check for presence 'index' file at the slice level only,
|
|
@@ -105,17 +122,38 @@ interface PublicApiOptions {
|
|
|
105
122
|
* Default is 'slices', but 'segments' is recommended
|
|
106
123
|
* @default 'slices'
|
|
107
124
|
*/
|
|
108
|
-
level
|
|
125
|
+
level?: ValidationLevel;
|
|
126
|
+
/**
|
|
127
|
+
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
128
|
+
*/
|
|
129
|
+
ignoreImports?: string[];
|
|
130
|
+
/**
|
|
131
|
+
* Disable the rule in certain files
|
|
132
|
+
*/
|
|
133
|
+
ignoreFiles?: string[];
|
|
134
|
+
}
|
|
135
|
+
interface NoCrossSegmentReexportOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Severity level for this rule
|
|
138
|
+
* @default uses global severity or 'error'
|
|
139
|
+
*/
|
|
140
|
+
severity?: Severity;
|
|
109
141
|
/**
|
|
110
142
|
* Ignore certain import paths (import foo from '<path-to-ignore>')
|
|
111
143
|
*/
|
|
112
|
-
ignoreImports
|
|
144
|
+
ignoreImports?: string[];
|
|
113
145
|
/**
|
|
114
146
|
* Disable the rule in certain files
|
|
115
147
|
*/
|
|
116
|
-
ignoreFiles
|
|
148
|
+
ignoreFiles?: string[];
|
|
117
149
|
}
|
|
118
150
|
interface ESLintPluginFeatureSlicedOptions {
|
|
151
|
+
/**
|
|
152
|
+
* Global severity level for all rules.
|
|
153
|
+
* Can be overridden per-rule.
|
|
154
|
+
* @default 'error'
|
|
155
|
+
*/
|
|
156
|
+
severity?: Severity;
|
|
119
157
|
/**
|
|
120
158
|
* Custom layers configuration.
|
|
121
159
|
* Supports mixed syntax: strings for layers with slices, objects for customization.
|
|
@@ -130,13 +168,26 @@ interface ESLintPluginFeatureSlicedOptions {
|
|
|
130
168
|
* ]
|
|
131
169
|
*/
|
|
132
170
|
layers?: LayersConfig;
|
|
133
|
-
absoluteRelative?: false | AbsoluteRelativeOptions
|
|
134
|
-
layersSlices?: false | LayersSlicesOptions
|
|
135
|
-
publicApi?: false | PublicApiOptions
|
|
171
|
+
absoluteRelative?: false | Partial<AbsoluteRelativeOptions>;
|
|
172
|
+
layersSlices?: false | Partial<LayersSlicesOptions>;
|
|
173
|
+
publicApi?: false | Partial<PublicApiOptions>;
|
|
174
|
+
noCrossSegmentReexport?: false | Partial<NoCrossSegmentReexportOptions>;
|
|
136
175
|
sortImports?: false | ImportOrderConfigName;
|
|
137
176
|
}
|
|
138
177
|
declare function createPlugin(options?: ESLintPluginFeatureSlicedOptions): TypedFlatConfigItem;
|
|
139
178
|
|
|
179
|
+
declare const ERROR_MESSAGE_ID$2: {
|
|
180
|
+
readonly NO_CROSS_SEGMENT_REEXPORT: "no-cross-segment-reexport";
|
|
181
|
+
readonly MOVE_TO_SLICE_PUBLIC_API_SUGGESTION: "move-to-slice-public-api-suggestion";
|
|
182
|
+
};
|
|
183
|
+
type MessageIds$2 = typeof ERROR_MESSAGE_ID$2[keyof typeof ERROR_MESSAGE_ID$2];
|
|
184
|
+
type Options$2 = [
|
|
185
|
+
{
|
|
186
|
+
ignoreImports: string[];
|
|
187
|
+
ignoreFiles: string[];
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
|
|
140
191
|
declare const ERROR_MESSAGE_ID$1: {
|
|
141
192
|
readonly CAN_NOT_IMPORT: "can-not-import";
|
|
142
193
|
readonly INVALID_CROSS_IMPORT: "invalid-cross-import";
|
|
@@ -171,8 +222,9 @@ declare const plugin: {
|
|
|
171
222
|
'absolute-relative': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds, Options, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
172
223
|
'import-order': _typescript_eslint_utils_ts_eslint.RuleModule<"error" | "order" | "noLineWithinGroup" | "noLineBetweenGroups" | "oneLineBetweenGroups" | "oneLineBetweenTheMultiLineImport" | "oneLineBetweenThisMultiLineImport" | "noLineBetweenSingleLineImport", [(eslint_plugin_import_x_rules_order.Options | undefined)?], eslint_plugin_import_x_utils.ImportXPluginDocs, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
173
224
|
'layers-slices': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$1, Options$1, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
174
|
-
'
|
|
225
|
+
'no-cross-segment-reexport': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$2, Options$2, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
226
|
+
'public-api': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$3, Options$3, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
175
227
|
};
|
|
176
228
|
};
|
|
177
229
|
|
|
178
|
-
export { type ImportOrderConfigName, type Layer, PLUGIN_NAME, RULE_NAMES, type Segment, type TypedFlatConfigItem, createPlugin, createPlugin as default, layers, plugin, segments };
|
|
230
|
+
export { type ImportOrderConfigName, type Layer, PLUGIN_NAME, RULE_NAMES, type Segment, type Severity, type TypedFlatConfigItem, createPlugin, createPlugin as default, layers, plugin, segments };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@ var RULE_NAMES = {
|
|
|
4
4
|
LAYERS_SLICES: `${PLUGIN_NAME}/layers-slices`,
|
|
5
5
|
ABSOLUTE_RELATIVE: `${PLUGIN_NAME}/absolute-relative`,
|
|
6
6
|
PUBLIC_API: `${PLUGIN_NAME}/public-api`,
|
|
7
|
-
IMPORT_ORDER: `${PLUGIN_NAME}/import-order
|
|
7
|
+
IMPORT_ORDER: `${PLUGIN_NAME}/import-order`,
|
|
8
|
+
NO_CROSS_SEGMENT_REEXPORT: `${PLUGIN_NAME}/no-cross-segment-reexport`
|
|
8
9
|
};
|
|
9
10
|
var layers = [
|
|
10
11
|
"shared",
|
|
@@ -79,7 +80,7 @@ function canLayerContainSlices(layer, config) {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// package.json
|
|
82
|
-
var version = "2.0.0-rc.
|
|
83
|
+
var version = "2.0.0-rc.7";
|
|
83
84
|
|
|
84
85
|
// src/rules/index.ts
|
|
85
86
|
import pluginImport from "eslint-plugin-import-x";
|
|
@@ -840,6 +841,228 @@ var layers_slices_default = createEslintRule({
|
|
|
840
841
|
}
|
|
841
842
|
});
|
|
842
843
|
|
|
844
|
+
// src/rules/no-cross-segment-reexport/config.ts
|
|
845
|
+
var ERROR_MESSAGE_ID3 = {
|
|
846
|
+
NO_CROSS_SEGMENT_REEXPORT: "no-cross-segment-reexport",
|
|
847
|
+
MOVE_TO_SLICE_PUBLIC_API_SUGGESTION: "move-to-slice-public-api-suggestion"
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/rules/no-cross-segment-reexport/model/errors.ts
|
|
851
|
+
function buildSlicePublicApiPath(sourcePath, targetSegment) {
|
|
852
|
+
const parts = sourcePath.split("/");
|
|
853
|
+
const segmentIndex = parts.findIndex((part) => part === targetSegment);
|
|
854
|
+
if (segmentIndex !== -1) {
|
|
855
|
+
return parts.slice(0, segmentIndex).join("/");
|
|
856
|
+
}
|
|
857
|
+
return parts.slice(0, -1).join("/") || "..";
|
|
858
|
+
}
|
|
859
|
+
function reportCrossSegmentReexport(context, node, currentSegment, targetSegment) {
|
|
860
|
+
const sourcePath = node.source.value;
|
|
861
|
+
const suggestedPath = buildSlicePublicApiPath(sourcePath, targetSegment);
|
|
862
|
+
context.report({
|
|
863
|
+
node: node.source,
|
|
864
|
+
messageId: ERROR_MESSAGE_ID3.NO_CROSS_SEGMENT_REEXPORT,
|
|
865
|
+
data: {
|
|
866
|
+
currentSegment,
|
|
867
|
+
targetSegment
|
|
868
|
+
},
|
|
869
|
+
suggest: [
|
|
870
|
+
{
|
|
871
|
+
messageId: ERROR_MESSAGE_ID3.MOVE_TO_SLICE_PUBLIC_API_SUGGESTION,
|
|
872
|
+
data: {
|
|
873
|
+
suggestedPath
|
|
874
|
+
},
|
|
875
|
+
fix: (fixer) => fixer.replaceTextRange(
|
|
876
|
+
getSourceRangeWithoutQuotes(node.source.range),
|
|
877
|
+
suggestedPath
|
|
878
|
+
)
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/rules/no-cross-segment-reexport/model/is-cross-segment-reexport.ts
|
|
885
|
+
var NOT_CROSS_SEGMENT = {
|
|
886
|
+
isCrossSegmentReexport: false,
|
|
887
|
+
currentSegment: null,
|
|
888
|
+
targetSegment: null
|
|
889
|
+
};
|
|
890
|
+
var FILE_EXT_REGEXP = /\..+$/;
|
|
891
|
+
var KNOWN_SEGMENTS = segments.map((s) => s.toLowerCase());
|
|
892
|
+
function splitPathParts(path3) {
|
|
893
|
+
return path3.split("/").filter(Boolean);
|
|
894
|
+
}
|
|
895
|
+
function normalizeToDirParts(parts) {
|
|
896
|
+
const INDEX_FILE_REGEXP = /^index\..+$/;
|
|
897
|
+
return parts.reduce((acc, part) => {
|
|
898
|
+
if (INDEX_FILE_REGEXP.test(part))
|
|
899
|
+
return acc;
|
|
900
|
+
if (FILE_EXT_REGEXP.test(part)) {
|
|
901
|
+
acc.push(part.replace(FILE_EXT_REGEXP, ""));
|
|
902
|
+
return acc;
|
|
903
|
+
}
|
|
904
|
+
acc.push(part);
|
|
905
|
+
return acc;
|
|
906
|
+
}, []);
|
|
907
|
+
}
|
|
908
|
+
function findLayerIndex(parts, layersWithSlices2) {
|
|
909
|
+
return parts.findIndex(
|
|
910
|
+
(part) => layersWithSlices2.includes(part.toLowerCase())
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
function extractSegmentAndSlice(pathParts) {
|
|
914
|
+
if (pathParts.length < 2)
|
|
915
|
+
return null;
|
|
916
|
+
const knownSegmentIndex = pathParts.findIndex(
|
|
917
|
+
(part) => KNOWN_SEGMENTS.includes(part.toLowerCase())
|
|
918
|
+
);
|
|
919
|
+
let segmentIndex;
|
|
920
|
+
if (knownSegmentIndex > 0) {
|
|
921
|
+
segmentIndex = knownSegmentIndex;
|
|
922
|
+
} else if (knownSegmentIndex === 0) {
|
|
923
|
+
return null;
|
|
924
|
+
} else {
|
|
925
|
+
segmentIndex = pathParts.length - 1;
|
|
926
|
+
if (segmentIndex < 1)
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const segment = pathParts[segmentIndex];
|
|
930
|
+
const sliceParts = pathParts.slice(0, segmentIndex);
|
|
931
|
+
if (sliceParts.length === 0)
|
|
932
|
+
return null;
|
|
933
|
+
return { segment, sliceParts };
|
|
934
|
+
}
|
|
935
|
+
function findTargetSegmentInSameSlice(targetPathParts, currentSliceParts, currentSegment) {
|
|
936
|
+
if (targetPathParts.length === 0)
|
|
937
|
+
return null;
|
|
938
|
+
if (currentSliceParts.length > targetPathParts.length)
|
|
939
|
+
return null;
|
|
940
|
+
const targetHasSameSlice = currentSliceParts.every(
|
|
941
|
+
(part, i) => part.toLowerCase() === targetPathParts[i]?.toLowerCase()
|
|
942
|
+
);
|
|
943
|
+
if (!targetHasSameSlice)
|
|
944
|
+
return null;
|
|
945
|
+
const targetSegmentCandidate = targetPathParts[currentSliceParts.length];
|
|
946
|
+
if (!targetSegmentCandidate)
|
|
947
|
+
return null;
|
|
948
|
+
if (targetSegmentCandidate.toLowerCase() !== currentSegment.toLowerCase()) {
|
|
949
|
+
return targetSegmentCandidate;
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
function isCrossSegmentReexport(normalizedCurrentFilePath, absoluteTargetPath, config) {
|
|
954
|
+
const layersConfig = config ?? normalizeLayersConfig();
|
|
955
|
+
const layersWithSlices2 = getLayersWithSlices(layersConfig).map((l) => l.toLowerCase());
|
|
956
|
+
const currentParts = splitPathParts(normalizedCurrentFilePath);
|
|
957
|
+
const targetParts = splitPathParts(absoluteTargetPath);
|
|
958
|
+
const currentLayerIndex = findLayerIndex(currentParts, layersWithSlices2);
|
|
959
|
+
if (currentLayerIndex === -1)
|
|
960
|
+
return NOT_CROSS_SEGMENT;
|
|
961
|
+
const currentAfterLayer = currentParts.slice(currentLayerIndex + 1);
|
|
962
|
+
const currentPathParts = normalizeToDirParts(currentAfterLayer);
|
|
963
|
+
const currentInfo = extractSegmentAndSlice(currentPathParts);
|
|
964
|
+
if (!currentInfo)
|
|
965
|
+
return NOT_CROSS_SEGMENT;
|
|
966
|
+
const targetLayerIndex = findLayerIndex(targetParts, layersWithSlices2);
|
|
967
|
+
if (targetLayerIndex === -1)
|
|
968
|
+
return NOT_CROSS_SEGMENT;
|
|
969
|
+
if (currentParts[currentLayerIndex].toLowerCase() !== targetParts[targetLayerIndex].toLowerCase()) {
|
|
970
|
+
return NOT_CROSS_SEGMENT;
|
|
971
|
+
}
|
|
972
|
+
const targetAfterLayer = targetParts.slice(targetLayerIndex + 1);
|
|
973
|
+
const targetPathParts = normalizeToDirParts(targetAfterLayer);
|
|
974
|
+
const targetSegment = findTargetSegmentInSameSlice(
|
|
975
|
+
targetPathParts,
|
|
976
|
+
currentInfo.sliceParts,
|
|
977
|
+
currentInfo.segment
|
|
978
|
+
);
|
|
979
|
+
if (!targetSegment)
|
|
980
|
+
return NOT_CROSS_SEGMENT;
|
|
981
|
+
return {
|
|
982
|
+
isCrossSegmentReexport: true,
|
|
983
|
+
currentSegment: currentInfo.segment,
|
|
984
|
+
targetSegment
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/rules/no-cross-segment-reexport/model/validate-and-report.ts
|
|
989
|
+
function validateAndReport3(node, context, optionsWithDefault, config) {
|
|
990
|
+
if (!hasPath(node))
|
|
991
|
+
return;
|
|
992
|
+
const isIgnored2 = isIgnoredTarget(node, optionsWithDefault) || isIgnoredCurrentFile(context, optionsWithDefault);
|
|
993
|
+
if (isIgnored2)
|
|
994
|
+
return;
|
|
995
|
+
const {
|
|
996
|
+
normalizedCurrentFilePath,
|
|
997
|
+
absoluteTargetPath
|
|
998
|
+
} = extractPaths(node, context);
|
|
999
|
+
const result = isCrossSegmentReexport(
|
|
1000
|
+
normalizedCurrentFilePath,
|
|
1001
|
+
absoluteTargetPath,
|
|
1002
|
+
config
|
|
1003
|
+
);
|
|
1004
|
+
if (!result.isCrossSegmentReexport)
|
|
1005
|
+
return;
|
|
1006
|
+
reportCrossSegmentReexport(
|
|
1007
|
+
context,
|
|
1008
|
+
node,
|
|
1009
|
+
result.currentSegment,
|
|
1010
|
+
result.targetSegment
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/rules/no-cross-segment-reexport/index.ts
|
|
1015
|
+
var no_cross_segment_reexport_default = createEslintRule({
|
|
1016
|
+
name: "no-cross-segment-reexport",
|
|
1017
|
+
meta: {
|
|
1018
|
+
type: "problem",
|
|
1019
|
+
docs: {
|
|
1020
|
+
description: "Checks for cross-segment re-exports within the same slice"
|
|
1021
|
+
},
|
|
1022
|
+
hasSuggestions: true,
|
|
1023
|
+
messages: {
|
|
1024
|
+
[ERROR_MESSAGE_ID3.NO_CROSS_SEGMENT_REEXPORT]: 'Segment "{{ currentSegment }}" should not re-export from sibling segment "{{ targetSegment }}". Move the re-export to the slice public API.',
|
|
1025
|
+
[ERROR_MESSAGE_ID3.MOVE_TO_SLICE_PUBLIC_API_SUGGESTION]: 'Replace import path with slice public API ("{{ suggestedPath }}")'
|
|
1026
|
+
},
|
|
1027
|
+
schema: [
|
|
1028
|
+
{
|
|
1029
|
+
type: "object",
|
|
1030
|
+
properties: {
|
|
1031
|
+
ignoreImports: {
|
|
1032
|
+
type: "array",
|
|
1033
|
+
items: {
|
|
1034
|
+
type: "string"
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
ignoreFiles: {
|
|
1038
|
+
type: "array",
|
|
1039
|
+
items: {
|
|
1040
|
+
type: "string"
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
]
|
|
1046
|
+
},
|
|
1047
|
+
defaultOptions: [
|
|
1048
|
+
{
|
|
1049
|
+
ignoreImports: [],
|
|
1050
|
+
ignoreFiles: []
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
create(context, optionsWithDefault) {
|
|
1054
|
+
const layersConfig = extractLayersConfig(context);
|
|
1055
|
+
return {
|
|
1056
|
+
ExportAllDeclaration(node) {
|
|
1057
|
+
validateAndReport3(node, context, optionsWithDefault, layersConfig);
|
|
1058
|
+
},
|
|
1059
|
+
ExportNamedDeclaration(node) {
|
|
1060
|
+
validateAndReport3(node, context, optionsWithDefault, layersConfig);
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
843
1066
|
// src/rules/public-api/config.ts
|
|
844
1067
|
var MESSAGE_ID = {
|
|
845
1068
|
SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api",
|
|
@@ -963,7 +1186,7 @@ function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig)
|
|
|
963
1186
|
}
|
|
964
1187
|
|
|
965
1188
|
// src/rules/public-api/model/validate-and-report.ts
|
|
966
|
-
function
|
|
1189
|
+
function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
967
1190
|
if (!hasPath(node)) {
|
|
968
1191
|
return;
|
|
969
1192
|
}
|
|
@@ -1053,16 +1276,16 @@ var public_api_default = createEslintRule({
|
|
|
1053
1276
|
const layersConfig = extractLayersConfig(context);
|
|
1054
1277
|
return {
|
|
1055
1278
|
ImportDeclaration(node) {
|
|
1056
|
-
|
|
1279
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1057
1280
|
},
|
|
1058
1281
|
ImportExpression(node) {
|
|
1059
|
-
|
|
1282
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1060
1283
|
},
|
|
1061
1284
|
ExportAllDeclaration(node) {
|
|
1062
|
-
|
|
1285
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1063
1286
|
},
|
|
1064
1287
|
ExportNamedDeclaration(node) {
|
|
1065
|
-
|
|
1288
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1066
1289
|
},
|
|
1067
1290
|
Program(node) {
|
|
1068
1291
|
validateAndReportProgram(node, context, optionsWithDefault);
|
|
@@ -1076,6 +1299,7 @@ var rules = {
|
|
|
1076
1299
|
"absolute-relative": absolute_relative_default,
|
|
1077
1300
|
"import-order": pluginImport.rules.order,
|
|
1078
1301
|
"layers-slices": layers_slices_default,
|
|
1302
|
+
"no-cross-segment-reexport": no_cross_segment_reexport_default,
|
|
1079
1303
|
"public-api": public_api_default
|
|
1080
1304
|
};
|
|
1081
1305
|
var rules_default = rules;
|
|
@@ -1154,14 +1378,16 @@ var importOrderRuleConfigs = createImportOrderRuleConfigs();
|
|
|
1154
1378
|
// src/create-plugin.ts
|
|
1155
1379
|
function createPlugin(options = {}) {
|
|
1156
1380
|
const {
|
|
1381
|
+
severity = "error",
|
|
1157
1382
|
layers: layers2,
|
|
1158
1383
|
sortImports = "recommended",
|
|
1159
1384
|
absoluteRelative,
|
|
1160
1385
|
layersSlices,
|
|
1161
|
-
publicApi
|
|
1386
|
+
publicApi,
|
|
1387
|
+
noCrossSegmentReexport
|
|
1162
1388
|
} = options;
|
|
1163
1389
|
const normalizedLayers = normalizeLayersConfig(layers2);
|
|
1164
|
-
const rules2 = defineRules({ absoluteRelative, layersSlices, publicApi, sortImports }, normalizedLayers);
|
|
1390
|
+
const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, noCrossSegmentReexport, sortImports }, normalizedLayers);
|
|
1165
1391
|
return {
|
|
1166
1392
|
name: PLUGIN_NAME,
|
|
1167
1393
|
plugins: {
|
|
@@ -1177,16 +1403,25 @@ function createPlugin(options = {}) {
|
|
|
1177
1403
|
}
|
|
1178
1404
|
function defineRules(options, layersConfig) {
|
|
1179
1405
|
const {
|
|
1406
|
+
severity: globalSeverity = "error",
|
|
1180
1407
|
absoluteRelative = {},
|
|
1181
1408
|
layersSlices = {},
|
|
1182
1409
|
publicApi = {},
|
|
1410
|
+
noCrossSegmentReexport = {},
|
|
1183
1411
|
sortImports = "recommended"
|
|
1184
1412
|
} = options;
|
|
1185
|
-
const createRuleEntry = (ruleOptions) =>
|
|
1413
|
+
const createRuleEntry = (ruleOptions) => {
|
|
1414
|
+
if (ruleOptions === false) {
|
|
1415
|
+
return "off";
|
|
1416
|
+
}
|
|
1417
|
+
const { severity = globalSeverity, ...restOptions } = ruleOptions;
|
|
1418
|
+
return [severity, restOptions];
|
|
1419
|
+
};
|
|
1186
1420
|
const rules2 = {
|
|
1187
1421
|
[RULE_NAMES.LAYERS_SLICES]: createRuleEntry(layersSlices),
|
|
1188
1422
|
[RULE_NAMES.ABSOLUTE_RELATIVE]: createRuleEntry(absoluteRelative),
|
|
1189
|
-
[RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi)
|
|
1423
|
+
[RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi),
|
|
1424
|
+
[RULE_NAMES.NO_CROSS_SEGMENT_REEXPORT]: createRuleEntry(noCrossSegmentReexport)
|
|
1190
1425
|
};
|
|
1191
1426
|
if (sortImports) {
|
|
1192
1427
|
const importOrderConfigs = createImportOrderRuleConfigs(layersConfig);
|
package/package.json
CHANGED