@conarti/eslint-plugin-feature-sliced 2.0.0-rc.6 → 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 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.5";
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 validateAndReport3(node, context, optionsWithDefault, layersConfig) {
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
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1279
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1057
1280
  },
1058
1281
  ImportExpression(node) {
1059
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1282
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1060
1283
  },
1061
1284
  ExportAllDeclaration(node) {
1062
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1285
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1063
1286
  },
1064
1287
  ExportNamedDeclaration(node) {
1065
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
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;
@@ -1159,10 +1383,11 @@ function createPlugin(options = {}) {
1159
1383
  sortImports = "recommended",
1160
1384
  absoluteRelative,
1161
1385
  layersSlices,
1162
- publicApi
1386
+ publicApi,
1387
+ noCrossSegmentReexport
1163
1388
  } = options;
1164
1389
  const normalizedLayers = normalizeLayersConfig(layers2);
1165
- const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, sortImports }, normalizedLayers);
1390
+ const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, noCrossSegmentReexport, sortImports }, normalizedLayers);
1166
1391
  return {
1167
1392
  name: PLUGIN_NAME,
1168
1393
  plugins: {
@@ -1182,6 +1407,7 @@ function defineRules(options, layersConfig) {
1182
1407
  absoluteRelative = {},
1183
1408
  layersSlices = {},
1184
1409
  publicApi = {},
1410
+ noCrossSegmentReexport = {},
1185
1411
  sortImports = "recommended"
1186
1412
  } = options;
1187
1413
  const createRuleEntry = (ruleOptions) => {
@@ -1194,7 +1420,8 @@ function defineRules(options, layersConfig) {
1194
1420
  const rules2 = {
1195
1421
  [RULE_NAMES.LAYERS_SLICES]: createRuleEntry(layersSlices),
1196
1422
  [RULE_NAMES.ABSOLUTE_RELATIVE]: createRuleEntry(absoluteRelative),
1197
- [RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi)
1423
+ [RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi),
1424
+ [RULE_NAMES.NO_CROSS_SEGMENT_REEXPORT]: createRuleEntry(noCrossSegmentReexport)
1198
1425
  };
1199
1426
  if (sortImports) {
1200
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$2 = typeof MESSAGE_ID[keyof typeof MESSAGE_ID];
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$2 = [
68
+ type Options$3 = [
68
69
  {
69
70
  level: ValidationLevel;
70
71
  ignoreImports: string[];
@@ -131,6 +132,21 @@ interface PublicApiOptions {
131
132
  */
132
133
  ignoreFiles?: string[];
133
134
  }
135
+ interface NoCrossSegmentReexportOptions {
136
+ /**
137
+ * Severity level for this rule
138
+ * @default uses global severity or 'error'
139
+ */
140
+ severity?: Severity;
141
+ /**
142
+ * Ignore certain import paths (import foo from '<path-to-ignore>')
143
+ */
144
+ ignoreImports?: string[];
145
+ /**
146
+ * Disable the rule in certain files
147
+ */
148
+ ignoreFiles?: string[];
149
+ }
134
150
  interface ESLintPluginFeatureSlicedOptions {
135
151
  /**
136
152
  * Global severity level for all rules.
@@ -155,10 +171,23 @@ interface ESLintPluginFeatureSlicedOptions {
155
171
  absoluteRelative?: false | Partial<AbsoluteRelativeOptions>;
156
172
  layersSlices?: false | Partial<LayersSlicesOptions>;
157
173
  publicApi?: false | Partial<PublicApiOptions>;
174
+ noCrossSegmentReexport?: false | Partial<NoCrossSegmentReexportOptions>;
158
175
  sortImports?: false | ImportOrderConfigName;
159
176
  }
160
177
  declare function createPlugin(options?: ESLintPluginFeatureSlicedOptions): TypedFlatConfigItem;
161
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
+
162
191
  declare const ERROR_MESSAGE_ID$1: {
163
192
  readonly CAN_NOT_IMPORT: "can-not-import";
164
193
  readonly INVALID_CROSS_IMPORT: "invalid-cross-import";
@@ -193,7 +222,8 @@ declare const plugin: {
193
222
  'absolute-relative': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds, Options, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
194
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>;
195
224
  'layers-slices': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$1, Options$1, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
196
- 'public-api': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$2, Options$2, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
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>;
197
227
  };
198
228
  };
199
229
 
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$2 = typeof MESSAGE_ID[keyof typeof MESSAGE_ID];
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$2 = [
68
+ type Options$3 = [
68
69
  {
69
70
  level: ValidationLevel;
70
71
  ignoreImports: string[];
@@ -131,6 +132,21 @@ interface PublicApiOptions {
131
132
  */
132
133
  ignoreFiles?: string[];
133
134
  }
135
+ interface NoCrossSegmentReexportOptions {
136
+ /**
137
+ * Severity level for this rule
138
+ * @default uses global severity or 'error'
139
+ */
140
+ severity?: Severity;
141
+ /**
142
+ * Ignore certain import paths (import foo from '<path-to-ignore>')
143
+ */
144
+ ignoreImports?: string[];
145
+ /**
146
+ * Disable the rule in certain files
147
+ */
148
+ ignoreFiles?: string[];
149
+ }
134
150
  interface ESLintPluginFeatureSlicedOptions {
135
151
  /**
136
152
  * Global severity level for all rules.
@@ -155,10 +171,23 @@ interface ESLintPluginFeatureSlicedOptions {
155
171
  absoluteRelative?: false | Partial<AbsoluteRelativeOptions>;
156
172
  layersSlices?: false | Partial<LayersSlicesOptions>;
157
173
  publicApi?: false | Partial<PublicApiOptions>;
174
+ noCrossSegmentReexport?: false | Partial<NoCrossSegmentReexportOptions>;
158
175
  sortImports?: false | ImportOrderConfigName;
159
176
  }
160
177
  declare function createPlugin(options?: ESLintPluginFeatureSlicedOptions): TypedFlatConfigItem;
161
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
+
162
191
  declare const ERROR_MESSAGE_ID$1: {
163
192
  readonly CAN_NOT_IMPORT: "can-not-import";
164
193
  readonly INVALID_CROSS_IMPORT: "invalid-cross-import";
@@ -193,7 +222,8 @@ declare const plugin: {
193
222
  'absolute-relative': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds, Options, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
194
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>;
195
224
  'layers-slices': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$1, Options$1, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
196
- 'public-api': _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds$2, Options$2, unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
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>;
197
227
  };
198
228
  };
199
229
 
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.5";
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 validateAndReport3(node, context, optionsWithDefault, layersConfig) {
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
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1279
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1057
1280
  },
1058
1281
  ImportExpression(node) {
1059
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1282
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1060
1283
  },
1061
1284
  ExportAllDeclaration(node) {
1062
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
1285
+ validateAndReport4(node, context, optionsWithDefault, layersConfig);
1063
1286
  },
1064
1287
  ExportNamedDeclaration(node) {
1065
- validateAndReport3(node, context, optionsWithDefault, layersConfig);
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;
@@ -1159,10 +1383,11 @@ function createPlugin(options = {}) {
1159
1383
  sortImports = "recommended",
1160
1384
  absoluteRelative,
1161
1385
  layersSlices,
1162
- publicApi
1386
+ publicApi,
1387
+ noCrossSegmentReexport
1163
1388
  } = options;
1164
1389
  const normalizedLayers = normalizeLayersConfig(layers2);
1165
- const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, sortImports }, normalizedLayers);
1390
+ const rules2 = defineRules({ severity, absoluteRelative, layersSlices, publicApi, noCrossSegmentReexport, sortImports }, normalizedLayers);
1166
1391
  return {
1167
1392
  name: PLUGIN_NAME,
1168
1393
  plugins: {
@@ -1182,6 +1407,7 @@ function defineRules(options, layersConfig) {
1182
1407
  absoluteRelative = {},
1183
1408
  layersSlices = {},
1184
1409
  publicApi = {},
1410
+ noCrossSegmentReexport = {},
1185
1411
  sortImports = "recommended"
1186
1412
  } = options;
1187
1413
  const createRuleEntry = (ruleOptions) => {
@@ -1194,7 +1420,8 @@ function defineRules(options, layersConfig) {
1194
1420
  const rules2 = {
1195
1421
  [RULE_NAMES.LAYERS_SLICES]: createRuleEntry(layersSlices),
1196
1422
  [RULE_NAMES.ABSOLUTE_RELATIVE]: createRuleEntry(absoluteRelative),
1197
- [RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi)
1423
+ [RULE_NAMES.PUBLIC_API]: createRuleEntry(publicApi),
1424
+ [RULE_NAMES.NO_CROSS_SEGMENT_REEXPORT]: createRuleEntry(noCrossSegmentReexport)
1198
1425
  };
1199
1426
  if (sortImports) {
1200
1427
  const importOrderConfigs = createImportOrderRuleConfigs(layersConfig);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@conarti/eslint-plugin-feature-sliced",
3
3
  "type": "module",
4
- "version": "2.0.0-rc.6",
4
+ "version": "2.0.0-rc.7",
5
5
  "description": "Feature-sliced design methodology plugin",
6
6
  "author": "Aleksandr Belous <abelous2009@gmail.com>",
7
7
  "license": "ISC",