@conarti/eslint-plugin-feature-sliced 2.0.0-rc.7 → 2.0.0-rc.9
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 +156 -43
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +153 -40
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,14 @@ var segments = [
|
|
|
38
38
|
"config",
|
|
39
39
|
"assets"
|
|
40
40
|
];
|
|
41
|
+
var DEFAULT_SEGMENTS = [
|
|
42
|
+
"ui",
|
|
43
|
+
"model",
|
|
44
|
+
"lib",
|
|
45
|
+
"api",
|
|
46
|
+
"config",
|
|
47
|
+
"assets"
|
|
48
|
+
];
|
|
41
49
|
var pathSeparator = "/";
|
|
42
50
|
|
|
43
51
|
// src/lib/feature-sliced/layers-config.ts
|
|
@@ -80,7 +88,7 @@ function canLayerContainSlices(layer, config) {
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
// package.json
|
|
83
|
-
var version = "2.0.0-rc.
|
|
91
|
+
var version = "2.0.0-rc.9";
|
|
84
92
|
|
|
85
93
|
// src/rules/index.ts
|
|
86
94
|
var _eslintpluginimportx = require('eslint-plugin-import-x'); var _eslintpluginimportx2 = _interopRequireDefault(_eslintpluginimportx);
|
|
@@ -132,7 +140,7 @@ function convertToAbsolute(base, target) {
|
|
|
132
140
|
|
|
133
141
|
// src/lib/rule/extract-current-file-path.ts
|
|
134
142
|
function extractCurrentFilePath(context) {
|
|
135
|
-
const currentFilePath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
|
|
143
|
+
const currentFilePath = _nullishCoalesce(_nullishCoalesce(context.physicalFilename, () => ( context.filename)), () => ( (context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename())));
|
|
136
144
|
return normalizePath(currentFilePath);
|
|
137
145
|
}
|
|
138
146
|
|
|
@@ -163,7 +171,7 @@ function isUndefined(target) {
|
|
|
163
171
|
|
|
164
172
|
// src/lib/rule/extract-cwd.ts
|
|
165
173
|
function extractCwd(context) {
|
|
166
|
-
const cwd = _optionalChain([context, 'access', _2 => _2.getCwd, 'optionalCall', _3 => _3()]);
|
|
174
|
+
const cwd = _nullishCoalesce(context.cwd, () => ( _optionalChain([context, 'access', _2 => _2.getCwd, 'optionalCall', _3 => _3()])));
|
|
167
175
|
if (isUndefined(cwd)) {
|
|
168
176
|
return void 0;
|
|
169
177
|
}
|
|
@@ -213,6 +221,46 @@ function extractRuleOptions(optionsWithDefault) {
|
|
|
213
221
|
return optionsWithDefault[0];
|
|
214
222
|
}
|
|
215
223
|
|
|
224
|
+
// src/lib/feature-sliced/segments-config.ts
|
|
225
|
+
function normalizeSegmentsConfig(config) {
|
|
226
|
+
if (!config) {
|
|
227
|
+
return [...DEFAULT_SEGMENTS];
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(config)) {
|
|
230
|
+
const customSegments = config.map((s) => s.toLowerCase());
|
|
231
|
+
const combined = [...DEFAULT_SEGMENTS, ...customSegments];
|
|
232
|
+
return [...new Set(combined)];
|
|
233
|
+
}
|
|
234
|
+
const replacedSegments = config.replace.map((s) => s.toLowerCase());
|
|
235
|
+
return [...new Set(replacedSegments)];
|
|
236
|
+
}
|
|
237
|
+
function isKnownSegment(segment, segments2) {
|
|
238
|
+
if (typeof segment !== "string" || segment === "") {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return segments2.includes(segment.toLowerCase());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/lib/rule/extract-segments-config.ts
|
|
245
|
+
function isValidSegmentsConfig(value) {
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.every((item) => typeof item === "string");
|
|
248
|
+
}
|
|
249
|
+
if (typeof value === "object" && value !== null && "replace" in value) {
|
|
250
|
+
const replaceValue = value.replace;
|
|
251
|
+
return Array.isArray(replaceValue) && replaceValue.every((item) => typeof item === "string");
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
function extractSegmentsConfig(context) {
|
|
256
|
+
const settings = context.settings;
|
|
257
|
+
const pluginSettings = _optionalChain([settings, 'optionalAccess', _6 => _6[PLUGIN_NAME]]);
|
|
258
|
+
if (_optionalChain([pluginSettings, 'optionalAccess', _7 => _7.segments]) && isValidSegmentsConfig(pluginSettings.segments)) {
|
|
259
|
+
return normalizeSegmentsConfig(pluginSettings.segments);
|
|
260
|
+
}
|
|
261
|
+
return normalizeSegmentsConfig();
|
|
262
|
+
}
|
|
263
|
+
|
|
216
264
|
// src/lib/rule/get-source-range-without-quotes.ts
|
|
217
265
|
function getSourceRangeWithoutQuotes([rangeStart, rangeEnd]) {
|
|
218
266
|
return [rangeStart + 1, rangeEnd - 1];
|
|
@@ -286,7 +334,7 @@ function extractCrossImportInfo(path3) {
|
|
|
286
334
|
}
|
|
287
335
|
const crossImportRegex = /(?<sourceSlice>[\w-]+)\/@x\/(?<targetSlice>[\w-]+)(?:\.\w+)?$/;
|
|
288
336
|
const match = normalizedPath.match(crossImportRegex);
|
|
289
|
-
if (!_optionalChain([match, 'optionalAccess',
|
|
337
|
+
if (!_optionalChain([match, 'optionalAccess', _8 => _8.groups])) {
|
|
290
338
|
return EMPTY_RESULT;
|
|
291
339
|
}
|
|
292
340
|
const { sourceSlice, targetSlice } = match.groups;
|
|
@@ -317,17 +365,18 @@ function extractLayer(targetPath, cwd, config) {
|
|
|
317
365
|
}
|
|
318
366
|
|
|
319
367
|
// src/lib/feature-sliced/extract-segment.ts
|
|
320
|
-
function createFsdPartsRegExp(layersWithSlices2) {
|
|
368
|
+
function createFsdPartsRegExp(layersWithSlices2, segmentsList) {
|
|
321
369
|
const layersUnion = layersWithSlices2.join("|");
|
|
322
|
-
const segmentsUnion =
|
|
370
|
+
const segmentsUnion = segmentsList.join("|");
|
|
323
371
|
return new RegExp(
|
|
324
372
|
`(?<=(?<layer>${layersUnion}))\\/(?<slice>([\\w-]*\\/)+?)(?<segment>(${segmentsUnion})(\\.\\w+)?)(\\/(?<segmentFiles>.*))?`
|
|
325
373
|
);
|
|
326
374
|
}
|
|
327
|
-
function extractSegment(targetPath,
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
const
|
|
375
|
+
function extractSegment(targetPath, layersConfig, segmentsConfig) {
|
|
376
|
+
const normalizedLayersConfig = _nullishCoalesce(layersConfig, () => ( normalizeLayersConfig()));
|
|
377
|
+
const segmentsList = _nullishCoalesce(segmentsConfig, () => ( [...DEFAULT_SEGMENTS]));
|
|
378
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
379
|
+
const fsdPartsRegExp = createFsdPartsRegExp(layersWithSlices2, segmentsList);
|
|
331
380
|
const fsdParts = targetPath.match(fsdPartsRegExp);
|
|
332
381
|
if (fsdParts === null) {
|
|
333
382
|
return [null, null];
|
|
@@ -337,14 +386,15 @@ function extractSegment(targetPath, config) {
|
|
|
337
386
|
segmentFiles = null
|
|
338
387
|
} = fsdParts.groups || {};
|
|
339
388
|
const fileExtensionRegExp = /\.[^/.]+$/;
|
|
340
|
-
const segmentWithoutFileExtension = _optionalChain([segment, 'optionalAccess',
|
|
389
|
+
const segmentWithoutFileExtension = _optionalChain([segment, 'optionalAccess', _9 => _9.replace, 'call', _10 => _10(fileExtensionRegExp, "")]) || null;
|
|
341
390
|
return [segmentWithoutFileExtension, segmentFiles];
|
|
342
391
|
}
|
|
343
392
|
|
|
344
393
|
// src/lib/feature-sliced/extract-slice.ts
|
|
345
|
-
function extractSlice(targetPath,
|
|
346
|
-
const
|
|
347
|
-
const
|
|
394
|
+
function extractSlice(targetPath, layersConfig, segmentsConfig) {
|
|
395
|
+
const normalizedLayersConfig = _nullishCoalesce(layersConfig, () => ( normalizeLayersConfig()));
|
|
396
|
+
const segmentsList = _nullishCoalesce(segmentsConfig, () => ( [...DEFAULT_SEGMENTS]));
|
|
397
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
348
398
|
const pathWithoutFile = targetPath.replace(/\/[\w-]+\.\w+$/, "");
|
|
349
399
|
const parts = pathWithoutFile.split("/").filter(Boolean);
|
|
350
400
|
const layerIndex = parts.findIndex(
|
|
@@ -355,7 +405,7 @@ function extractSlice(targetPath, config) {
|
|
|
355
405
|
}
|
|
356
406
|
const partsAfterLayer = parts.slice(layerIndex + 1);
|
|
357
407
|
const segmentIndex = partsAfterLayer.findIndex(
|
|
358
|
-
(part) =>
|
|
408
|
+
(part) => segmentsList.some((seg) => seg.toLowerCase() === part.toLowerCase())
|
|
359
409
|
);
|
|
360
410
|
if (segmentIndex > 0) {
|
|
361
411
|
return partsAfterLayer[segmentIndex - 1];
|
|
@@ -367,10 +417,11 @@ function extractSlice(targetPath, config) {
|
|
|
367
417
|
}
|
|
368
418
|
|
|
369
419
|
// src/lib/feature-sliced/extract-feature-sliced-parts.ts
|
|
370
|
-
function extractFeatureSlicedParts(targetPath, cwd,
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
const
|
|
420
|
+
function extractFeatureSlicedParts(targetPath, cwd, options) {
|
|
421
|
+
const { layersConfig, segmentsConfig } = _nullishCoalesce(options, () => ( {}));
|
|
422
|
+
const layer = extractLayer(targetPath, cwd, layersConfig);
|
|
423
|
+
const slice = extractSlice(targetPath, layersConfig, segmentsConfig);
|
|
424
|
+
const [segment, segmentFiles] = extractSegment(targetPath, layersConfig, segmentsConfig);
|
|
374
425
|
return {
|
|
375
426
|
layer,
|
|
376
427
|
slice,
|
|
@@ -442,7 +493,8 @@ function compareFeatureSlicedParts(fsPartsToCompare) {
|
|
|
442
493
|
isSameLayerWithoutSlices
|
|
443
494
|
};
|
|
444
495
|
}
|
|
445
|
-
function extractPathsInfo(node, context,
|
|
496
|
+
function extractPathsInfo(node, context, options) {
|
|
497
|
+
const { layersConfig, segmentsConfig } = _nullishCoalesce(options, () => ( {}));
|
|
446
498
|
const {
|
|
447
499
|
targetPath,
|
|
448
500
|
normalizedTargetPath,
|
|
@@ -450,10 +502,10 @@ function extractPathsInfo(node, context, config) {
|
|
|
450
502
|
absoluteTargetPath,
|
|
451
503
|
normalizedCwd
|
|
452
504
|
} = extractPaths(node, context);
|
|
453
|
-
const fsdPartsOfTarget = extractFeatureSlicedParts(absoluteTargetPath, normalizedCwd,
|
|
454
|
-
const fsdPartsOfCurrentFile = extractFeatureSlicedParts(normalizedCurrentFilePath, normalizedCwd,
|
|
455
|
-
const validatedFeatureSlicedPartsOfTarget = validateExtractedFeatureSlicedParts(fsdPartsOfTarget,
|
|
456
|
-
const validatedFeatureSlicedPartsOfCurrentFile = validateExtractedFeatureSlicedParts(fsdPartsOfCurrentFile,
|
|
505
|
+
const fsdPartsOfTarget = extractFeatureSlicedParts(absoluteTargetPath, normalizedCwd, { layersConfig, segmentsConfig });
|
|
506
|
+
const fsdPartsOfCurrentFile = extractFeatureSlicedParts(normalizedCurrentFilePath, normalizedCwd, { layersConfig, segmentsConfig });
|
|
507
|
+
const validatedFeatureSlicedPartsOfTarget = validateExtractedFeatureSlicedParts(fsdPartsOfTarget, layersConfig);
|
|
508
|
+
const validatedFeatureSlicedPartsOfCurrentFile = validateExtractedFeatureSlicedParts(fsdPartsOfCurrentFile, layersConfig);
|
|
457
509
|
const {
|
|
458
510
|
hasUnknownLayers,
|
|
459
511
|
isSameLayer,
|
|
@@ -552,7 +604,7 @@ function validateAndReport(node, context, optionsWithDefault, options = { needCh
|
|
|
552
604
|
if (isIgnoredForValidation) {
|
|
553
605
|
return;
|
|
554
606
|
}
|
|
555
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
607
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig });
|
|
556
608
|
if (shouldBeRelative(pathsInfo)) {
|
|
557
609
|
reportShouldBeRelative(node, context);
|
|
558
610
|
}
|
|
@@ -758,7 +810,7 @@ function validate(node, pathsInfo, allowTypeImports, config) {
|
|
|
758
810
|
function reportValidationErrors(nodes, context, pathsInfo, config) {
|
|
759
811
|
nodes.forEach((node) => reportCanNotImportLayer(context, node, pathsInfo, config));
|
|
760
812
|
}
|
|
761
|
-
function validateAndReport2(node, context, optionsWithDefault, config) {
|
|
813
|
+
function validateAndReport2(node, context, optionsWithDefault, config, segmentsConfig) {
|
|
762
814
|
if (!hasPath(node)) {
|
|
763
815
|
return;
|
|
764
816
|
}
|
|
@@ -766,7 +818,7 @@ function validateAndReport2(node, context, optionsWithDefault, config) {
|
|
|
766
818
|
if (isIgnoredForValidation) {
|
|
767
819
|
return;
|
|
768
820
|
}
|
|
769
|
-
const pathsInfo = extractPathsInfo(node, context, config);
|
|
821
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig: config, segmentsConfig });
|
|
770
822
|
const crossImportInfo = extractCrossImportInfo(pathsInfo.normalizedTargetPath);
|
|
771
823
|
if (crossImportInfo.isCrossImport) {
|
|
772
824
|
const currentFileSlice = pathsInfo.fsdPartsOfCurrentFile.slice;
|
|
@@ -830,12 +882,13 @@ var layers_slices_default = createEslintRule({
|
|
|
830
882
|
],
|
|
831
883
|
create(context, optionsWithDefault) {
|
|
832
884
|
const layersConfig = extractLayersConfig(context);
|
|
885
|
+
const segmentsConfig = extractSegmentsConfig(context);
|
|
833
886
|
return {
|
|
834
887
|
ImportDeclaration(node) {
|
|
835
|
-
validateAndReport2(node, context, optionsWithDefault, layersConfig);
|
|
888
|
+
validateAndReport2(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
836
889
|
},
|
|
837
890
|
ImportExpression(node) {
|
|
838
|
-
validateAndReport2(node, context, optionsWithDefault, layersConfig);
|
|
891
|
+
validateAndReport2(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
839
892
|
}
|
|
840
893
|
};
|
|
841
894
|
}
|
|
@@ -938,7 +991,7 @@ function findTargetSegmentInSameSlice(targetPathParts, currentSliceParts, curren
|
|
|
938
991
|
if (currentSliceParts.length > targetPathParts.length)
|
|
939
992
|
return null;
|
|
940
993
|
const targetHasSameSlice = currentSliceParts.every(
|
|
941
|
-
(part, i) => part.toLowerCase() === _optionalChain([targetPathParts, 'access',
|
|
994
|
+
(part, i) => part.toLowerCase() === _optionalChain([targetPathParts, 'access', _11 => _11[i], 'optionalAccess', _12 => _12.toLowerCase, 'call', _13 => _13()])
|
|
942
995
|
);
|
|
943
996
|
if (!targetHasSameSlice)
|
|
944
997
|
return null;
|
|
@@ -1067,7 +1120,8 @@ var no_cross_segment_reexport_default = createEslintRule({
|
|
|
1067
1120
|
var MESSAGE_ID = {
|
|
1068
1121
|
SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api",
|
|
1069
1122
|
REMOVE_SUGGESTION: "remove-suggestion",
|
|
1070
|
-
LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed"
|
|
1123
|
+
LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed",
|
|
1124
|
+
UNKNOWN_SEGMENT: "unknown-segment"
|
|
1071
1125
|
};
|
|
1072
1126
|
var VALIDATION_LEVEL = {
|
|
1073
1127
|
SEGMENTS: "segments",
|
|
@@ -1109,8 +1163,8 @@ function convertToPublicApi(pathsInfo) {
|
|
|
1109
1163
|
}
|
|
1110
1164
|
|
|
1111
1165
|
// src/rules/public-api/model/errors.ts
|
|
1112
|
-
function reportShouldBeFromPublicApi(node, context, layersConfig) {
|
|
1113
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
1166
|
+
function reportShouldBeFromPublicApi(node, context, layersConfig, segmentsConfig) {
|
|
1167
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig });
|
|
1114
1168
|
const [fixedPath, valueToRemove] = convertToPublicApi(pathsInfo);
|
|
1115
1169
|
context.report({
|
|
1116
1170
|
node: node.source,
|
|
@@ -1129,6 +1183,15 @@ function reportShouldBeFromPublicApi(node, context, layersConfig) {
|
|
|
1129
1183
|
]
|
|
1130
1184
|
});
|
|
1131
1185
|
}
|
|
1186
|
+
function reportUnknownSegment(node, context, segment) {
|
|
1187
|
+
context.report({
|
|
1188
|
+
node: node.source,
|
|
1189
|
+
messageId: MESSAGE_ID.UNKNOWN_SEGMENT,
|
|
1190
|
+
data: {
|
|
1191
|
+
segment
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1132
1195
|
function reportLayersPublicApiNotAllowed(node, context) {
|
|
1133
1196
|
context.report({
|
|
1134
1197
|
node,
|
|
@@ -1136,6 +1199,49 @@ function reportLayersPublicApiNotAllowed(node, context) {
|
|
|
1136
1199
|
});
|
|
1137
1200
|
}
|
|
1138
1201
|
|
|
1202
|
+
// src/rules/public-api/model/is-unknown-segment.ts
|
|
1203
|
+
function isGroupFolder(part) {
|
|
1204
|
+
return part.startsWith("(") && part.endsWith(")");
|
|
1205
|
+
}
|
|
1206
|
+
function extractPotentialSegment(targetPath, layersConfig) {
|
|
1207
|
+
const normalizedLayersConfig = _nullishCoalesce(layersConfig, () => ( normalizeLayersConfig()));
|
|
1208
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
1209
|
+
const parts = targetPath.split("/").filter(Boolean);
|
|
1210
|
+
const layerIndex = parts.findIndex(
|
|
1211
|
+
(part) => layersWithSlices2.some((layer) => layer.toLowerCase() === part.toLowerCase())
|
|
1212
|
+
);
|
|
1213
|
+
if (layerIndex === -1 || layerIndex >= parts.length - 2) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
const partsAfterLayer = parts.slice(layerIndex + 1);
|
|
1217
|
+
const nonGroupParts = partsAfterLayer.filter((part) => !isGroupFolder(part));
|
|
1218
|
+
if (nonGroupParts.length < 2) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
const potentialSegment = nonGroupParts[1];
|
|
1222
|
+
const segmentWithoutExt = _optionalChain([potentialSegment, 'optionalAccess', _14 => _14.replace, 'call', _15 => _15(/\.\w+$/, "")]) || null;
|
|
1223
|
+
return segmentWithoutExt;
|
|
1224
|
+
}
|
|
1225
|
+
function isUnknownSegment(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1226
|
+
const ruleOptions = extractRuleOptions(optionsWithDefault);
|
|
1227
|
+
if (ruleOptions.level !== VALIDATION_LEVEL.SEGMENTS) {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
const effectiveSegmentsConfig = _nullishCoalesce(segmentsConfig, () => ( [...DEFAULT_SEGMENTS]));
|
|
1231
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig: effectiveSegmentsConfig });
|
|
1232
|
+
if (pathsInfo.fsdPartsOfTarget.segment) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
const potentialSegment = extractPotentialSegment(pathsInfo.normalizedTargetPath, layersConfig);
|
|
1236
|
+
if (!potentialSegment) {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
if (!isKnownSegment(potentialSegment, effectiveSegmentsConfig)) {
|
|
1240
|
+
return potentialSegment;
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1139
1245
|
// src/rules/public-api/model/is-index-file.ts
|
|
1140
1246
|
function isIndexFile(segmentFiles) {
|
|
1141
1247
|
return /^index\.\w+/i.test(segmentFiles);
|
|
@@ -1176,8 +1282,8 @@ function shouldBeFromSegmentsPublicApi(pathsInfo, validateOptions) {
|
|
|
1176
1282
|
const needValidateSegments = validateOptions.level === VALIDATION_LEVEL.SEGMENTS;
|
|
1177
1283
|
return needValidateSegments && !isSegmentsPublicApi(pathsInfo);
|
|
1178
1284
|
}
|
|
1179
|
-
function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig) {
|
|
1180
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
1285
|
+
function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1286
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig });
|
|
1181
1287
|
const ruleOptions = extractRuleOptions(optionsWithDefault);
|
|
1182
1288
|
if (hasNestedCrossImportPath(pathsInfo.normalizedTargetPath)) {
|
|
1183
1289
|
return true;
|
|
@@ -1186,7 +1292,7 @@ function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig)
|
|
|
1186
1292
|
}
|
|
1187
1293
|
|
|
1188
1294
|
// src/rules/public-api/model/validate-and-report.ts
|
|
1189
|
-
function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
1295
|
+
function validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1190
1296
|
if (!hasPath(node)) {
|
|
1191
1297
|
return;
|
|
1192
1298
|
}
|
|
@@ -1194,8 +1300,13 @@ function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
|
1194
1300
|
if (isIgnoredForValidation) {
|
|
1195
1301
|
return;
|
|
1196
1302
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1303
|
+
const unknownSegment = isUnknownSegment(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1304
|
+
if (unknownSegment) {
|
|
1305
|
+
reportUnknownSegment(node, context, unknownSegment);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig, segmentsConfig)) {
|
|
1309
|
+
reportShouldBeFromPublicApi(node, context, layersConfig, segmentsConfig);
|
|
1199
1310
|
}
|
|
1200
1311
|
}
|
|
1201
1312
|
|
|
@@ -1236,7 +1347,8 @@ var public_api_default = createEslintRule({
|
|
|
1236
1347
|
messages: {
|
|
1237
1348
|
[MESSAGE_ID.SHOULD_BE_FROM_PUBLIC_API]: 'Absolute imports are only allowed from public api ("{{ fixedPath }}")',
|
|
1238
1349
|
[MESSAGE_ID.REMOVE_SUGGESTION]: 'Remove the "{{ valueToRemove }}"',
|
|
1239
|
-
[MESSAGE_ID.LAYERS_PUBLIC_API_NOT_ALLOWED]: "The layer public API is not allowed. It harms both architecturally and practically (code splitting)"
|
|
1350
|
+
[MESSAGE_ID.LAYERS_PUBLIC_API_NOT_ALLOWED]: "The layer public API is not allowed. It harms both architecturally and practically (code splitting)",
|
|
1351
|
+
[MESSAGE_ID.UNKNOWN_SEGMENT]: 'Unknown segment "{{ segment }}". Add it to segments configuration or use the public API.'
|
|
1240
1352
|
},
|
|
1241
1353
|
schema: [
|
|
1242
1354
|
{
|
|
@@ -1274,18 +1386,19 @@ var public_api_default = createEslintRule({
|
|
|
1274
1386
|
],
|
|
1275
1387
|
create(context, optionsWithDefault) {
|
|
1276
1388
|
const layersConfig = extractLayersConfig(context);
|
|
1389
|
+
const segmentsConfig = extractSegmentsConfig(context);
|
|
1277
1390
|
return {
|
|
1278
1391
|
ImportDeclaration(node) {
|
|
1279
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1392
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1280
1393
|
},
|
|
1281
1394
|
ImportExpression(node) {
|
|
1282
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1395
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1283
1396
|
},
|
|
1284
1397
|
ExportAllDeclaration(node) {
|
|
1285
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1398
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1286
1399
|
},
|
|
1287
1400
|
ExportNamedDeclaration(node) {
|
|
1288
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1401
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1289
1402
|
},
|
|
1290
1403
|
Program(node) {
|
|
1291
1404
|
validateAndReportProgram(node, context, optionsWithDefault);
|
package/dist/index.d.cts
CHANGED
|
@@ -42,6 +42,7 @@ type Segments = ReadonlyArray<'ui' | 'model' | 'lib' | 'api' | 'config' | 'asset
|
|
|
42
42
|
type Segment = Segments[number];
|
|
43
43
|
/**
|
|
44
44
|
* Slice segments regulated by feature-sliced methodologies
|
|
45
|
+
* @deprecated Use normalizeSegmentsConfig() from segments-config.ts for custom segments support
|
|
45
46
|
*/
|
|
46
47
|
declare const segments: Segments;
|
|
47
48
|
type TypedFlatConfigItem = Omit<Linter.Config<Linter.RulesRecord>, 'plugins'> & {
|
|
@@ -58,6 +59,7 @@ declare const MESSAGE_ID: {
|
|
|
58
59
|
readonly SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api";
|
|
59
60
|
readonly REMOVE_SUGGESTION: "remove-suggestion";
|
|
60
61
|
readonly LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed";
|
|
62
|
+
readonly UNKNOWN_SEGMENT: "unknown-segment";
|
|
61
63
|
};
|
|
62
64
|
declare const VALIDATION_LEVEL: {
|
|
63
65
|
readonly SEGMENTS: "segments";
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ type Segments = ReadonlyArray<'ui' | 'model' | 'lib' | 'api' | 'config' | 'asset
|
|
|
42
42
|
type Segment = Segments[number];
|
|
43
43
|
/**
|
|
44
44
|
* Slice segments regulated by feature-sliced methodologies
|
|
45
|
+
* @deprecated Use normalizeSegmentsConfig() from segments-config.ts for custom segments support
|
|
45
46
|
*/
|
|
46
47
|
declare const segments: Segments;
|
|
47
48
|
type TypedFlatConfigItem = Omit<Linter.Config<Linter.RulesRecord>, 'plugins'> & {
|
|
@@ -58,6 +59,7 @@ declare const MESSAGE_ID: {
|
|
|
58
59
|
readonly SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api";
|
|
59
60
|
readonly REMOVE_SUGGESTION: "remove-suggestion";
|
|
60
61
|
readonly LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed";
|
|
62
|
+
readonly UNKNOWN_SEGMENT: "unknown-segment";
|
|
61
63
|
};
|
|
62
64
|
declare const VALIDATION_LEVEL: {
|
|
63
65
|
readonly SEGMENTS: "segments";
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,14 @@ var segments = [
|
|
|
38
38
|
"config",
|
|
39
39
|
"assets"
|
|
40
40
|
];
|
|
41
|
+
var DEFAULT_SEGMENTS = [
|
|
42
|
+
"ui",
|
|
43
|
+
"model",
|
|
44
|
+
"lib",
|
|
45
|
+
"api",
|
|
46
|
+
"config",
|
|
47
|
+
"assets"
|
|
48
|
+
];
|
|
41
49
|
var pathSeparator = "/";
|
|
42
50
|
|
|
43
51
|
// src/lib/feature-sliced/layers-config.ts
|
|
@@ -80,7 +88,7 @@ function canLayerContainSlices(layer, config) {
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
// package.json
|
|
83
|
-
var version = "2.0.0-rc.
|
|
91
|
+
var version = "2.0.0-rc.9";
|
|
84
92
|
|
|
85
93
|
// src/rules/index.ts
|
|
86
94
|
import pluginImport from "eslint-plugin-import-x";
|
|
@@ -132,7 +140,7 @@ function convertToAbsolute(base, target) {
|
|
|
132
140
|
|
|
133
141
|
// src/lib/rule/extract-current-file-path.ts
|
|
134
142
|
function extractCurrentFilePath(context) {
|
|
135
|
-
const currentFilePath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
|
|
143
|
+
const currentFilePath = context.physicalFilename ?? context.filename ?? (context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
|
|
136
144
|
return normalizePath(currentFilePath);
|
|
137
145
|
}
|
|
138
146
|
|
|
@@ -163,7 +171,7 @@ function isUndefined(target) {
|
|
|
163
171
|
|
|
164
172
|
// src/lib/rule/extract-cwd.ts
|
|
165
173
|
function extractCwd(context) {
|
|
166
|
-
const cwd = context.getCwd?.();
|
|
174
|
+
const cwd = context.cwd ?? context.getCwd?.();
|
|
167
175
|
if (isUndefined(cwd)) {
|
|
168
176
|
return void 0;
|
|
169
177
|
}
|
|
@@ -213,6 +221,46 @@ function extractRuleOptions(optionsWithDefault) {
|
|
|
213
221
|
return optionsWithDefault[0];
|
|
214
222
|
}
|
|
215
223
|
|
|
224
|
+
// src/lib/feature-sliced/segments-config.ts
|
|
225
|
+
function normalizeSegmentsConfig(config) {
|
|
226
|
+
if (!config) {
|
|
227
|
+
return [...DEFAULT_SEGMENTS];
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(config)) {
|
|
230
|
+
const customSegments = config.map((s) => s.toLowerCase());
|
|
231
|
+
const combined = [...DEFAULT_SEGMENTS, ...customSegments];
|
|
232
|
+
return [...new Set(combined)];
|
|
233
|
+
}
|
|
234
|
+
const replacedSegments = config.replace.map((s) => s.toLowerCase());
|
|
235
|
+
return [...new Set(replacedSegments)];
|
|
236
|
+
}
|
|
237
|
+
function isKnownSegment(segment, segments2) {
|
|
238
|
+
if (typeof segment !== "string" || segment === "") {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return segments2.includes(segment.toLowerCase());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/lib/rule/extract-segments-config.ts
|
|
245
|
+
function isValidSegmentsConfig(value) {
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.every((item) => typeof item === "string");
|
|
248
|
+
}
|
|
249
|
+
if (typeof value === "object" && value !== null && "replace" in value) {
|
|
250
|
+
const replaceValue = value.replace;
|
|
251
|
+
return Array.isArray(replaceValue) && replaceValue.every((item) => typeof item === "string");
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
function extractSegmentsConfig(context) {
|
|
256
|
+
const settings = context.settings;
|
|
257
|
+
const pluginSettings = settings?.[PLUGIN_NAME];
|
|
258
|
+
if (pluginSettings?.segments && isValidSegmentsConfig(pluginSettings.segments)) {
|
|
259
|
+
return normalizeSegmentsConfig(pluginSettings.segments);
|
|
260
|
+
}
|
|
261
|
+
return normalizeSegmentsConfig();
|
|
262
|
+
}
|
|
263
|
+
|
|
216
264
|
// src/lib/rule/get-source-range-without-quotes.ts
|
|
217
265
|
function getSourceRangeWithoutQuotes([rangeStart, rangeEnd]) {
|
|
218
266
|
return [rangeStart + 1, rangeEnd - 1];
|
|
@@ -317,17 +365,18 @@ function extractLayer(targetPath, cwd, config) {
|
|
|
317
365
|
}
|
|
318
366
|
|
|
319
367
|
// src/lib/feature-sliced/extract-segment.ts
|
|
320
|
-
function createFsdPartsRegExp(layersWithSlices2) {
|
|
368
|
+
function createFsdPartsRegExp(layersWithSlices2, segmentsList) {
|
|
321
369
|
const layersUnion = layersWithSlices2.join("|");
|
|
322
|
-
const segmentsUnion =
|
|
370
|
+
const segmentsUnion = segmentsList.join("|");
|
|
323
371
|
return new RegExp(
|
|
324
372
|
`(?<=(?<layer>${layersUnion}))\\/(?<slice>([\\w-]*\\/)+?)(?<segment>(${segmentsUnion})(\\.\\w+)?)(\\/(?<segmentFiles>.*))?`
|
|
325
373
|
);
|
|
326
374
|
}
|
|
327
|
-
function extractSegment(targetPath,
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
const
|
|
375
|
+
function extractSegment(targetPath, layersConfig, segmentsConfig) {
|
|
376
|
+
const normalizedLayersConfig = layersConfig ?? normalizeLayersConfig();
|
|
377
|
+
const segmentsList = segmentsConfig ?? [...DEFAULT_SEGMENTS];
|
|
378
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
379
|
+
const fsdPartsRegExp = createFsdPartsRegExp(layersWithSlices2, segmentsList);
|
|
331
380
|
const fsdParts = targetPath.match(fsdPartsRegExp);
|
|
332
381
|
if (fsdParts === null) {
|
|
333
382
|
return [null, null];
|
|
@@ -342,9 +391,10 @@ function extractSegment(targetPath, config) {
|
|
|
342
391
|
}
|
|
343
392
|
|
|
344
393
|
// src/lib/feature-sliced/extract-slice.ts
|
|
345
|
-
function extractSlice(targetPath,
|
|
346
|
-
const
|
|
347
|
-
const
|
|
394
|
+
function extractSlice(targetPath, layersConfig, segmentsConfig) {
|
|
395
|
+
const normalizedLayersConfig = layersConfig ?? normalizeLayersConfig();
|
|
396
|
+
const segmentsList = segmentsConfig ?? [...DEFAULT_SEGMENTS];
|
|
397
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
348
398
|
const pathWithoutFile = targetPath.replace(/\/[\w-]+\.\w+$/, "");
|
|
349
399
|
const parts = pathWithoutFile.split("/").filter(Boolean);
|
|
350
400
|
const layerIndex = parts.findIndex(
|
|
@@ -355,7 +405,7 @@ function extractSlice(targetPath, config) {
|
|
|
355
405
|
}
|
|
356
406
|
const partsAfterLayer = parts.slice(layerIndex + 1);
|
|
357
407
|
const segmentIndex = partsAfterLayer.findIndex(
|
|
358
|
-
(part) =>
|
|
408
|
+
(part) => segmentsList.some((seg) => seg.toLowerCase() === part.toLowerCase())
|
|
359
409
|
);
|
|
360
410
|
if (segmentIndex > 0) {
|
|
361
411
|
return partsAfterLayer[segmentIndex - 1];
|
|
@@ -367,10 +417,11 @@ function extractSlice(targetPath, config) {
|
|
|
367
417
|
}
|
|
368
418
|
|
|
369
419
|
// src/lib/feature-sliced/extract-feature-sliced-parts.ts
|
|
370
|
-
function extractFeatureSlicedParts(targetPath, cwd,
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
const
|
|
420
|
+
function extractFeatureSlicedParts(targetPath, cwd, options) {
|
|
421
|
+
const { layersConfig, segmentsConfig } = options ?? {};
|
|
422
|
+
const layer = extractLayer(targetPath, cwd, layersConfig);
|
|
423
|
+
const slice = extractSlice(targetPath, layersConfig, segmentsConfig);
|
|
424
|
+
const [segment, segmentFiles] = extractSegment(targetPath, layersConfig, segmentsConfig);
|
|
374
425
|
return {
|
|
375
426
|
layer,
|
|
376
427
|
slice,
|
|
@@ -442,7 +493,8 @@ function compareFeatureSlicedParts(fsPartsToCompare) {
|
|
|
442
493
|
isSameLayerWithoutSlices
|
|
443
494
|
};
|
|
444
495
|
}
|
|
445
|
-
function extractPathsInfo(node, context,
|
|
496
|
+
function extractPathsInfo(node, context, options) {
|
|
497
|
+
const { layersConfig, segmentsConfig } = options ?? {};
|
|
446
498
|
const {
|
|
447
499
|
targetPath,
|
|
448
500
|
normalizedTargetPath,
|
|
@@ -450,10 +502,10 @@ function extractPathsInfo(node, context, config) {
|
|
|
450
502
|
absoluteTargetPath,
|
|
451
503
|
normalizedCwd
|
|
452
504
|
} = extractPaths(node, context);
|
|
453
|
-
const fsdPartsOfTarget = extractFeatureSlicedParts(absoluteTargetPath, normalizedCwd,
|
|
454
|
-
const fsdPartsOfCurrentFile = extractFeatureSlicedParts(normalizedCurrentFilePath, normalizedCwd,
|
|
455
|
-
const validatedFeatureSlicedPartsOfTarget = validateExtractedFeatureSlicedParts(fsdPartsOfTarget,
|
|
456
|
-
const validatedFeatureSlicedPartsOfCurrentFile = validateExtractedFeatureSlicedParts(fsdPartsOfCurrentFile,
|
|
505
|
+
const fsdPartsOfTarget = extractFeatureSlicedParts(absoluteTargetPath, normalizedCwd, { layersConfig, segmentsConfig });
|
|
506
|
+
const fsdPartsOfCurrentFile = extractFeatureSlicedParts(normalizedCurrentFilePath, normalizedCwd, { layersConfig, segmentsConfig });
|
|
507
|
+
const validatedFeatureSlicedPartsOfTarget = validateExtractedFeatureSlicedParts(fsdPartsOfTarget, layersConfig);
|
|
508
|
+
const validatedFeatureSlicedPartsOfCurrentFile = validateExtractedFeatureSlicedParts(fsdPartsOfCurrentFile, layersConfig);
|
|
457
509
|
const {
|
|
458
510
|
hasUnknownLayers,
|
|
459
511
|
isSameLayer,
|
|
@@ -552,7 +604,7 @@ function validateAndReport(node, context, optionsWithDefault, options = { needCh
|
|
|
552
604
|
if (isIgnoredForValidation) {
|
|
553
605
|
return;
|
|
554
606
|
}
|
|
555
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
607
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig });
|
|
556
608
|
if (shouldBeRelative(pathsInfo)) {
|
|
557
609
|
reportShouldBeRelative(node, context);
|
|
558
610
|
}
|
|
@@ -758,7 +810,7 @@ function validate(node, pathsInfo, allowTypeImports, config) {
|
|
|
758
810
|
function reportValidationErrors(nodes, context, pathsInfo, config) {
|
|
759
811
|
nodes.forEach((node) => reportCanNotImportLayer(context, node, pathsInfo, config));
|
|
760
812
|
}
|
|
761
|
-
function validateAndReport2(node, context, optionsWithDefault, config) {
|
|
813
|
+
function validateAndReport2(node, context, optionsWithDefault, config, segmentsConfig) {
|
|
762
814
|
if (!hasPath(node)) {
|
|
763
815
|
return;
|
|
764
816
|
}
|
|
@@ -766,7 +818,7 @@ function validateAndReport2(node, context, optionsWithDefault, config) {
|
|
|
766
818
|
if (isIgnoredForValidation) {
|
|
767
819
|
return;
|
|
768
820
|
}
|
|
769
|
-
const pathsInfo = extractPathsInfo(node, context, config);
|
|
821
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig: config, segmentsConfig });
|
|
770
822
|
const crossImportInfo = extractCrossImportInfo(pathsInfo.normalizedTargetPath);
|
|
771
823
|
if (crossImportInfo.isCrossImport) {
|
|
772
824
|
const currentFileSlice = pathsInfo.fsdPartsOfCurrentFile.slice;
|
|
@@ -830,12 +882,13 @@ var layers_slices_default = createEslintRule({
|
|
|
830
882
|
],
|
|
831
883
|
create(context, optionsWithDefault) {
|
|
832
884
|
const layersConfig = extractLayersConfig(context);
|
|
885
|
+
const segmentsConfig = extractSegmentsConfig(context);
|
|
833
886
|
return {
|
|
834
887
|
ImportDeclaration(node) {
|
|
835
|
-
validateAndReport2(node, context, optionsWithDefault, layersConfig);
|
|
888
|
+
validateAndReport2(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
836
889
|
},
|
|
837
890
|
ImportExpression(node) {
|
|
838
|
-
validateAndReport2(node, context, optionsWithDefault, layersConfig);
|
|
891
|
+
validateAndReport2(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
839
892
|
}
|
|
840
893
|
};
|
|
841
894
|
}
|
|
@@ -1067,7 +1120,8 @@ var no_cross_segment_reexport_default = createEslintRule({
|
|
|
1067
1120
|
var MESSAGE_ID = {
|
|
1068
1121
|
SHOULD_BE_FROM_PUBLIC_API: "should-be-from-public-api",
|
|
1069
1122
|
REMOVE_SUGGESTION: "remove-suggestion",
|
|
1070
|
-
LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed"
|
|
1123
|
+
LAYERS_PUBLIC_API_NOT_ALLOWED: "layers-public-api-not-allowed",
|
|
1124
|
+
UNKNOWN_SEGMENT: "unknown-segment"
|
|
1071
1125
|
};
|
|
1072
1126
|
var VALIDATION_LEVEL = {
|
|
1073
1127
|
SEGMENTS: "segments",
|
|
@@ -1109,8 +1163,8 @@ function convertToPublicApi(pathsInfo) {
|
|
|
1109
1163
|
}
|
|
1110
1164
|
|
|
1111
1165
|
// src/rules/public-api/model/errors.ts
|
|
1112
|
-
function reportShouldBeFromPublicApi(node, context, layersConfig) {
|
|
1113
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
1166
|
+
function reportShouldBeFromPublicApi(node, context, layersConfig, segmentsConfig) {
|
|
1167
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig });
|
|
1114
1168
|
const [fixedPath, valueToRemove] = convertToPublicApi(pathsInfo);
|
|
1115
1169
|
context.report({
|
|
1116
1170
|
node: node.source,
|
|
@@ -1129,6 +1183,15 @@ function reportShouldBeFromPublicApi(node, context, layersConfig) {
|
|
|
1129
1183
|
]
|
|
1130
1184
|
});
|
|
1131
1185
|
}
|
|
1186
|
+
function reportUnknownSegment(node, context, segment) {
|
|
1187
|
+
context.report({
|
|
1188
|
+
node: node.source,
|
|
1189
|
+
messageId: MESSAGE_ID.UNKNOWN_SEGMENT,
|
|
1190
|
+
data: {
|
|
1191
|
+
segment
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1132
1195
|
function reportLayersPublicApiNotAllowed(node, context) {
|
|
1133
1196
|
context.report({
|
|
1134
1197
|
node,
|
|
@@ -1136,6 +1199,49 @@ function reportLayersPublicApiNotAllowed(node, context) {
|
|
|
1136
1199
|
});
|
|
1137
1200
|
}
|
|
1138
1201
|
|
|
1202
|
+
// src/rules/public-api/model/is-unknown-segment.ts
|
|
1203
|
+
function isGroupFolder(part) {
|
|
1204
|
+
return part.startsWith("(") && part.endsWith(")");
|
|
1205
|
+
}
|
|
1206
|
+
function extractPotentialSegment(targetPath, layersConfig) {
|
|
1207
|
+
const normalizedLayersConfig = layersConfig ?? normalizeLayersConfig();
|
|
1208
|
+
const layersWithSlices2 = getLayersWithSlices(normalizedLayersConfig);
|
|
1209
|
+
const parts = targetPath.split("/").filter(Boolean);
|
|
1210
|
+
const layerIndex = parts.findIndex(
|
|
1211
|
+
(part) => layersWithSlices2.some((layer) => layer.toLowerCase() === part.toLowerCase())
|
|
1212
|
+
);
|
|
1213
|
+
if (layerIndex === -1 || layerIndex >= parts.length - 2) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
const partsAfterLayer = parts.slice(layerIndex + 1);
|
|
1217
|
+
const nonGroupParts = partsAfterLayer.filter((part) => !isGroupFolder(part));
|
|
1218
|
+
if (nonGroupParts.length < 2) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
const potentialSegment = nonGroupParts[1];
|
|
1222
|
+
const segmentWithoutExt = potentialSegment?.replace(/\.\w+$/, "") || null;
|
|
1223
|
+
return segmentWithoutExt;
|
|
1224
|
+
}
|
|
1225
|
+
function isUnknownSegment(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1226
|
+
const ruleOptions = extractRuleOptions(optionsWithDefault);
|
|
1227
|
+
if (ruleOptions.level !== VALIDATION_LEVEL.SEGMENTS) {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
const effectiveSegmentsConfig = segmentsConfig ?? [...DEFAULT_SEGMENTS];
|
|
1231
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig: effectiveSegmentsConfig });
|
|
1232
|
+
if (pathsInfo.fsdPartsOfTarget.segment) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
const potentialSegment = extractPotentialSegment(pathsInfo.normalizedTargetPath, layersConfig);
|
|
1236
|
+
if (!potentialSegment) {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
if (!isKnownSegment(potentialSegment, effectiveSegmentsConfig)) {
|
|
1240
|
+
return potentialSegment;
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1139
1245
|
// src/rules/public-api/model/is-index-file.ts
|
|
1140
1246
|
function isIndexFile(segmentFiles) {
|
|
1141
1247
|
return /^index\.\w+/i.test(segmentFiles);
|
|
@@ -1176,8 +1282,8 @@ function shouldBeFromSegmentsPublicApi(pathsInfo, validateOptions) {
|
|
|
1176
1282
|
const needValidateSegments = validateOptions.level === VALIDATION_LEVEL.SEGMENTS;
|
|
1177
1283
|
return needValidateSegments && !isSegmentsPublicApi(pathsInfo);
|
|
1178
1284
|
}
|
|
1179
|
-
function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig) {
|
|
1180
|
-
const pathsInfo = extractPathsInfo(node, context, layersConfig);
|
|
1285
|
+
function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1286
|
+
const pathsInfo = extractPathsInfo(node, context, { layersConfig, segmentsConfig });
|
|
1181
1287
|
const ruleOptions = extractRuleOptions(optionsWithDefault);
|
|
1182
1288
|
if (hasNestedCrossImportPath(pathsInfo.normalizedTargetPath)) {
|
|
1183
1289
|
return true;
|
|
@@ -1186,7 +1292,7 @@ function shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig)
|
|
|
1186
1292
|
}
|
|
1187
1293
|
|
|
1188
1294
|
// src/rules/public-api/model/validate-and-report.ts
|
|
1189
|
-
function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
1295
|
+
function validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig) {
|
|
1190
1296
|
if (!hasPath(node)) {
|
|
1191
1297
|
return;
|
|
1192
1298
|
}
|
|
@@ -1194,8 +1300,13 @@ function validateAndReport4(node, context, optionsWithDefault, layersConfig) {
|
|
|
1194
1300
|
if (isIgnoredForValidation) {
|
|
1195
1301
|
return;
|
|
1196
1302
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1303
|
+
const unknownSegment = isUnknownSegment(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1304
|
+
if (unknownSegment) {
|
|
1305
|
+
reportUnknownSegment(node, context, unknownSegment);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (shouldBeFromPublicApi(node, context, optionsWithDefault, layersConfig, segmentsConfig)) {
|
|
1309
|
+
reportShouldBeFromPublicApi(node, context, layersConfig, segmentsConfig);
|
|
1199
1310
|
}
|
|
1200
1311
|
}
|
|
1201
1312
|
|
|
@@ -1236,7 +1347,8 @@ var public_api_default = createEslintRule({
|
|
|
1236
1347
|
messages: {
|
|
1237
1348
|
[MESSAGE_ID.SHOULD_BE_FROM_PUBLIC_API]: 'Absolute imports are only allowed from public api ("{{ fixedPath }}")',
|
|
1238
1349
|
[MESSAGE_ID.REMOVE_SUGGESTION]: 'Remove the "{{ valueToRemove }}"',
|
|
1239
|
-
[MESSAGE_ID.LAYERS_PUBLIC_API_NOT_ALLOWED]: "The layer public API is not allowed. It harms both architecturally and practically (code splitting)"
|
|
1350
|
+
[MESSAGE_ID.LAYERS_PUBLIC_API_NOT_ALLOWED]: "The layer public API is not allowed. It harms both architecturally and practically (code splitting)",
|
|
1351
|
+
[MESSAGE_ID.UNKNOWN_SEGMENT]: 'Unknown segment "{{ segment }}". Add it to segments configuration or use the public API.'
|
|
1240
1352
|
},
|
|
1241
1353
|
schema: [
|
|
1242
1354
|
{
|
|
@@ -1274,18 +1386,19 @@ var public_api_default = createEslintRule({
|
|
|
1274
1386
|
],
|
|
1275
1387
|
create(context, optionsWithDefault) {
|
|
1276
1388
|
const layersConfig = extractLayersConfig(context);
|
|
1389
|
+
const segmentsConfig = extractSegmentsConfig(context);
|
|
1277
1390
|
return {
|
|
1278
1391
|
ImportDeclaration(node) {
|
|
1279
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1392
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1280
1393
|
},
|
|
1281
1394
|
ImportExpression(node) {
|
|
1282
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1395
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1283
1396
|
},
|
|
1284
1397
|
ExportAllDeclaration(node) {
|
|
1285
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1398
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1286
1399
|
},
|
|
1287
1400
|
ExportNamedDeclaration(node) {
|
|
1288
|
-
validateAndReport4(node, context, optionsWithDefault, layersConfig);
|
|
1401
|
+
validateAndReport4(node, context, optionsWithDefault, layersConfig, segmentsConfig);
|
|
1289
1402
|
},
|
|
1290
1403
|
Program(node) {
|
|
1291
1404
|
validateAndReportProgram(node, context, optionsWithDefault);
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conarti/eslint-plugin-feature-sliced",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.9",
|
|
5
5
|
"description": "Feature-sliced design methodology plugin",
|
|
6
6
|
"author": "Aleksandr Belous <abelous2009@gmail.com>",
|
|
7
7
|
"license": "ISC",
|
|
8
|
-
"repository":
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/conarti/eslint-plugin-feature-sliced.git"
|
|
11
|
+
},
|
|
9
12
|
"keywords": [
|
|
10
13
|
"eslint",
|
|
11
14
|
"eslintplugin",
|