@atlaskit/eslint-plugin-platform 2.8.0 → 2.9.1
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/CHANGELOG.md +16 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +0 -1
- package/dist/cjs/rules/ensure-use-sync-external-store-server-snapshot/index.js +41 -0
- package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +534 -74
- package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +428 -119
- package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +3 -2
- package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +7 -3
- package/dist/cjs/rules/import/shared/jest-utils.js +62 -9
- package/dist/cjs/rules/import/shared/package-resolution.js +300 -22
- package/dist/cjs/rules/no-restricted-fedramp-imports/index.js +65 -0
- package/dist/cjs/rules/visit-example-type-import-required/index.js +409 -0
- package/dist/es2019/index.js +8 -1
- package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +0 -1
- package/dist/es2019/rules/ensure-use-sync-external-store-server-snapshot/index.js +43 -0
- package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +431 -25
- package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +287 -25
- package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +3 -2
- package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +7 -3
- package/dist/es2019/rules/import/shared/jest-utils.js +44 -0
- package/dist/es2019/rules/import/shared/package-resolution.js +211 -4
- package/dist/es2019/rules/no-restricted-fedramp-imports/index.js +47 -0
- package/dist/es2019/rules/visit-example-type-import-required/index.js +375 -0
- package/dist/esm/index.js +8 -1
- package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +0 -1
- package/dist/esm/rules/ensure-use-sync-external-store-server-snapshot/index.js +35 -0
- package/dist/esm/rules/import/no-barrel-entry-imports/index.js +535 -75
- package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +430 -121
- package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +3 -2
- package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +7 -3
- package/dist/esm/rules/import/shared/jest-utils.js +61 -9
- package/dist/esm/rules/import/shared/package-resolution.js +298 -24
- package/dist/esm/rules/no-restricted-fedramp-imports/index.js +59 -0
- package/dist/esm/rules/visit-example-type-import-required/index.js +402 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
- package/dist/types/rules/import/shared/jest-utils.d.ts +8 -0
- package/dist/types/rules/import/shared/package-resolution.d.ts +47 -2
- package/dist/types/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
- package/dist/types/rules/visit-example-type-import-required/index.d.ts +4 -0
- package/dist/types-ts4.5/index.d.ts +14 -0
- package/dist/types-ts4.5/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +8 -0
- package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +47 -2
- package/dist/types-ts4.5/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/visit-example-type-import-required/index.d.ts +4 -0
- package/package.json +3 -1
|
@@ -2,9 +2,9 @@ import { dirname } from 'path';
|
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
3
|
import { hasReExportsFromOtherFiles, parseBarrelExports } from '../shared/barrel-parsing';
|
|
4
4
|
import { DEFAULT_TARGET_FOLDERS, findWorkspaceRoot, isRelativeImport, readFileContent, resolveImportPath } from '../shared/file-system';
|
|
5
|
-
import { extractImportPath, findJestRequireMockCalls, isJestMockCall, isJestRequireActual, resolveNewPathForRequireMock } from '../shared/jest-utils';
|
|
5
|
+
import { extractImportPath, findJestRequireActualCalls, findJestRequireMockCalls, isJestMockCall, isJestRequireActual, resolveNewPathForRequireMock } from '../shared/jest-utils';
|
|
6
6
|
import { findPackageInRegistry, isPackageInApplyToImportsFrom } from '../shared/package-registry';
|
|
7
|
-
import { findExportForSourceFile, parsePackageExports } from '../shared/package-resolution';
|
|
7
|
+
import { findCrossPackageBridgeExportPath, findExportForSourceFile, parsePackageExports } from '../shared/package-resolution';
|
|
8
8
|
import { realFileSystem } from '../shared/types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -525,12 +525,15 @@ function traceSymbolsToExports({
|
|
|
525
525
|
exportMap,
|
|
526
526
|
exportsMap,
|
|
527
527
|
currentExportPath,
|
|
528
|
-
fs
|
|
528
|
+
fs,
|
|
529
|
+
barrelPackageName,
|
|
530
|
+
preferImportedPackageSubpath
|
|
529
531
|
}) {
|
|
530
532
|
const groupedByExport = new Map();
|
|
531
533
|
const crossPackageGroups = new Map();
|
|
532
534
|
const unmappedSymbols = [];
|
|
533
535
|
for (const symbolName of symbolNames) {
|
|
536
|
+
var _findExportForSourceF, _findExportForSourceF2;
|
|
534
537
|
const exportInfo = exportMap.get(symbolName);
|
|
535
538
|
if (!exportInfo) {
|
|
536
539
|
unmappedSymbols.push(symbolName);
|
|
@@ -539,25 +542,47 @@ function traceSymbolsToExports({
|
|
|
539
542
|
|
|
540
543
|
// Check for cross-package source first
|
|
541
544
|
if (exportInfo.crossPackageSource) {
|
|
542
|
-
|
|
545
|
+
let key;
|
|
546
|
+
let tracedOriginalName = exportInfo.originalName;
|
|
547
|
+
let barrelBridgeExportPath;
|
|
548
|
+
if (preferImportedPackageSubpath) {
|
|
549
|
+
const bridge = findCrossPackageBridgeExportPath({
|
|
550
|
+
exportsMap,
|
|
551
|
+
crossPackageName: exportInfo.crossPackageSource.packageName,
|
|
552
|
+
exportedName: symbolName,
|
|
553
|
+
fs
|
|
554
|
+
});
|
|
555
|
+
if (bridge) {
|
|
556
|
+
key = `${barrelPackageName}${bridge.exportPath.slice(1)}`;
|
|
557
|
+
barrelBridgeExportPath = bridge.exportPath;
|
|
558
|
+
if (bridge.entryPointExportName !== undefined) {
|
|
559
|
+
tracedOriginalName = bridge.entryPointExportName === symbolName ? undefined : bridge.entryPointExportName;
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
key = `${exportInfo.crossPackageSource.packageName}${exportInfo.crossPackageSource.exportPath === '.' ? '' : exportInfo.crossPackageSource.exportPath.slice(1)}`;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
key = `${exportInfo.crossPackageSource.packageName}${exportInfo.crossPackageSource.exportPath === '.' ? '' : exportInfo.crossPackageSource.exportPath.slice(1)}`;
|
|
566
|
+
}
|
|
543
567
|
if (!crossPackageGroups.has(key)) {
|
|
544
568
|
crossPackageGroups.set(key, []);
|
|
545
569
|
}
|
|
546
570
|
crossPackageGroups.get(key).push({
|
|
547
571
|
symbolName,
|
|
548
|
-
originalName:
|
|
572
|
+
originalName: tracedOriginalName,
|
|
549
573
|
sourceFilePath: exportInfo.path,
|
|
550
574
|
isTypeOnly: exportInfo.isTypeOnly,
|
|
551
|
-
crossPackageSource: exportInfo.crossPackageSource
|
|
575
|
+
crossPackageSource: exportInfo.crossPackageSource,
|
|
576
|
+
barrelBridgeExportPath
|
|
552
577
|
});
|
|
553
578
|
continue;
|
|
554
579
|
}
|
|
555
580
|
|
|
556
581
|
// First try to find an export that directly exposes the source file
|
|
557
|
-
let targetExportPath = findExportForSourceFile({
|
|
582
|
+
let targetExportPath = (_findExportForSourceF = (_findExportForSourceF2 = findExportForSourceFile({
|
|
558
583
|
sourceFilePath: exportInfo.path,
|
|
559
584
|
exportsMap
|
|
560
|
-
});
|
|
585
|
+
})) === null || _findExportForSourceF2 === void 0 ? void 0 : _findExportForSourceF2.exportPath) !== null && _findExportForSourceF !== void 0 ? _findExportForSourceF : null;
|
|
561
586
|
|
|
562
587
|
// If no direct match, check which export can provide this symbol
|
|
563
588
|
// (handles nested barrels where the symbol is re-exported through intermediate files)
|
|
@@ -613,6 +638,59 @@ function escapeRegExp(str) {
|
|
|
613
638
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
614
639
|
}
|
|
615
640
|
|
|
641
|
+
/** Mock object keys that are module interop metadata, not package exports. */
|
|
642
|
+
const ESM_INTEROP_MOCK_KEYS = new Set(['__esModule']);
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* If a preamble line is `const|let actual = jest.requireActual('<barrel>')`, rewrite the specifier to
|
|
646
|
+
* `targetImportPathForThisMock` when every `binding.<prop>` read in this split mock's implementation
|
|
647
|
+
* resolves to that same path via `symbolToNewImportPath`. Otherwise leave unchanged (e.g. mixed paths
|
|
648
|
+
* still need the barrel module).
|
|
649
|
+
*/
|
|
650
|
+
function rewritePreambleLineBarrelRequireActual({
|
|
651
|
+
lineText,
|
|
652
|
+
oldBarrelPath,
|
|
653
|
+
targetImportPathForThisMock,
|
|
654
|
+
mockImplementationTextForGroup,
|
|
655
|
+
symbolToNewImportPath
|
|
656
|
+
}) {
|
|
657
|
+
const requireActualRe = /jest\.requireActual(?:<[^>]*>)?\((['"])([^'"]+)\1\)/;
|
|
658
|
+
const requireMatch = requireActualRe.exec(lineText);
|
|
659
|
+
if (!requireMatch || requireMatch[2] !== oldBarrelPath) {
|
|
660
|
+
return lineText;
|
|
661
|
+
}
|
|
662
|
+
const quote = requireMatch[1];
|
|
663
|
+
const bindingMatch = /^\s*(?:const|let)\s+(\w+)\s*=/m.exec(lineText);
|
|
664
|
+
if (!bindingMatch) {
|
|
665
|
+
return lineText;
|
|
666
|
+
}
|
|
667
|
+
const binding = bindingMatch[1];
|
|
668
|
+
const propAccessRe = new RegExp(`\\b${escapeRegExp(binding)}\\.(\\w+)`, 'g');
|
|
669
|
+
const accessedProps = new Set();
|
|
670
|
+
let propMatch;
|
|
671
|
+
while ((propMatch = propAccessRe.exec(mockImplementationTextForGroup)) !== null) {
|
|
672
|
+
accessedProps.add(propMatch[1]);
|
|
673
|
+
}
|
|
674
|
+
if (accessedProps.size === 0) {
|
|
675
|
+
return lineText;
|
|
676
|
+
}
|
|
677
|
+
const resolvedPaths = [];
|
|
678
|
+
for (const prop of accessedProps) {
|
|
679
|
+
const mapped = symbolToNewImportPath.get(prop);
|
|
680
|
+
if (mapped) {
|
|
681
|
+
resolvedPaths.push(mapped);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (resolvedPaths.length === 0) {
|
|
685
|
+
return lineText;
|
|
686
|
+
}
|
|
687
|
+
const uniquePaths = new Set(resolvedPaths);
|
|
688
|
+
if (uniquePaths.size !== 1 || !uniquePaths.has(targetImportPathForThisMock)) {
|
|
689
|
+
return lineText;
|
|
690
|
+
}
|
|
691
|
+
return lineText.replace(`jest.requireActual(${quote}${oldBarrelPath}${quote})`, `jest.requireActual(${quote}${targetImportPathForThisMock}${quote})`);
|
|
692
|
+
}
|
|
693
|
+
|
|
616
694
|
/**
|
|
617
695
|
* Generate fix text for multiple jest.mock calls
|
|
618
696
|
*/
|
|
@@ -622,7 +700,10 @@ function generateMockFixes({
|
|
|
622
700
|
packageName,
|
|
623
701
|
mockProperties,
|
|
624
702
|
quote,
|
|
625
|
-
preambleStatements
|
|
703
|
+
preambleStatements,
|
|
704
|
+
propagateEsModuleFromOriginalMock,
|
|
705
|
+
oldBarrelImportPath,
|
|
706
|
+
symbolToNewImportPath
|
|
626
707
|
}) {
|
|
627
708
|
const mockCalls = [];
|
|
628
709
|
|
|
@@ -631,8 +712,8 @@ function generateMockFixes({
|
|
|
631
712
|
const propTexts = [];
|
|
632
713
|
propTexts.push(`...jest.requireActual(${quote}${fullImportPath}${quote})`);
|
|
633
714
|
|
|
634
|
-
// Add __esModule: true when mocking default exports
|
|
635
|
-
if (group.hasDefaultExport) {
|
|
715
|
+
// Add __esModule: true when mocking default exports, or when the original mock already used __esModule (apply to every split).
|
|
716
|
+
if (group.hasDefaultExport || propagateEsModuleFromOriginalMock) {
|
|
636
717
|
propTexts.push('__esModule: true');
|
|
637
718
|
}
|
|
638
719
|
for (const propName of group.propertyNames) {
|
|
@@ -669,15 +750,26 @@ function generateMockFixes({
|
|
|
669
750
|
}
|
|
670
751
|
}
|
|
671
752
|
}
|
|
753
|
+
const combinedGroupImplText = group.propertyNames.map(name => {
|
|
754
|
+
var _ref, _group$propertyTexts$, _mockProperties$get;
|
|
755
|
+
return (_ref = (_group$propertyTexts$ = group.propertyTexts.get(name)) !== null && _group$propertyTexts$ !== void 0 ? _group$propertyTexts$ : (_mockProperties$get = mockProperties.get(name)) === null || _mockProperties$get === void 0 ? void 0 : _mockProperties$get.text) !== null && _ref !== void 0 ? _ref : '';
|
|
756
|
+
}).join('\n');
|
|
672
757
|
|
|
673
758
|
// Determine if we need preamble for this group
|
|
674
759
|
const neededPreamble = getNeededPreamble({
|
|
675
760
|
propertyTexts: propTexts,
|
|
676
761
|
allPreamble: preambleStatements
|
|
677
762
|
});
|
|
763
|
+
const rewrittenPreamble = neededPreamble.map(p => rewritePreambleLineBarrelRequireActual({
|
|
764
|
+
lineText: p.text,
|
|
765
|
+
oldBarrelPath: oldBarrelImportPath,
|
|
766
|
+
targetImportPathForThisMock: fullImportPath,
|
|
767
|
+
mockImplementationTextForGroup: combinedGroupImplText,
|
|
768
|
+
symbolToNewImportPath
|
|
769
|
+
}));
|
|
678
770
|
if (neededPreamble.length > 0) {
|
|
679
771
|
// Generate block body arrow function with preamble
|
|
680
|
-
const preambleLines =
|
|
772
|
+
const preambleLines = rewrittenPreamble.map(text => `\t${text}`).join('\n');
|
|
681
773
|
const formattedProps = propTexts.map(p => `\t\t${p},`).join('\n');
|
|
682
774
|
return `jest.mock(${quote}${fullImportPath}${quote}, () => {\n${preambleLines}\n\treturn {\n${formattedProps}\n\t};\n})`;
|
|
683
775
|
} else {
|
|
@@ -808,12 +900,17 @@ const ruleMeta = {
|
|
|
808
900
|
type: 'string'
|
|
809
901
|
},
|
|
810
902
|
description: 'The folder paths (relative to workspace root) containing packages whose imports will be checked and autofixed.'
|
|
903
|
+
},
|
|
904
|
+
preferImportedPackageSubpath: {
|
|
905
|
+
type: 'boolean',
|
|
906
|
+
description: 'Prefer subpaths on the mocked barrel package when they bridge to the dependency.'
|
|
811
907
|
}
|
|
812
908
|
},
|
|
813
909
|
additionalProperties: false
|
|
814
910
|
}],
|
|
815
911
|
messages: {
|
|
816
|
-
barrelEntryMock: "jest.mock('{{path}}') is mocking a barrel file entry point. Split into separate mocks for each source file using package.json exports."
|
|
912
|
+
barrelEntryMock: "jest.mock('{{path}}') is mocking a barrel file entry point. Split into separate mocks for each source file using package.json exports.",
|
|
913
|
+
barrelEntryRequireActual: "jest.requireActual('{{path}}') references a barrel file entry point. Use a specific package.json export path instead."
|
|
817
914
|
}
|
|
818
915
|
};
|
|
819
916
|
|
|
@@ -825,9 +922,10 @@ export function createRule(fs) {
|
|
|
825
922
|
return {
|
|
826
923
|
meta: ruleMeta,
|
|
827
924
|
create(context) {
|
|
828
|
-
var _options$applyToImpor;
|
|
925
|
+
var _options$applyToImpor, _options$preferImport;
|
|
829
926
|
const options = context.options[0] || {};
|
|
830
927
|
const applyToImportsFrom = (_options$applyToImpor = options.applyToImportsFrom) !== null && _options$applyToImpor !== void 0 ? _options$applyToImpor : DEFAULT_TARGET_FOLDERS;
|
|
928
|
+
const preferImportedPackageSubpath = (_options$preferImport = options.preferImportedPackageSubpath) !== null && _options$preferImport !== void 0 ? _options$preferImport : false;
|
|
831
929
|
const workspaceRoot = findWorkspaceRoot({
|
|
832
930
|
startPath: dirname(context.filename),
|
|
833
931
|
fs,
|
|
@@ -836,6 +934,124 @@ export function createRule(fs) {
|
|
|
836
934
|
return {
|
|
837
935
|
CallExpression(rawNode) {
|
|
838
936
|
const node = rawNode;
|
|
937
|
+
|
|
938
|
+
// Handle standalone jest.requireActual() calls that reference barrel entries.
|
|
939
|
+
// e.g. jest.requireActual('@atlaskit/pkg').Foo or const { Foo } = jest.requireActual('@atlaskit/pkg')
|
|
940
|
+
if (isJestRequireActual(node)) {
|
|
941
|
+
const raImportPath = extractImportPath(node);
|
|
942
|
+
if (!raImportPath) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const raContext = resolveJestMockContext({
|
|
946
|
+
importPath: raImportPath,
|
|
947
|
+
workspaceRoot,
|
|
948
|
+
fs,
|
|
949
|
+
applyToImportsFrom
|
|
950
|
+
});
|
|
951
|
+
if (!raContext) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (!isBarrelFile({
|
|
955
|
+
exportMap: raContext.exportMap,
|
|
956
|
+
entryFilePath: raContext.entryFilePath
|
|
957
|
+
})) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// `jest.requireActual('<barrel>')` inside `jest.mock('<barrel>')` is handled by the mock
|
|
962
|
+
// rule's fix (preamble retargeting + `jest.requireActual('barrel').x` rewriting), including
|
|
963
|
+
// `const actual = jest.requireActual('<barrel>')` with no member access on the call.
|
|
964
|
+
// Skip standalone handling here to avoid duplicate diagnostics.
|
|
965
|
+
let ancestor = node.parent;
|
|
966
|
+
while (ancestor) {
|
|
967
|
+
if (ancestor.type === 'CallExpression' && isJestMockCall(ancestor)) {
|
|
968
|
+
const ancestorPath = extractImportPath(ancestor);
|
|
969
|
+
if (ancestorPath) {
|
|
970
|
+
const ancestorCtx = resolveJestMockContext({
|
|
971
|
+
importPath: ancestorPath,
|
|
972
|
+
workspaceRoot,
|
|
973
|
+
fs,
|
|
974
|
+
applyToImportsFrom
|
|
975
|
+
});
|
|
976
|
+
if (ancestorCtx && isBarrelFile({
|
|
977
|
+
exportMap: ancestorCtx.exportMap,
|
|
978
|
+
entryFilePath: ancestorCtx.entryFilePath
|
|
979
|
+
})) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
ancestor = ancestor.parent;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Determine which symbols are accessed from the barrel
|
|
988
|
+
const parent = node.parent;
|
|
989
|
+
const accessedSymbols = [];
|
|
990
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MemberExpression' && parent.property.type === 'Identifier') {
|
|
991
|
+
accessedSymbols.push(parent.property.name);
|
|
992
|
+
} else if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'VariableDeclarator' && parent.id.type === 'ObjectPattern') {
|
|
993
|
+
for (const prop of parent.id.properties) {
|
|
994
|
+
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
995
|
+
accessedSymbols.push(prop.key.name);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (accessedSymbols.length === 0) {
|
|
1000
|
+
context.report({
|
|
1001
|
+
node: node,
|
|
1002
|
+
messageId: 'barrelEntryRequireActual',
|
|
1003
|
+
data: {
|
|
1004
|
+
path: raImportPath
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const {
|
|
1010
|
+
groupedByExport,
|
|
1011
|
+
crossPackageGroups
|
|
1012
|
+
} = traceSymbolsToExports({
|
|
1013
|
+
symbolNames: accessedSymbols,
|
|
1014
|
+
exportMap: raContext.exportMap,
|
|
1015
|
+
exportsMap: raContext.exportsMap,
|
|
1016
|
+
currentExportPath: raContext.currentExportPath,
|
|
1017
|
+
fs,
|
|
1018
|
+
barrelPackageName: raContext.packageName,
|
|
1019
|
+
preferImportedPackageSubpath
|
|
1020
|
+
});
|
|
1021
|
+
let newPath = null;
|
|
1022
|
+
if (groupedByExport.size === 1 && crossPackageGroups.size === 0) {
|
|
1023
|
+
const [exportPath] = groupedByExport.keys();
|
|
1024
|
+
newPath = `${raContext.packageName}${exportPath.slice(1)}`;
|
|
1025
|
+
} else if (crossPackageGroups.size === 1 && groupedByExport.size === 0) {
|
|
1026
|
+
const [cpImportPath] = crossPackageGroups.keys();
|
|
1027
|
+
newPath = cpImportPath;
|
|
1028
|
+
}
|
|
1029
|
+
const sourceCode = context.getSourceCode();
|
|
1030
|
+
if (newPath) {
|
|
1031
|
+
const resolvedNewPath = newPath;
|
|
1032
|
+
context.report({
|
|
1033
|
+
node: node,
|
|
1034
|
+
messageId: 'barrelEntryRequireActual',
|
|
1035
|
+
data: {
|
|
1036
|
+
path: raImportPath
|
|
1037
|
+
},
|
|
1038
|
+
fix(fixer) {
|
|
1039
|
+
const firstArg = node.arguments[0];
|
|
1040
|
+
const quote = sourceCode.getText(firstArg)[0];
|
|
1041
|
+
return fixer.replaceText(firstArg, `${quote}${resolvedNewPath}${quote}`);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
} else {
|
|
1045
|
+
context.report({
|
|
1046
|
+
node: node,
|
|
1047
|
+
messageId: 'barrelEntryRequireActual',
|
|
1048
|
+
data: {
|
|
1049
|
+
path: raImportPath
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
839
1055
|
if (!isJestMockCall(node)) {
|
|
840
1056
|
return;
|
|
841
1057
|
}
|
|
@@ -883,7 +1099,8 @@ export function createRule(fs) {
|
|
|
883
1099
|
if (mockProperties.size === 0) {
|
|
884
1100
|
return;
|
|
885
1101
|
}
|
|
886
|
-
const
|
|
1102
|
+
const originalMockHadEsModule = mockProperties.has('__esModule');
|
|
1103
|
+
const symbolNames = Array.from(mockProperties.keys()).filter(name => !ESM_INTEROP_MOCK_KEYS.has(name));
|
|
887
1104
|
const {
|
|
888
1105
|
groupedByExport,
|
|
889
1106
|
crossPackageGroups,
|
|
@@ -893,7 +1110,9 @@ export function createRule(fs) {
|
|
|
893
1110
|
exportMap: mockContext.exportMap,
|
|
894
1111
|
exportsMap: mockContext.exportsMap,
|
|
895
1112
|
currentExportPath: mockContext.currentExportPath,
|
|
896
|
-
fs
|
|
1113
|
+
fs,
|
|
1114
|
+
barrelPackageName: mockContext.packageName,
|
|
1115
|
+
preferImportedPackageSubpath
|
|
897
1116
|
});
|
|
898
1117
|
|
|
899
1118
|
// If no symbols can be mapped to specific exports or cross-package sources,
|
|
@@ -939,8 +1158,9 @@ export function createRule(fs) {
|
|
|
939
1158
|
|
|
940
1159
|
// Get cross-package source info from the first symbol (all symbols in same group have same source)
|
|
941
1160
|
const crossPackageSource = symbols[0].crossPackageSource;
|
|
1161
|
+
const bridgeExportPath = symbols[0].barrelBridgeExportPath;
|
|
942
1162
|
crossPackageMockGroups.push({
|
|
943
|
-
exportPath: crossPackageSource.exportPath,
|
|
1163
|
+
exportPath: bridgeExportPath !== null && bridgeExportPath !== void 0 ? bridgeExportPath : crossPackageSource.exportPath,
|
|
944
1164
|
importPath,
|
|
945
1165
|
propertyNames: symbols.map(s => s.symbolName),
|
|
946
1166
|
propertyTexts: new Map(symbols.map(s => [s.symbolName, mockProperties.get(s.symbolName).text])),
|
|
@@ -1028,22 +1248,36 @@ export function createRule(fs) {
|
|
|
1028
1248
|
mergedGroups.push(group);
|
|
1029
1249
|
}
|
|
1030
1250
|
}
|
|
1031
|
-
const
|
|
1251
|
+
const symbolToNewImportPath = new Map();
|
|
1252
|
+
for (const group of [...mergedGroups, ...crossPackageMockGroups]) {
|
|
1253
|
+
for (const propName of group.propertyNames) {
|
|
1254
|
+
symbolToNewImportPath.set(propName, group.importPath);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
let fixText = generateMockFixes({
|
|
1032
1258
|
groups: mergedGroups,
|
|
1033
1259
|
crossPackageGroups: crossPackageMockGroups,
|
|
1034
1260
|
packageName: mockContext.packageName,
|
|
1035
1261
|
mockProperties,
|
|
1036
1262
|
quote,
|
|
1037
|
-
preambleStatements
|
|
1263
|
+
preambleStatements,
|
|
1264
|
+
propagateEsModuleFromOriginalMock: originalMockHadEsModule,
|
|
1265
|
+
oldBarrelImportPath: oldImportPath,
|
|
1266
|
+
symbolToNewImportPath
|
|
1038
1267
|
});
|
|
1039
1268
|
|
|
1040
|
-
//
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1269
|
+
// Post-process fixText to update jest.requireActual('barrel').Symbol
|
|
1270
|
+
// references embedded in property texts (e.g. inside jest.fn callbacks)
|
|
1271
|
+
fixText = fixText.replace(/jest\.requireActual(?:<[^>]*>)?\((['"])([^'"]+)\1\)\.(\w+)/g, (match, _q, path, symbol) => {
|
|
1272
|
+
if (path !== oldImportPath) {
|
|
1273
|
+
return match;
|
|
1045
1274
|
}
|
|
1046
|
-
|
|
1275
|
+
const newPath = symbolToNewImportPath.get(symbol);
|
|
1276
|
+
if (newPath && newPath !== path) {
|
|
1277
|
+
return match.replace(path, newPath);
|
|
1278
|
+
}
|
|
1279
|
+
return match;
|
|
1280
|
+
});
|
|
1047
1281
|
|
|
1048
1282
|
// Sort nodes by position
|
|
1049
1283
|
const sortedNodesToRemove = nodesToRemove.sort((a, b) => {
|
|
@@ -1101,6 +1335,34 @@ export function createRule(fs) {
|
|
|
1101
1335
|
fixes.push(fixer.replaceText(requireMockArg, `${quote}${newPath}${quote}`));
|
|
1102
1336
|
}
|
|
1103
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
// Fix jest.requireActual() calls that reference the old barrel path.
|
|
1340
|
+
// Only fix calls OUTSIDE the replaced jest.mock node range
|
|
1341
|
+
// (calls inside it are handled via fixText string replacement below).
|
|
1342
|
+
const replacedRanges = sortedNodesToRemove.map(n => n.range);
|
|
1343
|
+
const requireActualCalls = findJestRequireActualCalls({
|
|
1344
|
+
ast,
|
|
1345
|
+
matchPath: candidatePath => candidatePath === oldImportPath
|
|
1346
|
+
});
|
|
1347
|
+
for (const raNode of requireActualCalls) {
|
|
1348
|
+
const raArg = raNode.arguments[0];
|
|
1349
|
+
if (!raArg || !raNode.range) {
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Skip calls inside any node being replaced (ranges overlap)
|
|
1354
|
+
const insideReplacedNode = replacedRanges.some(([start, end]) => raNode.range[0] >= start && raNode.range[1] <= end);
|
|
1355
|
+
if (insideReplacedNode) {
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
const newPath = resolveNewPathForRequireMock({
|
|
1359
|
+
requireMockNode: raNode,
|
|
1360
|
+
symbolToNewPath: symbolToNewImportPath
|
|
1361
|
+
});
|
|
1362
|
+
if (newPath) {
|
|
1363
|
+
fixes.push(fixer.replaceText(raArg, `${quote}${newPath}${quote}`));
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1104
1366
|
return fixes;
|
|
1105
1367
|
}
|
|
1106
1368
|
});
|
|
@@ -152,6 +152,7 @@ function getImportPathForSourceFile({
|
|
|
152
152
|
var _exportInfo$crossPack;
|
|
153
153
|
const crossPackageName = exportInfo === null || exportInfo === void 0 ? void 0 : (_exportInfo$crossPack = exportInfo.crossPackageSource) === null || _exportInfo$crossPack === void 0 ? void 0 : _exportInfo$crossPack.packageName;
|
|
154
154
|
if (crossPackageName) {
|
|
155
|
+
var _findExportForSourceF;
|
|
155
156
|
const sourcePackageExportsMaps = getSourcePackageExportsMaps(fs);
|
|
156
157
|
let exportsMap = sourcePackageExportsMaps.get(crossPackageName);
|
|
157
158
|
if (!exportsMap) {
|
|
@@ -168,10 +169,10 @@ function getImportPathForSourceFile({
|
|
|
168
169
|
sourcePackageExportsMaps.set(crossPackageName, exportsMap);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
|
-
const targetExportPath = exportsMap ? findExportForSourceFile({
|
|
172
|
+
const targetExportPath = exportsMap ? (_findExportForSourceF = findExportForSourceFile({
|
|
172
173
|
sourceFilePath,
|
|
173
174
|
exportsMap
|
|
174
|
-
}) : null;
|
|
175
|
+
})) === null || _findExportForSourceF === void 0 ? void 0 : _findExportForSourceF.exportPath : null;
|
|
175
176
|
return targetExportPath ? crossPackageName + targetExportPath.slice(1) : crossPackageName;
|
|
176
177
|
}
|
|
177
178
|
return getRelativeImportPath({
|
|
@@ -54,6 +54,7 @@ function getImportPathForSourceFile({
|
|
|
54
54
|
var _exportInfo$crossPack;
|
|
55
55
|
const crossPackageName = exportInfo === null || exportInfo === void 0 ? void 0 : (_exportInfo$crossPack = exportInfo.crossPackageSource) === null || _exportInfo$crossPack === void 0 ? void 0 : _exportInfo$crossPack.packageName;
|
|
56
56
|
if (crossPackageName) {
|
|
57
|
+
var _findExportForSourceF;
|
|
57
58
|
const sourcePackageExportsMaps = getSourcePackageExportsMaps(fs);
|
|
58
59
|
let exportsMap = sourcePackageExportsMaps.get(crossPackageName);
|
|
59
60
|
if (!exportsMap) {
|
|
@@ -70,10 +71,10 @@ function getImportPathForSourceFile({
|
|
|
70
71
|
sourcePackageExportsMaps.set(crossPackageName, exportsMap);
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
|
-
const targetExportPath = exportsMap ? findExportForSourceFile({
|
|
74
|
+
const targetExportPath = exportsMap ? (_findExportForSourceF = findExportForSourceFile({
|
|
74
75
|
sourceFilePath,
|
|
75
76
|
exportsMap
|
|
76
|
-
}) : null;
|
|
77
|
+
})) === null || _findExportForSourceF === void 0 ? void 0 : _findExportForSourceF.exportPath : null;
|
|
77
78
|
return targetExportPath ? crossPackageName + targetExportPath.slice(1) : crossPackageName;
|
|
78
79
|
}
|
|
79
80
|
return getRelativeImportPath({
|
|
@@ -437,10 +438,13 @@ function transformExportSpecifiers({
|
|
|
437
438
|
}) {
|
|
438
439
|
return specsWithOriginal.map(({
|
|
439
440
|
originalName,
|
|
441
|
+
nameInSource,
|
|
440
442
|
nameInLocal,
|
|
441
443
|
kind
|
|
442
444
|
}) => ({
|
|
443
|
-
|
|
445
|
+
// Use originalName if available (means there was an alias in the barrel),
|
|
446
|
+
// otherwise use nameInSource (the direct export name from the barrel)
|
|
447
|
+
nameInSource: originalName !== null && originalName !== void 0 ? originalName : nameInSource,
|
|
444
448
|
nameInLocal,
|
|
445
449
|
kind
|
|
446
450
|
}));
|
|
@@ -112,6 +112,50 @@ export function findJestRequireMockCalls({
|
|
|
112
112
|
return results;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Find all jest.requireActual() calls in the AST whose import path matches a given target.
|
|
117
|
+
* Works identically to findJestRequireMockCalls but for requireActual.
|
|
118
|
+
*/
|
|
119
|
+
export function findJestRequireActualCalls({
|
|
120
|
+
ast,
|
|
121
|
+
matchPath
|
|
122
|
+
}) {
|
|
123
|
+
const results = [];
|
|
124
|
+
const visited = new WeakSet();
|
|
125
|
+
const skipKeys = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);
|
|
126
|
+
function visit(node) {
|
|
127
|
+
if (visited.has(node)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
visited.add(node);
|
|
131
|
+
if (node.type === 'CallExpression' && isJestRequireActual(node)) {
|
|
132
|
+
const path = extractImportPath(node);
|
|
133
|
+
if (path && matchPath(path)) {
|
|
134
|
+
results.push(node);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const key in node) {
|
|
138
|
+
if (skipKeys.has(key)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const value = node[key];
|
|
142
|
+
if (value && typeof value === 'object') {
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
for (const child of value) {
|
|
145
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
146
|
+
visit(child);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else if ('type' in value) {
|
|
150
|
+
visit(value);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
visit(ast);
|
|
156
|
+
return results;
|
|
157
|
+
}
|
|
158
|
+
|
|
115
159
|
/**
|
|
116
160
|
* Determine the best new import path for a jest.requireMock() call by inspecting
|
|
117
161
|
* the destructured symbols or property access at the call site.
|