@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/index.js +8 -1
  3. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  4. package/dist/cjs/rules/ensure-use-sync-external-store-server-snapshot/index.js +41 -0
  5. package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +534 -74
  6. package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +428 -119
  7. package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  8. package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  9. package/dist/cjs/rules/import/shared/jest-utils.js +62 -9
  10. package/dist/cjs/rules/import/shared/package-resolution.js +300 -22
  11. package/dist/cjs/rules/no-restricted-fedramp-imports/index.js +65 -0
  12. package/dist/cjs/rules/visit-example-type-import-required/index.js +409 -0
  13. package/dist/es2019/index.js +8 -1
  14. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  15. package/dist/es2019/rules/ensure-use-sync-external-store-server-snapshot/index.js +43 -0
  16. package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +431 -25
  17. package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +287 -25
  18. package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  19. package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  20. package/dist/es2019/rules/import/shared/jest-utils.js +44 -0
  21. package/dist/es2019/rules/import/shared/package-resolution.js +211 -4
  22. package/dist/es2019/rules/no-restricted-fedramp-imports/index.js +47 -0
  23. package/dist/es2019/rules/visit-example-type-import-required/index.js +375 -0
  24. package/dist/esm/index.js +8 -1
  25. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  26. package/dist/esm/rules/ensure-use-sync-external-store-server-snapshot/index.js +35 -0
  27. package/dist/esm/rules/import/no-barrel-entry-imports/index.js +535 -75
  28. package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +430 -121
  29. package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  30. package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  31. package/dist/esm/rules/import/shared/jest-utils.js +61 -9
  32. package/dist/esm/rules/import/shared/package-resolution.js +298 -24
  33. package/dist/esm/rules/no-restricted-fedramp-imports/index.js +59 -0
  34. package/dist/esm/rules/visit-example-type-import-required/index.js +402 -0
  35. package/dist/types/index.d.ts +14 -0
  36. package/dist/types/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  37. package/dist/types/rules/import/shared/jest-utils.d.ts +8 -0
  38. package/dist/types/rules/import/shared/package-resolution.d.ts +47 -2
  39. package/dist/types/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
  40. package/dist/types/rules/visit-example-type-import-required/index.d.ts +4 -0
  41. package/dist/types-ts4.5/index.d.ts +14 -0
  42. package/dist/types-ts4.5/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  43. package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +8 -0
  44. package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +47 -2
  45. package/dist/types-ts4.5/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
  46. package/dist/types-ts4.5/rules/visit-example-type-import-required/index.d.ts +4 -0
  47. 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
- const key = `${exportInfo.crossPackageSource.packageName}${exportInfo.crossPackageSource.exportPath === '.' ? '' : exportInfo.crossPackageSource.exportPath.slice(1)}`;
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: exportInfo.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 = neededPreamble.map(p => `\t${p.text}`).join('\n');
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 symbolNames = Array.from(mockProperties.keys());
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 fixText = generateMockFixes({
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
- // Build a map of symbol name -> new import path for jest.requireMock() rewriting
1041
- const symbolToNewImportPath = new Map();
1042
- for (const group of [...mergedGroups, ...crossPackageMockGroups]) {
1043
- for (const propName of group.propertyNames) {
1044
- symbolToNewImportPath.set(propName, group.importPath);
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
- nameInSource: originalName || 'default',
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.