@atlaskit/eslint-plugin-platform 2.8.0 → 2.9.0

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 (39) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/index.js +6 -1
  3. package/dist/cjs/rules/ensure-use-sync-external-store-server-snapshot/index.js +41 -0
  4. package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +475 -67
  5. package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +387 -112
  6. package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  7. package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  8. package/dist/cjs/rules/import/shared/jest-utils.js +62 -9
  9. package/dist/cjs/rules/import/shared/package-resolution.js +156 -23
  10. package/dist/cjs/rules/visit-example-type-import-required/index.js +409 -0
  11. package/dist/es2019/index.js +6 -1
  12. package/dist/es2019/rules/ensure-use-sync-external-store-server-snapshot/index.js +43 -0
  13. package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +372 -15
  14. package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +245 -17
  15. package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  16. package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  17. package/dist/es2019/rules/import/shared/jest-utils.js +44 -0
  18. package/dist/es2019/rules/import/shared/package-resolution.js +97 -5
  19. package/dist/es2019/rules/visit-example-type-import-required/index.js +375 -0
  20. package/dist/esm/index.js +6 -1
  21. package/dist/esm/rules/ensure-use-sync-external-store-server-snapshot/index.js +35 -0
  22. package/dist/esm/rules/import/no-barrel-entry-imports/index.js +475 -67
  23. package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +388 -113
  24. package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  25. package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  26. package/dist/esm/rules/import/shared/jest-utils.js +61 -9
  27. package/dist/esm/rules/import/shared/package-resolution.js +156 -25
  28. package/dist/esm/rules/visit-example-type-import-required/index.js +402 -0
  29. package/dist/types/index.d.ts +12 -0
  30. package/dist/types/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  31. package/dist/types/rules/import/shared/jest-utils.d.ts +8 -0
  32. package/dist/types/rules/import/shared/package-resolution.d.ts +22 -2
  33. package/dist/types/rules/visit-example-type-import-required/index.d.ts +4 -0
  34. package/dist/types-ts4.5/index.d.ts +12 -0
  35. package/dist/types-ts4.5/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  36. package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +8 -0
  37. package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +22 -2
  38. package/dist/types-ts4.5/rules/visit-example-type-import-required/index.d.ts +4 -0
  39. package/package.json +3 -1
@@ -110,20 +110,15 @@ function buildImportStatement({
110
110
  */
111
111
 
112
112
  /**
113
- * Resolves import context for barrel file analysis.
113
+ * Resolves import context for barrel file analysis from a module specifier string.
114
114
  * Returns null if the import should not be processed (relative import, not in target folder, etc.)
115
115
  */
116
- function resolveImportContext({
117
- node,
116
+ function resolveImportContextFromModulePath({
117
+ importPath,
118
118
  workspaceRoot,
119
119
  fs,
120
120
  applyToImportsFrom
121
121
  }) {
122
- if (!node.source || typeof node.source.value !== 'string') {
123
- return null;
124
- }
125
- const importPath = node.source.value;
126
-
127
122
  // Skip relative imports - this rule is for cross-package imports
128
123
  if (isRelativeImport(importPath)) {
129
124
  return null;
@@ -197,6 +192,27 @@ function resolveImportContext({
197
192
  };
198
193
  }
199
194
 
195
+ /**
196
+ * Resolves import context for barrel file analysis.
197
+ * Returns null if the import should not be processed (relative import, not in target folder, etc.)
198
+ */
199
+ function resolveImportContext({
200
+ node,
201
+ workspaceRoot,
202
+ fs,
203
+ applyToImportsFrom
204
+ }) {
205
+ if (!node.source || typeof node.source.value !== 'string') {
206
+ return null;
207
+ }
208
+ return resolveImportContextFromModulePath({
209
+ importPath: node.source.value,
210
+ workspaceRoot,
211
+ fs,
212
+ applyToImportsFrom
213
+ });
214
+ }
215
+
200
216
  /**
201
217
  * Classifies import specifiers by their target export paths.
202
218
  * Groups specifiers that can be remapped to more specific exports.
@@ -238,7 +254,7 @@ function classifySpecifiers({
238
254
  }
239
255
  const exportInfo = exportMap.get(nameInSource);
240
256
  if (exportInfo) {
241
- var _exportInfo$crossPack;
257
+ var _exportInfo$crossPack, _exportInfo$originalN2, _matchResult$exportPa2;
242
258
  const effectiveKind = kind === 'type' || exportInfo.isTypeOnly ? 'type' : 'value';
243
259
 
244
260
  // Check if this is a cross-package re-export
@@ -264,11 +280,20 @@ function classifySpecifiers({
264
280
 
265
281
  // Find the best export path in the source package
266
282
  let targetExportPath = null;
283
+ let resolvedOriginalName = exportInfo.originalName;
267
284
  if (sourcePackageExportsMap) {
268
- targetExportPath = findExportForSourceFile({
285
+ var _exportInfo$originalN, _matchResult$exportPa;
286
+ const sourceExportName = (_exportInfo$originalN = exportInfo.originalName) !== null && _exportInfo$originalN !== void 0 ? _exportInfo$originalN : nameInSource;
287
+ const matchResult = findExportForSourceFile({
269
288
  sourceFilePath: exportInfo.path,
270
- exportsMap: sourcePackageExportsMap
289
+ exportsMap: sourcePackageExportsMap,
290
+ fs,
291
+ sourceExportName
271
292
  });
293
+ targetExportPath = (_matchResult$exportPa = matchResult === null || matchResult === void 0 ? void 0 : matchResult.exportPath) !== null && _matchResult$exportPa !== void 0 ? _matchResult$exportPa : null;
294
+ if ((matchResult === null || matchResult === void 0 ? void 0 : matchResult.entryPointExportName) !== undefined) {
295
+ resolvedOriginalName = matchResult.entryPointExportName === nameInSource ? undefined : matchResult.entryPointExportName;
296
+ }
272
297
  }
273
298
 
274
299
  // Build the full import path: @package/subpath or just @package if no subpath found
@@ -282,7 +307,7 @@ function classifySpecifiers({
282
307
  ...spec,
283
308
  importKind: effectiveKind
284
309
  },
285
- originalName: exportInfo.originalName,
310
+ originalName: resolvedOriginalName,
286
311
  targetExportPath: targetKey,
287
312
  kind: effectiveKind,
288
313
  sourcePackageName
@@ -291,10 +316,18 @@ function classifySpecifiers({
291
316
  }
292
317
 
293
318
  // Find if there's a package.json export that points to this source file
294
- const targetExportPath = findExportForSourceFile({
319
+ const sourceExportName = (_exportInfo$originalN2 = exportInfo.originalName) !== null && _exportInfo$originalN2 !== void 0 ? _exportInfo$originalN2 : nameInSource;
320
+ const matchResult = findExportForSourceFile({
295
321
  sourceFilePath: exportInfo.path,
296
- exportsMap
322
+ exportsMap,
323
+ fs,
324
+ sourceExportName
297
325
  });
326
+ const targetExportPath = (_matchResult$exportPa2 = matchResult === null || matchResult === void 0 ? void 0 : matchResult.exportPath) !== null && _matchResult$exportPa2 !== void 0 ? _matchResult$exportPa2 : null;
327
+ let resolvedOriginalName2 = exportInfo.originalName;
328
+ if ((matchResult === null || matchResult === void 0 ? void 0 : matchResult.entryPointExportName) !== undefined) {
329
+ resolvedOriginalName2 = matchResult.entryPointExportName === nameInSource ? undefined : matchResult.entryPointExportName;
330
+ }
298
331
 
299
332
  // Get the file that the current export path resolves to
300
333
  const currentExportResolvedFile = exportsMap.get(currentExportPath);
@@ -314,7 +347,7 @@ function classifySpecifiers({
314
347
  ...spec,
315
348
  importKind: effectiveKind
316
349
  },
317
- originalName: exportInfo.originalName,
350
+ originalName: resolvedOriginalName2,
318
351
  targetExportPath,
319
352
  kind: effectiveKind
320
353
  });
@@ -686,6 +719,312 @@ function createBarrelImportFix({
686
719
  }
687
720
  return fixes;
688
721
  }
722
+ function isPlainRequireCall(node) {
723
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'require') {
724
+ return false;
725
+ }
726
+ if (node.arguments.length !== 1) {
727
+ return false;
728
+ }
729
+ const arg = node.arguments[0];
730
+ return arg.type === 'Literal' && typeof arg.value === 'string';
731
+ }
732
+ function unwrapToRequireCall(expr) {
733
+ let e = expr;
734
+ for (;;) {
735
+ const wrapped = e;
736
+ if (wrapped.type !== 'ParenthesizedExpression' || !wrapped.expression) {
737
+ break;
738
+ }
739
+ e = wrapped.expression;
740
+ }
741
+ if (e.type !== 'CallExpression' || !isPlainRequireCall(e)) {
742
+ return null;
743
+ }
744
+ return e;
745
+ }
746
+ function buildSyntheticImportFromRequireAccess(exportPropertyName, modulePath) {
747
+ const specifiers = exportPropertyName === 'default' ? [{
748
+ type: 'ImportDefaultSpecifier',
749
+ local: {
750
+ type: 'Identifier',
751
+ name: '_r'
752
+ }
753
+ }] : [{
754
+ type: 'ImportSpecifier',
755
+ imported: {
756
+ type: 'Identifier',
757
+ name: exportPropertyName
758
+ },
759
+ local: {
760
+ type: 'Identifier',
761
+ name: exportPropertyName
762
+ }
763
+ }];
764
+ return {
765
+ type: 'ImportDeclaration',
766
+ source: {
767
+ type: 'Literal',
768
+ value: modulePath,
769
+ raw: `'${modulePath}'`
770
+ },
771
+ specifiers,
772
+ importKind: 'value'
773
+ };
774
+ }
775
+ function fullNewImportPathForTarget(targetKey, specsWithTarget, packageName) {
776
+ const isCrossPackage = specsWithTarget.some(s => s.sourcePackageName);
777
+ return isCrossPackage ? targetKey : packageName + targetKey.slice(1);
778
+ }
779
+ function getRhsPropertyAfterTransform(spec) {
780
+ if (spec.type === 'ImportDefaultSpecifier') {
781
+ return 'default';
782
+ }
783
+ return getImportedName(spec);
784
+ }
785
+ function appendAutomockFixesForPathMigration({
786
+ fixer,
787
+ sourceCode,
788
+ oldBarrelPath,
789
+ newPaths
790
+ }) {
791
+ const automocks = findMatchingAutomocks({
792
+ sourceCode,
793
+ importPath: oldBarrelPath
794
+ });
795
+ if (automocks.length === 0 || newPaths.length === 0) {
796
+ return [];
797
+ }
798
+ const fixes = [];
799
+ for (const automock of automocks) {
800
+ const newAutomockStatements = newPaths.map(path => buildAutomockStatement({
801
+ path,
802
+ quoteChar: automock.quoteChar
803
+ }));
804
+ fixes.push(fixer.replaceTextRange(automock.statementNode.range, newAutomockStatements.join('\n')));
805
+ }
806
+ return fixes;
807
+ }
808
+
809
+ /**
810
+ * `require('barrel').default` or `require('barrel').namedExport`
811
+ */
812
+ function handleRequireMemberExpression({
813
+ node,
814
+ context,
815
+ workspaceRoot,
816
+ fs,
817
+ applyToImportsFrom
818
+ }) {
819
+ if (node.computed || node.property.type !== 'Identifier') {
820
+ return;
821
+ }
822
+ const reqCall = unwrapToRequireCall(node.object);
823
+ if (!reqCall) {
824
+ return;
825
+ }
826
+ const modulePath = reqCall.arguments[0].value;
827
+ const importContext = resolveImportContextFromModulePath({
828
+ importPath: modulePath,
829
+ workspaceRoot,
830
+ fs,
831
+ applyToImportsFrom
832
+ });
833
+ if (!importContext) {
834
+ return;
835
+ }
836
+ const exportPropertyName = node.property.name;
837
+ const synthetic = buildSyntheticImportFromRequireAccess(exportPropertyName, modulePath);
838
+ const {
839
+ specifiersByTarget,
840
+ hasNamespaceImport
841
+ } = classifySpecifiers({
842
+ node: synthetic,
843
+ importContext,
844
+ workspaceRoot,
845
+ fs
846
+ });
847
+ if (hasNamespaceImport || specifiersByTarget.size === 0) {
848
+ return;
849
+ }
850
+ const entries = [...specifiersByTarget.entries()];
851
+ if (entries.length !== 1) {
852
+ return;
853
+ }
854
+ const [targetKey, specsWithTarget] = entries[0];
855
+ if (specsWithTarget.length !== 1) {
856
+ return;
857
+ }
858
+ const st = specsWithTarget[0];
859
+ const newImportPath = fullNewImportPathForTarget(targetKey, specsWithTarget, importContext.packageName);
860
+ const transformed = transformSpecifierForExport({
861
+ spec: st.spec,
862
+ originalName: st.originalName,
863
+ kind: st.kind
864
+ });
865
+ const newRhs = getRhsPropertyAfterTransform(transformed);
866
+ const sourceCode = context.getSourceCode();
867
+ const quote = sourceCode.getText(reqCall.arguments[0])[0];
868
+ context.report({
869
+ node: node,
870
+ messageId: 'barrelEntryImport',
871
+ data: {
872
+ path: importContext.importPath
873
+ },
874
+ fix(fixer) {
875
+ const fixes = [];
876
+ fixes.push(fixer.replaceText(node, `require(${quote}${newImportPath}${quote}).${newRhs}`));
877
+ if (st.kind === 'value') {
878
+ fixes.push(...appendAutomockFixesForPathMigration({
879
+ fixer,
880
+ sourceCode,
881
+ oldBarrelPath: modulePath,
882
+ newPaths: [newImportPath]
883
+ }));
884
+ }
885
+ return fixes;
886
+ }
887
+ });
888
+ }
889
+
890
+ /**
891
+ * `const { a, b } = require('barrel')`
892
+ */
893
+ function handleRequireDestructuringDeclarator({
894
+ node,
895
+ context,
896
+ workspaceRoot,
897
+ fs,
898
+ applyToImportsFrom
899
+ }) {
900
+ if (node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'CallExpression') {
901
+ return;
902
+ }
903
+ const initCall = node.init;
904
+ if (!isPlainRequireCall(initCall)) {
905
+ return;
906
+ }
907
+ const modulePath = initCall.arguments[0].value;
908
+ const importContext = resolveImportContextFromModulePath({
909
+ importPath: modulePath,
910
+ workspaceRoot,
911
+ fs,
912
+ applyToImportsFrom
913
+ });
914
+ if (!importContext) {
915
+ return;
916
+ }
917
+ const specifiers = [];
918
+ for (const prop of node.id.properties) {
919
+ if (prop.type !== 'Property' || prop.computed) {
920
+ continue;
921
+ }
922
+ if (prop.key.type !== 'Identifier' || prop.value.type !== 'Identifier') {
923
+ continue;
924
+ }
925
+ const importedName = prop.key.name;
926
+ const localName = prop.value.name;
927
+ specifiers.push({
928
+ type: 'ImportSpecifier',
929
+ imported: {
930
+ type: 'Identifier',
931
+ name: importedName
932
+ },
933
+ local: {
934
+ type: 'Identifier',
935
+ name: localName
936
+ }
937
+ });
938
+ }
939
+ if (specifiers.length === 0) {
940
+ return;
941
+ }
942
+ const synthetic = {
943
+ type: 'ImportDeclaration',
944
+ source: {
945
+ type: 'Literal',
946
+ value: modulePath,
947
+ raw: `'${modulePath}'`
948
+ },
949
+ specifiers,
950
+ importKind: 'value'
951
+ };
952
+ const {
953
+ specifiersByTarget,
954
+ unmappedSpecifiers,
955
+ hasNamespaceImport
956
+ } = classifySpecifiers({
957
+ node: synthetic,
958
+ importContext,
959
+ workspaceRoot,
960
+ fs
961
+ });
962
+ if (hasNamespaceImport || specifiersByTarget.size === 0 || unmappedSpecifiers.length > 0) {
963
+ return;
964
+ }
965
+ const parentDecl = node.parent;
966
+ if (parentDecl.type !== 'VariableDeclaration') {
967
+ return;
968
+ }
969
+ if (specifiersByTarget.size > 1 && parentDecl.declarations.length !== 1) {
970
+ return;
971
+ }
972
+ const sourceCode = context.getSourceCode();
973
+ const quote = sourceCode.getText(initCall.arguments[0])[0];
974
+ const pkg = importContext.packageName;
975
+ const buildFixes = fixer => {
976
+ const fixes = [];
977
+ let hasValue = false;
978
+ const automockPaths = [];
979
+ if (specifiersByTarget.size === 1) {
980
+ const [targetKey, specsWithTarget] = [...specifiersByTarget.entries()][0];
981
+ const newImportPath = fullNewImportPathForTarget(targetKey, specsWithTarget, pkg);
982
+ if (specsWithTarget.some(s => s.kind === 'value')) {
983
+ hasValue = true;
984
+ automockPaths.push(newImportPath);
985
+ }
986
+ fixes.push(fixer.replaceText(initCall.arguments[0], `${quote}${newImportPath}${quote}`));
987
+ } else {
988
+ const lines = [];
989
+ for (const [targetKey, specsWithTarget] of specifiersByTarget) {
990
+ const newImportPath = fullNewImportPathForTarget(targetKey, specsWithTarget, pkg);
991
+ if (specsWithTarget.some(s => s.kind === 'value')) {
992
+ hasValue = true;
993
+ automockPaths.push(newImportPath);
994
+ }
995
+ for (const st of specsWithTarget) {
996
+ const transformed = transformSpecifierForExport({
997
+ spec: st.spec,
998
+ originalName: st.originalName,
999
+ kind: st.kind
1000
+ });
1001
+ const rhs = getRhsPropertyAfterTransform(transformed);
1002
+ const local = st.spec.local.name;
1003
+ lines.push(`${local} = require(${quote}${newImportPath}${quote}).${rhs}`);
1004
+ }
1005
+ }
1006
+ const declText = lines.map(l => `${parentDecl.kind} ${l};`).join('\n');
1007
+ fixes.push(fixer.replaceText(parentDecl, declText));
1008
+ }
1009
+ if (hasValue) {
1010
+ fixes.push(...appendAutomockFixesForPathMigration({
1011
+ fixer,
1012
+ sourceCode,
1013
+ oldBarrelPath: modulePath,
1014
+ newPaths: [...new Set(automockPaths)]
1015
+ }));
1016
+ }
1017
+ return fixes;
1018
+ };
1019
+ context.report({
1020
+ node: initCall,
1021
+ messageId: 'barrelEntryImport',
1022
+ data: {
1023
+ path: importContext.importPath
1024
+ },
1025
+ fix: buildFixes
1026
+ });
1027
+ }
689
1028
 
690
1029
  /**
691
1030
  * Handles an ImportDeclaration node to check for barrel file imports.
@@ -792,6 +1131,24 @@ export function createRule(fs) {
792
1131
  fs,
793
1132
  applyToImportsFrom
794
1133
  });
1134
+ },
1135
+ MemberExpression(rawNode) {
1136
+ handleRequireMemberExpression({
1137
+ node: rawNode,
1138
+ context,
1139
+ workspaceRoot,
1140
+ fs,
1141
+ applyToImportsFrom
1142
+ });
1143
+ },
1144
+ VariableDeclarator(rawNode) {
1145
+ handleRequireDestructuringDeclarator({
1146
+ node: rawNode,
1147
+ context,
1148
+ workspaceRoot,
1149
+ fs,
1150
+ applyToImportsFrom
1151
+ });
795
1152
  }
796
1153
  };
797
1154
  }