@atlaskit/eslint-plugin-platform 2.7.2 → 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 (85) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/index.js +19 -3
  3. package/dist/cjs/rules/ensure-use-sync-external-store-server-snapshot/index.js +41 -0
  4. package/dist/cjs/rules/feature-gating/valid-gate-name/index.js +60 -0
  5. package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +1279 -0
  6. package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +1659 -0
  7. package/dist/cjs/rules/import/no-conversation-assistant-barrel-imports/index.js +43 -0
  8. package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +1402 -0
  9. package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +781 -0
  10. package/dist/cjs/rules/import/shared/barrel-parsing.js +511 -0
  11. package/dist/cjs/rules/import/shared/file-system.js +186 -0
  12. package/dist/cjs/rules/import/shared/jest-utils.js +244 -0
  13. package/dist/cjs/rules/import/shared/package-registry.js +263 -0
  14. package/dist/cjs/rules/import/shared/package-resolution.js +318 -0
  15. package/dist/cjs/rules/import/shared/perf.js +89 -0
  16. package/dist/cjs/rules/import/shared/types.js +67 -0
  17. package/dist/cjs/rules/visit-example-type-import-required/index.js +409 -0
  18. package/dist/es2019/index.js +19 -3
  19. package/dist/es2019/rules/ensure-use-sync-external-store-server-snapshot/index.js +43 -0
  20. package/dist/es2019/rules/feature-gating/valid-gate-name/index.js +52 -0
  21. package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +1158 -0
  22. package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +1341 -0
  23. package/dist/es2019/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
  24. package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +1180 -0
  25. package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +742 -0
  26. package/dist/es2019/rules/import/shared/barrel-parsing.js +433 -0
  27. package/dist/es2019/rules/import/shared/file-system.js +174 -0
  28. package/dist/es2019/rules/import/shared/jest-utils.js +203 -0
  29. package/dist/es2019/rules/import/shared/package-registry.js +240 -0
  30. package/dist/es2019/rules/import/shared/package-resolution.js +253 -0
  31. package/dist/es2019/rules/import/shared/perf.js +83 -0
  32. package/dist/es2019/rules/import/shared/types.js +57 -0
  33. package/dist/es2019/rules/visit-example-type-import-required/index.js +375 -0
  34. package/dist/esm/index.js +19 -3
  35. package/dist/esm/rules/ensure-use-sync-external-store-server-snapshot/index.js +35 -0
  36. package/dist/esm/rules/feature-gating/valid-gate-name/index.js +53 -0
  37. package/dist/esm/rules/import/no-barrel-entry-imports/index.js +1272 -0
  38. package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +1650 -0
  39. package/dist/esm/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
  40. package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +1392 -0
  41. package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +774 -0
  42. package/dist/esm/rules/import/shared/barrel-parsing.js +500 -0
  43. package/dist/esm/rules/import/shared/file-system.js +176 -0
  44. package/dist/esm/rules/import/shared/jest-utils.js +231 -0
  45. package/dist/esm/rules/import/shared/package-registry.js +256 -0
  46. package/dist/esm/rules/import/shared/package-resolution.js +306 -0
  47. package/dist/esm/rules/import/shared/perf.js +80 -0
  48. package/dist/esm/rules/import/shared/types.js +61 -0
  49. package/dist/esm/rules/visit-example-type-import-required/index.js +402 -0
  50. package/dist/types/index.d.ts +28 -2
  51. package/dist/types/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  52. package/dist/types/rules/feature-gating/valid-gate-name/index.d.ts +3 -0
  53. package/dist/types/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
  54. package/dist/types/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
  55. package/dist/types/rules/import/no-conversation-assistant-barrel-imports/index.d.ts +3 -0
  56. package/dist/types/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
  57. package/dist/types/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
  58. package/dist/types/rules/import/shared/barrel-parsing.d.ts +30 -0
  59. package/dist/types/rules/import/shared/file-system.d.ts +38 -0
  60. package/dist/types/rules/import/shared/jest-utils.d.ts +55 -0
  61. package/dist/types/rules/import/shared/package-registry.d.ts +26 -0
  62. package/dist/types/rules/import/shared/package-resolution.d.ts +58 -0
  63. package/dist/types/rules/import/shared/perf.d.ts +13 -0
  64. package/dist/types/rules/import/shared/types.d.ts +131 -0
  65. package/dist/types/rules/visit-example-type-import-required/index.d.ts +4 -0
  66. package/dist/types-ts4.5/index.d.ts +28 -2
  67. package/dist/types-ts4.5/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
  68. package/dist/types-ts4.5/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
  69. package/dist/types-ts4.5/rules/import/no-conversation-assistant-barrel-imports/index.d.ts +3 -0
  70. package/dist/types-ts4.5/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
  71. package/dist/types-ts4.5/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
  72. package/dist/types-ts4.5/rules/import/shared/barrel-parsing.d.ts +30 -0
  73. package/dist/types-ts4.5/rules/import/shared/file-system.d.ts +38 -0
  74. package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +55 -0
  75. package/dist/types-ts4.5/rules/import/shared/package-registry.d.ts +26 -0
  76. package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +58 -0
  77. package/dist/types-ts4.5/rules/import/shared/perf.d.ts +13 -0
  78. package/dist/types-ts4.5/rules/import/shared/types.d.ts +131 -0
  79. package/dist/types-ts4.5/rules/visit-example-type-import-required/index.d.ts +4 -0
  80. package/package.json +6 -2
  81. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -158
  82. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -146
  83. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -151
  84. /package/dist/types-ts4.5/rules/{ensure-native-and-af-exports-synced → ensure-use-sync-external-store-server-snapshot}/index.d.ts +0 -0
  85. /package/dist/{types/rules/ensure-native-and-af-exports-synced → types-ts4.5/rules/feature-gating/valid-gate-name}/index.d.ts +0 -0
@@ -0,0 +1,742 @@
1
+ import { dirname, relative } from 'path';
2
+ import { hasReExportsFromOtherFiles, parseBarrelExports as parseBarrelExportsFromShared } from '../shared/barrel-parsing';
3
+ import { findWorkspaceRoot, isRelativeImport, resolveImportPath } from '../shared/file-system';
4
+ import { findPackageInRegistry } from '../shared/package-registry';
5
+ import { findExportForSourceFile, parsePackageExports } from '../shared/package-resolution';
6
+ import { realFileSystem } from '../shared/types';
7
+
8
+ /**
9
+ * Specifier with additional metadata for tracking during barrel file resolution.
10
+ */
11
+
12
+ /**
13
+ * Context for barrel file resolution, returned by resolveBarrelFileContext.
14
+ */
15
+
16
+ /**
17
+ * Result of collecting specifiers by their source file.
18
+ */
19
+
20
+ // Cache per source package name to avoid repeated exports parsing during a single lint run.
21
+ // This is keyed by fs instance to avoid test pollution.
22
+ const sourcePackageExportsMapsByFs = new WeakMap();
23
+ function getSourcePackageExportsMaps(fs) {
24
+ let map = sourcePackageExportsMapsByFs.get(fs);
25
+ if (!map) {
26
+ map = new Map();
27
+ sourcePackageExportsMapsByFs.set(fs, map);
28
+ }
29
+ return map;
30
+ }
31
+ function getImportedName(spec) {
32
+ const imported = spec.imported;
33
+ return imported.type === 'Identifier' ? imported.name : String(imported.value);
34
+ }
35
+
36
+ /**
37
+ * Get the exported name from an ExportSpecifier, handling both Identifier and Literal
38
+ */
39
+ function getExportedName(node) {
40
+ return node.type === 'Identifier' ? node.name : String(node.value);
41
+ }
42
+
43
+ /**
44
+ * Convert absolute file path back to relative import path
45
+ */
46
+ function getImportPathForSourceFile({
47
+ sourceFilePath,
48
+ basedir,
49
+ originalImportPath,
50
+ exportInfo,
51
+ workspaceRoot,
52
+ fs
53
+ }) {
54
+ var _exportInfo$crossPack;
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
+ if (crossPackageName) {
57
+ var _findExportForSourceF;
58
+ const sourcePackageExportsMaps = getSourcePackageExportsMaps(fs);
59
+ let exportsMap = sourcePackageExportsMaps.get(crossPackageName);
60
+ if (!exportsMap) {
61
+ const pkgDir = findPackageInRegistry({
62
+ packageName: crossPackageName,
63
+ workspaceRoot,
64
+ fs
65
+ });
66
+ if (pkgDir) {
67
+ exportsMap = parsePackageExports({
68
+ packageDir: pkgDir,
69
+ fs
70
+ });
71
+ sourcePackageExportsMaps.set(crossPackageName, exportsMap);
72
+ }
73
+ }
74
+ const targetExportPath = exportsMap ? (_findExportForSourceF = findExportForSourceFile({
75
+ sourceFilePath,
76
+ exportsMap
77
+ })) === null || _findExportForSourceF === void 0 ? void 0 : _findExportForSourceF.exportPath : null;
78
+ return targetExportPath ? crossPackageName + targetExportPath.slice(1) : crossPackageName;
79
+ }
80
+ return getRelativeImportPath({
81
+ basedir,
82
+ absolutePath: sourceFilePath,
83
+ originalImportPath
84
+ });
85
+ }
86
+ function getRelativeImportPath({
87
+ basedir,
88
+ absolutePath,
89
+ originalImportPath
90
+ }) {
91
+ if (!absolutePath.startsWith('/') && !absolutePath.match(/^[a-zA-Z]:/)) {
92
+ return absolutePath;
93
+ }
94
+ let relativePath = relative(basedir, absolutePath);
95
+ // Normalize to use forward slashes
96
+ relativePath = relativePath.replace(/\\/g, '/');
97
+
98
+ // Check for extension in original path
99
+ const extMatch = originalImportPath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
100
+ const originalExt = extMatch ? extMatch[0] : '';
101
+
102
+ // Get extension from the resolved absolute path
103
+ const targetExtMatch = absolutePath.match(/\.(js|jsx|ts|tsx|mjs|cjs)$/);
104
+ const targetExt = targetExtMatch ? targetExtMatch[0] : '';
105
+
106
+ // Remove file extension from the target path
107
+ relativePath = relativePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
108
+
109
+ // If original had extension, append it
110
+ if (originalExt) {
111
+ // If original was a TypeScript source extension, use the actual target extension
112
+ if (['.ts', '.tsx'].includes(originalExt) && targetExt) {
113
+ relativePath += targetExt;
114
+ } else {
115
+ relativePath += originalExt;
116
+ }
117
+ } else {
118
+ // Remove /index suffix only if no extension was present
119
+ if (relativePath.endsWith('/index')) {
120
+ relativePath = relativePath.slice(0, -6);
121
+ } else if (relativePath === 'index') {
122
+ relativePath = '.';
123
+ }
124
+ }
125
+
126
+ // Ensure it starts with .. or .
127
+ if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
128
+ relativePath = './' + relativePath;
129
+ }
130
+ return relativePath;
131
+ }
132
+
133
+ /**
134
+ * Build an import statement for a set of specifiers
135
+ */
136
+ function buildImportStatement({
137
+ specs,
138
+ path,
139
+ quoteChar,
140
+ isTypeImport = false
141
+ }) {
142
+ const importNames = specs.map(spec => {
143
+ if (spec.type === 'ImportDefaultSpecifier') {
144
+ return spec.local.name;
145
+ } else if (spec.type === 'ImportSpecifier') {
146
+ const imported = getImportedName(spec);
147
+ const local = spec.local.name;
148
+ const isInlineType = spec.importKind === 'type' && !isTypeImport;
149
+ const prefix = isInlineType ? 'type ' : '';
150
+ return imported === local ? `${prefix}${imported}` : `${prefix}${imported} as ${local}`;
151
+ }
152
+ return '';
153
+ }).filter(name => name.length > 0);
154
+ if (importNames.length === 0) {
155
+ return '';
156
+ }
157
+ const typeKeyword = isTypeImport ? 'type ' : '';
158
+ const hasDefault = specs.some(spec => spec.type === 'ImportDefaultSpecifier');
159
+ const hasNamed = specs.some(spec => spec.type === 'ImportSpecifier');
160
+ if (hasDefault && hasNamed) {
161
+ var _specs$find;
162
+ const defaultName = (_specs$find = specs.find(spec => spec.type === 'ImportDefaultSpecifier')) === null || _specs$find === void 0 ? void 0 : _specs$find.local.name;
163
+ const namedImports = specs.filter(spec => spec.type === 'ImportSpecifier').map(spec => {
164
+ const imported = getImportedName(spec);
165
+ const local = spec.local.name;
166
+ const isInlineType = spec.importKind === 'type' && !isTypeImport;
167
+ const prefix = isInlineType ? 'type ' : '';
168
+ return imported === local ? `${prefix}${imported}` : `${prefix}${imported} as ${local}`;
169
+ }).join(', ');
170
+ return `import ${typeKeyword}${defaultName}, { ${namedImports} } from ${quoteChar}${path}${quoteChar};`;
171
+ } else if (hasDefault) {
172
+ var _specs$find2;
173
+ const defaultName = (_specs$find2 = specs.find(spec => spec.type === 'ImportDefaultSpecifier')) === null || _specs$find2 === void 0 ? void 0 : _specs$find2.local.name;
174
+ return `import ${typeKeyword}${defaultName} from ${quoteChar}${path}${quoteChar};`;
175
+ } else {
176
+ return `import ${typeKeyword}{ ${importNames.join(', ')} } from ${quoteChar}${path}${quoteChar};`;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Build an export statement for a set of specifiers
182
+ */
183
+ function buildExportStatement({
184
+ specs,
185
+ path,
186
+ quoteChar,
187
+ isTypeExport = false
188
+ }) {
189
+ const exportNames = specs.map(spec => {
190
+ const isInlineType = spec.kind === 'type' && !isTypeExport;
191
+ const prefix = isInlineType ? 'type ' : '';
192
+ return spec.nameInSource === spec.nameInLocal ? `${prefix}${spec.nameInSource}` : `${prefix}${spec.nameInSource} as ${spec.nameInLocal}`;
193
+ }).filter(name => name.length > 0);
194
+ if (exportNames.length === 0) {
195
+ return '';
196
+ }
197
+ const typeKeyword = isTypeExport ? 'type ' : '';
198
+ return `export ${typeKeyword}{ ${exportNames.join(', ')} } from ${quoteChar}${path}${quoteChar};`;
199
+ }
200
+ const ruleMeta = {
201
+ type: 'problem',
202
+ docs: {
203
+ description: 'Warn when imports are from a relative barrel file and provide an auto-fix to split them into specific imports.',
204
+ category: 'Best Practices',
205
+ recommended: false
206
+ },
207
+ fixable: 'code',
208
+ messages: {
209
+ barrelImport: "Import from barrel file '{{path}}' should be split into more specific imports. Use auto-fix to resolve."
210
+ }
211
+ };
212
+
213
+ /**
214
+ * Resolve barrel file context from an import/export node.
215
+ * Returns null if the node should not be checked (not relative, not resolvable, not a barrel).
216
+ */
217
+ function resolveBarrelFileContext({
218
+ node,
219
+ context,
220
+ fs
221
+ }) {
222
+ if (!node.source) {
223
+ return null;
224
+ }
225
+ const importPath = node.source.value;
226
+ if (!isRelativeImport(importPath)) {
227
+ return null;
228
+ }
229
+ const basedir = dirname(context.filename);
230
+ const resolvedPath = resolveImportPath({
231
+ basedir,
232
+ importPath,
233
+ fs
234
+ });
235
+ if (!resolvedPath) {
236
+ return null;
237
+ }
238
+ const workspaceRoot = findWorkspaceRoot({
239
+ startPath: basedir,
240
+ fs
241
+ });
242
+ const exportMap = parseBarrelExportsFromShared({
243
+ barrelFilePath: resolvedPath,
244
+ workspaceRoot,
245
+ fs
246
+ });
247
+ if (exportMap.size === 0) {
248
+ return null;
249
+ }
250
+ if (!hasReExportsFromOtherFiles({
251
+ exportMap,
252
+ sourceFilePath: resolvedPath
253
+ })) {
254
+ return null;
255
+ }
256
+ return {
257
+ importPath,
258
+ basedir,
259
+ resolvedPath,
260
+ workspaceRoot,
261
+ exportMap
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Collect specifiers grouped by their source file.
267
+ */
268
+ function collectSpecifiersBySource({
269
+ specifiers,
270
+ node,
271
+ exportMap
272
+ }) {
273
+ const importsBySource = new Map();
274
+ const unmappedSpecifiers = [];
275
+ let hasNamespaceImport = false;
276
+ for (const spec of specifiers) {
277
+ let nameInSource;
278
+ let nameInLocal;
279
+ let kind = 'value';
280
+ if (spec.type === 'ImportNamespaceSpecifier') {
281
+ hasNamespaceImport = true;
282
+ continue;
283
+ }
284
+ if (spec.type === 'ImportDefaultSpecifier') {
285
+ nameInSource = 'default';
286
+ nameInLocal = spec.local.name;
287
+ } else if (spec.type === 'ImportSpecifier') {
288
+ nameInSource = getImportedName(spec);
289
+ nameInLocal = spec.local.name;
290
+ const parentImportKind = node.type === 'ImportDeclaration' ? node.importKind : undefined;
291
+ kind = parentImportKind === 'type' || spec.importKind === 'type' ? 'type' : 'value';
292
+ } else if (spec.type === 'ExportSpecifier') {
293
+ nameInSource = spec.local.name;
294
+ nameInLocal = spec.exported.name;
295
+ const parentExportKind = node.type === 'ExportNamedDeclaration' ? node.exportKind : undefined;
296
+ kind = parentExportKind === 'type' || spec.exportKind === 'type' ? 'type' : 'value';
297
+ } else {
298
+ continue;
299
+ }
300
+ const exportInfo = exportMap.get(nameInSource);
301
+ if (exportInfo) {
302
+ if (!importsBySource.has(exportInfo.path)) {
303
+ importsBySource.set(exportInfo.path, []);
304
+ }
305
+ const effectiveKind = kind === 'type' || exportInfo.isTypeOnly ? 'type' : 'value';
306
+ importsBySource.get(exportInfo.path).push({
307
+ spec,
308
+ originalName: exportInfo.originalName,
309
+ exportInfo,
310
+ nameInSource,
311
+ nameInLocal,
312
+ kind: effectiveKind
313
+ });
314
+ } else {
315
+ unmappedSpecifiers.push(spec);
316
+ }
317
+ }
318
+ return {
319
+ importsBySource,
320
+ unmappedSpecifiers,
321
+ hasNamespaceImport
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Find an existing import statement that imports from the same source file.
327
+ */
328
+ function findExistingImportForSourceFile({
329
+ sourceFile,
330
+ allImports,
331
+ basedir,
332
+ fs
333
+ }) {
334
+ return allImports.find(n => {
335
+ const isSourceAbsolute = sourceFile.startsWith('/') || sourceFile.match(/^[a-zA-Z]:/);
336
+ if (!isRelativeImport(sourceFile) && !isSourceAbsolute) {
337
+ return n.source.value === sourceFile;
338
+ }
339
+ if (!isRelativeImport(n.source.value)) {
340
+ return false;
341
+ }
342
+ const existingPath = resolveImportPath({
343
+ basedir,
344
+ importPath: n.source.value,
345
+ fs
346
+ });
347
+ return existingPath === sourceFile;
348
+ });
349
+ }
350
+
351
+ /**
352
+ * Find an existing export statement that exports from the same source file.
353
+ */
354
+ function findExistingExportForSourceFile({
355
+ sourceFile,
356
+ allExports,
357
+ basedir,
358
+ fs
359
+ }) {
360
+ return allExports.find(n => {
361
+ if (!n.source) {
362
+ return false;
363
+ }
364
+ const isSourceAbsolute = sourceFile.startsWith('/') || sourceFile.match(/^[a-zA-Z]:/);
365
+ if (!isRelativeImport(sourceFile) && !isSourceAbsolute) {
366
+ return n.source.value === sourceFile;
367
+ }
368
+ if (!isRelativeImport(n.source.value)) {
369
+ return false;
370
+ }
371
+ const existingPath = resolveImportPath({
372
+ basedir,
373
+ importPath: n.source.value,
374
+ fs
375
+ });
376
+ return existingPath === sourceFile;
377
+ });
378
+ }
379
+
380
+ /**
381
+ * Transform import specifiers to handle aliasing when the original name differs.
382
+ */
383
+ function transformImportSpecifiers({
384
+ specsWithOriginal
385
+ }) {
386
+ return specsWithOriginal.map(({
387
+ spec,
388
+ originalName,
389
+ kind
390
+ }) => {
391
+ if (!originalName) {
392
+ return spec;
393
+ }
394
+ if (originalName === 'default') {
395
+ if (spec.type === 'ImportDefaultSpecifier') {
396
+ return spec;
397
+ }
398
+ return {
399
+ type: 'ImportDefaultSpecifier',
400
+ local: spec.local,
401
+ range: spec.range,
402
+ loc: spec.loc,
403
+ parent: spec.parent
404
+ };
405
+ } else {
406
+ return {
407
+ type: 'ImportSpecifier',
408
+ local: spec.local,
409
+ imported: {
410
+ type: 'Identifier',
411
+ name: originalName,
412
+ range: [0, 0],
413
+ loc: {
414
+ start: {
415
+ line: 0,
416
+ column: 0
417
+ },
418
+ end: {
419
+ line: 0,
420
+ column: 0
421
+ }
422
+ }
423
+ },
424
+ importKind: kind,
425
+ range: spec.range,
426
+ loc: spec.loc,
427
+ parent: spec.parent
428
+ };
429
+ }
430
+ });
431
+ }
432
+
433
+ /**
434
+ * Transform export specifiers to use original names from source.
435
+ */
436
+ function transformExportSpecifiers({
437
+ specsWithOriginal
438
+ }) {
439
+ return specsWithOriginal.map(({
440
+ originalName,
441
+ nameInSource,
442
+ nameInLocal,
443
+ kind
444
+ }) => ({
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,
448
+ nameInLocal,
449
+ kind
450
+ }));
451
+ }
452
+ export function createRule(fs) {
453
+ return {
454
+ meta: ruleMeta,
455
+ create(context) {
456
+ const checkNode = rawNode => {
457
+ const node = rawNode;
458
+
459
+ // Resolve barrel file context (handles early exits for non-barrel files)
460
+ const barrelContext = resolveBarrelFileContext({
461
+ node,
462
+ context,
463
+ fs
464
+ });
465
+ if (!barrelContext) {
466
+ return;
467
+ }
468
+ const {
469
+ importPath,
470
+ resolvedPath,
471
+ exportMap
472
+ } = barrelContext;
473
+
474
+ // Check if we have any imports from this barrel
475
+ const specifiers = node.specifiers;
476
+ if (specifiers.length === 0) {
477
+ return;
478
+ }
479
+
480
+ // Collect imports by source file
481
+ const {
482
+ importsBySource,
483
+ unmappedSpecifiers,
484
+ hasNamespaceImport
485
+ } = collectSpecifiersBySource({
486
+ specifiers,
487
+ node,
488
+ exportMap
489
+ });
490
+
491
+ // If we have import * as, warn but don't auto-fix (too complex)
492
+ if (hasNamespaceImport) {
493
+ context.report({
494
+ node: node,
495
+ messageId: 'barrelImport',
496
+ data: {
497
+ path: importPath
498
+ }
499
+ });
500
+ return;
501
+ }
502
+
503
+ // Check if we actually found any imports that map to the barrel exports
504
+ // If we found 0 mapped imports (all are unknown or missing), don't warn
505
+ if (importsBySource.size === 0) {
506
+ return;
507
+ }
508
+
509
+ // If all imports map back to the resolved path itself, don't warn.
510
+ // This happens when the file (e.g. index.ts) exports the symbols directly (not re-exports),
511
+ // so it is not acting as a barrel file for these imports.
512
+ if (importsBySource.size === 1 && importsBySource.has(resolvedPath)) {
513
+ return;
514
+ }
515
+
516
+ // Build the report with optional auto-fix
517
+ const reportObj = {
518
+ node: node,
519
+ messageId: 'barrelImport',
520
+ data: {
521
+ path: importPath
522
+ }
523
+ };
524
+
525
+ // Only provide auto-fix if all imports are mapped
526
+ // If there are unmapped specifiers, don't auto-fix to avoid issues
527
+ const nodeSource = node.source;
528
+ if (unmappedSpecifiers.length === 0 && nodeSource) {
529
+ reportObj.fix = function (fixer) {
530
+ const sourceCode = context.getSourceCode();
531
+ const quote = sourceCode.getText(nodeSource)[0]; // Get quote character
532
+ const basedirForFix = dirname(context.filename);
533
+ const fixes = [];
534
+ const newStatementsForBarrelReplacement = [];
535
+
536
+ // Generate new import statements for each source file
537
+ const sourceFileArray = Array.from(importsBySource.entries());
538
+ for (let i = 0; i < sourceFileArray.length; i++) {
539
+ const [sourceFile, specsWithOriginal] = sourceFileArray[i];
540
+ if (node.type === 'ImportDeclaration') {
541
+ // Transform specifiers if needed (handle aliasing)
542
+ const specs = transformImportSpecifiers({
543
+ specsWithOriginal
544
+ });
545
+
546
+ // Get all existing imports to check for merging
547
+ const allImports = sourceCode.ast.body.filter(n => n.type === 'ImportDeclaration' && n !== node);
548
+
549
+ // Check for existing import
550
+ const existingImport = findExistingImportForSourceFile({
551
+ sourceFile,
552
+ allImports,
553
+ basedir: basedirForFix,
554
+ fs
555
+ });
556
+
557
+ // Skip merging if existing is namespace import
558
+ const isNamespace = existingImport === null || existingImport === void 0 ? void 0 : existingImport.specifiers.some(s => s.type === 'ImportNamespaceSpecifier');
559
+ if (existingImport && !isNamespace) {
560
+ var _specsWithOriginal$0$, _specsWithOriginal$;
561
+ // Merge!
562
+ const existingSpecs = existingImport.specifiers.map(s => {
563
+ // Normalize importKind
564
+ if (existingImport.importKind === 'type') {
565
+ // If the parent declaration is 'type', treat all specifiers as 'type'
566
+ // We cast to AugmentedSpecifier to allow attaching importKind to DefaultSpecifier
567
+ return {
568
+ ...s,
569
+ importKind: 'type'
570
+ };
571
+ }
572
+ return s;
573
+ });
574
+ const newSpecs = specs.map(s => {
575
+ if (node.importKind === 'type') {
576
+ return {
577
+ ...s,
578
+ importKind: 'type'
579
+ };
580
+ }
581
+ return s;
582
+ });
583
+ const mergedSpecs = [...existingSpecs, ...newSpecs];
584
+
585
+ // Determine if we should use 'import type'
586
+ const allType = mergedSpecs.every(s => s.importKind === 'type');
587
+ const relativePath = getImportPathForSourceFile({
588
+ sourceFilePath: sourceFile,
589
+ basedir: basedirForFix,
590
+ originalImportPath: importPath,
591
+ exportInfo: (_specsWithOriginal$0$ = (_specsWithOriginal$ = specsWithOriginal[0]) === null || _specsWithOriginal$ === void 0 ? void 0 : _specsWithOriginal$.exportInfo) !== null && _specsWithOriginal$0$ !== void 0 ? _specsWithOriginal$0$ : null,
592
+ workspaceRoot: findWorkspaceRoot({
593
+ startPath: basedirForFix,
594
+ fs: fs
595
+ }),
596
+ fs: fs
597
+ });
598
+ const newImportStatement = buildImportStatement({
599
+ specs: mergedSpecs,
600
+ path: relativePath,
601
+ quoteChar: quote,
602
+ isTypeImport: allType // Use type import if all are types
603
+ });
604
+ if (newImportStatement.length > 0) {
605
+ fixes.push(fixer.replaceText(existingImport, newImportStatement));
606
+ }
607
+ } else {
608
+ var _specsWithOriginal$0$2, _specsWithOriginal$2;
609
+ // Create new import
610
+ const relativePath = getImportPathForSourceFile({
611
+ sourceFilePath: sourceFile,
612
+ basedir: basedirForFix,
613
+ originalImportPath: importPath,
614
+ exportInfo: (_specsWithOriginal$0$2 = (_specsWithOriginal$2 = specsWithOriginal[0]) === null || _specsWithOriginal$2 === void 0 ? void 0 : _specsWithOriginal$2.exportInfo) !== null && _specsWithOriginal$0$2 !== void 0 ? _specsWithOriginal$0$2 : null,
615
+ workspaceRoot: findWorkspaceRoot({
616
+ startPath: basedirForFix,
617
+ fs: fs
618
+ }),
619
+ fs: fs
620
+ });
621
+ const isTypeImport = node.importKind === 'type';
622
+ const importStatement = buildImportStatement({
623
+ specs,
624
+ path: relativePath,
625
+ quoteChar: quote,
626
+ isTypeImport
627
+ });
628
+ if (importStatement.length > 0) {
629
+ newStatementsForBarrelReplacement.push(importStatement);
630
+ }
631
+ }
632
+ } else if (node.type === 'ExportNamedDeclaration') {
633
+ // Handle ExportNamedDeclaration
634
+ const specs = transformExportSpecifiers({
635
+ specsWithOriginal
636
+ });
637
+
638
+ // Get all existing exports to check for merging
639
+ const allExports = sourceCode.ast.body.filter(n => n.type === 'ExportNamedDeclaration' && n !== node && !!n.source);
640
+
641
+ // Check for existing export
642
+ const existingExport = findExistingExportForSourceFile({
643
+ sourceFile,
644
+ allExports,
645
+ basedir: basedirForFix,
646
+ fs
647
+ });
648
+ if (existingExport) {
649
+ var _specsWithOriginal$0$3, _specsWithOriginal$3;
650
+ // Merge!
651
+ const existingSpecs = existingExport.specifiers.map(s => {
652
+ // For `export type { A, B }`, the parent's exportKind is 'type'
653
+ // Individual specifiers have exportKind: 'type' only for inline type (export { type A })
654
+ // If parent is type-only, all specifiers are types
655
+ // Otherwise, check individual specifier's exportKind
656
+ const effectiveKind = existingExport.exportKind === 'type' || s.exportKind === 'type' ? 'type' : 'value';
657
+ return {
658
+ nameInSource: getExportedName(s.local),
659
+ nameInLocal: getExportedName(s.exported),
660
+ kind: effectiveKind
661
+ };
662
+ });
663
+ const newSpecs = specs.map(s => {
664
+ if (node.exportKind === 'type') {
665
+ return {
666
+ ...s,
667
+ kind: 'type'
668
+ };
669
+ }
670
+ return s;
671
+ });
672
+ const mergedSpecs = [...existingSpecs, ...newSpecs];
673
+
674
+ // Determine if we should use 'export type'
675
+ const allType = mergedSpecs.every(s => s.kind === 'type');
676
+ const relativePath = getImportPathForSourceFile({
677
+ sourceFilePath: sourceFile,
678
+ basedir: basedirForFix,
679
+ originalImportPath: importPath,
680
+ exportInfo: (_specsWithOriginal$0$3 = (_specsWithOriginal$3 = specsWithOriginal[0]) === null || _specsWithOriginal$3 === void 0 ? void 0 : _specsWithOriginal$3.exportInfo) !== null && _specsWithOriginal$0$3 !== void 0 ? _specsWithOriginal$0$3 : null,
681
+ workspaceRoot: findWorkspaceRoot({
682
+ startPath: basedirForFix,
683
+ fs: fs
684
+ }),
685
+ fs: fs
686
+ });
687
+ const newExportStatement = buildExportStatement({
688
+ specs: mergedSpecs,
689
+ path: relativePath,
690
+ quoteChar: quote,
691
+ isTypeExport: allType
692
+ });
693
+ if (newExportStatement.length > 0) {
694
+ fixes.push(fixer.replaceText(existingExport, newExportStatement));
695
+ }
696
+ } else {
697
+ var _specsWithOriginal$0$4, _specsWithOriginal$4;
698
+ // Create new export
699
+ const relativePath = getImportPathForSourceFile({
700
+ sourceFilePath: sourceFile,
701
+ basedir: basedirForFix,
702
+ originalImportPath: importPath,
703
+ exportInfo: (_specsWithOriginal$0$4 = (_specsWithOriginal$4 = specsWithOriginal[0]) === null || _specsWithOriginal$4 === void 0 ? void 0 : _specsWithOriginal$4.exportInfo) !== null && _specsWithOriginal$0$4 !== void 0 ? _specsWithOriginal$0$4 : null,
704
+ workspaceRoot: findWorkspaceRoot({
705
+ startPath: basedirForFix,
706
+ fs: fs
707
+ }),
708
+ fs: fs
709
+ });
710
+ const isTypeExport = node.exportKind === 'type';
711
+ const exportStatement = buildExportStatement({
712
+ specs,
713
+ path: relativePath,
714
+ quoteChar: quote,
715
+ isTypeExport
716
+ });
717
+ if (exportStatement.length > 0) {
718
+ newStatementsForBarrelReplacement.push(exportStatement);
719
+ }
720
+ }
721
+ }
722
+ }
723
+ if (newStatementsForBarrelReplacement.length > 0) {
724
+ fixes.push(fixer.replaceText(node, newStatementsForBarrelReplacement.join('\n')));
725
+ } else {
726
+ // If all were merged, remove the node
727
+ fixes.push(fixer.remove(node));
728
+ }
729
+ return fixes;
730
+ };
731
+ }
732
+ context.report(reportObj);
733
+ };
734
+ return {
735
+ ImportDeclaration: checkNode,
736
+ ExportNamedDeclaration: checkNode
737
+ };
738
+ }
739
+ };
740
+ }
741
+ const rule = createRule(realFileSystem);
742
+ export default rule;