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