@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,433 @@
1
+ import { dirname } from 'path';
2
+ import * as ts from 'typescript';
3
+ import { isRelativeImport, readFileContent, resolveImportPath } from './file-system';
4
+ import { resolveCrossPackageImport } from './package-resolution';
5
+ import { perfInc, perfTime } from './perf';
6
+ /**
7
+ * Maximum depth to traverse when looking for source files in barrel exports.
8
+ * Can be adjusted based on project structure needs.
9
+ */
10
+ const MAX_BARREL_DEPTH = 10;
11
+
12
+ /**
13
+ * Get all named exports from a file.
14
+ * This extracts what names are exported from a file for star exports.
15
+ */
16
+ export function getNamedExportsFromFile({
17
+ filePath,
18
+ fs
19
+ }) {
20
+ const names = new Set();
21
+ const content = readFileContent({
22
+ filePath,
23
+ fs
24
+ });
25
+ if (!content) {
26
+ return names;
27
+ }
28
+ try {
29
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
30
+ ts.forEachChild(sourceFile, node => {
31
+ if (ts.isExportDeclaration(node)) {
32
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
33
+ node.exportClause.elements.forEach(e => names.add(e.name.text));
34
+ }
35
+ } else if (ts.isExportAssignment(node)) {
36
+ names.add('default');
37
+ } else if (ts.isVariableStatement(node)) {
38
+ if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
39
+ node.declarationList.declarations.forEach(d => {
40
+ if (ts.isIdentifier(d.name)) {
41
+ names.add(d.name.text);
42
+ }
43
+ });
44
+ }
45
+ } else if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
46
+ if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
47
+ if (node.name && ts.isIdentifier(node.name)) {
48
+ names.add(node.name.text);
49
+ }
50
+ }
51
+ }
52
+ });
53
+ } catch {
54
+ // Ignore parsing errors
55
+ }
56
+ return names;
57
+ }
58
+
59
+ /**
60
+ * Check if an export map represents a barrel file (has re-exports from other files)
61
+ */
62
+ export function hasReExportsFromOtherFiles({
63
+ exportMap,
64
+ sourceFilePath
65
+ }) {
66
+ return Array.from(exportMap.values()).some(info => info.path !== sourceFilePath);
67
+ }
68
+
69
+ /**
70
+ * Process a named export element with cross-package support and add it to the exports map
71
+ */
72
+ function processNamedExportElementWithCrossPackage({
73
+ element,
74
+ resolvedSource,
75
+ nestedExports,
76
+ isStatementTypeOnly,
77
+ exports,
78
+ crossPackageSource
79
+ }) {
80
+ const localName = element.name.text;
81
+ const propertyName = element.propertyName ? element.propertyName.text : localName;
82
+ const isElementTypeOnly = isStatementTypeOnly || element.isTypeOnly;
83
+ const isDefaultExport = propertyName === 'default';
84
+ // Track if this export is aliased (exported name differs from source name)
85
+ const isAliased = element.propertyName !== undefined && localName !== propertyName;
86
+ if (nestedExports) {
87
+ const deepSource = nestedExports.get(propertyName);
88
+ if (deepSource) {
89
+ var _deepSource$originalN;
90
+ exports.set(localName, {
91
+ path: deepSource.path,
92
+ isTypeOnly: isElementTypeOnly || deepSource.isTypeOnly,
93
+ isDefaultExport: deepSource.isDefaultExport,
94
+ // Use deep source's original name if available, otherwise use propertyName if aliased
95
+ originalName: (_deepSource$originalN = deepSource.originalName) !== null && _deepSource$originalN !== void 0 ? _deepSource$originalN : isAliased ? propertyName : undefined,
96
+ // Preserve existing crossPackageSource from nested exports, or use the one from this level
97
+ crossPackageSource: deepSource.crossPackageSource || crossPackageSource
98
+ });
99
+ } else {
100
+ exports.set(localName, {
101
+ path: resolvedSource,
102
+ isTypeOnly: isElementTypeOnly,
103
+ isDefaultExport,
104
+ originalName: isAliased ? propertyName : undefined,
105
+ crossPackageSource
106
+ });
107
+ }
108
+ } else {
109
+ exports.set(localName, {
110
+ path: resolvedSource,
111
+ isTypeOnly: isElementTypeOnly,
112
+ isDefaultExport,
113
+ originalName: isAliased ? propertyName : undefined,
114
+ crossPackageSource
115
+ });
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Handle star exports with cross-package support (export * from '...')
121
+ */
122
+ function processStarExportWithCrossPackage({
123
+ resolvedSource,
124
+ nestedExports,
125
+ exports,
126
+ fs,
127
+ crossPackageSource
128
+ }) {
129
+ if (nestedExports) {
130
+ for (const [name, info] of nestedExports) {
131
+ if (name !== 'default') {
132
+ // Preserve existing crossPackageSource from nested exports, or use the one from this level
133
+ exports.set(name, {
134
+ ...info,
135
+ crossPackageSource: info.crossPackageSource || crossPackageSource
136
+ });
137
+ }
138
+ }
139
+ } else {
140
+ const nonBarrelExports = getNamedExportsFromFile({
141
+ filePath: resolvedSource,
142
+ fs
143
+ });
144
+ for (const name of nonBarrelExports) {
145
+ if (name !== 'default') {
146
+ exports.set(name, {
147
+ path: resolvedSource,
148
+ isTypeOnly: false,
149
+ crossPackageSource
150
+ });
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Process export declarations that have module specifiers (export ... from '...')
158
+ */
159
+ function processExportWithModuleSpecifier({
160
+ statement,
161
+ resolveModule,
162
+ depth,
163
+ exports,
164
+ fs,
165
+ workspaceRoot,
166
+ visitedPackages
167
+ }) {
168
+ if (!statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier)) {
169
+ return;
170
+ }
171
+ const modulePath = statement.moduleSpecifier.text;
172
+
173
+ // Try relative resolution first (existing behavior)
174
+ let resolvedSource = resolveModule(modulePath);
175
+ let crossPackageSource;
176
+
177
+ // If not a relative import, try cross-package resolution
178
+ if (!resolvedSource && !isRelativeImport(modulePath) && workspaceRoot) {
179
+ const crossPackageInfo = resolveCrossPackageImport({
180
+ moduleSpecifier: modulePath,
181
+ workspaceRoot,
182
+ fs
183
+ });
184
+ if (crossPackageInfo) {
185
+ // Check for circular dependencies
186
+ if (visitedPackages !== null && visitedPackages !== void 0 && visitedPackages.has(crossPackageInfo.packageName)) {
187
+ return;
188
+ }
189
+ resolvedSource = crossPackageInfo.entryFilePath;
190
+ crossPackageSource = {
191
+ packageName: crossPackageInfo.packageName,
192
+ exportPath: crossPackageInfo.exportPath
193
+ };
194
+ }
195
+ }
196
+ if (!resolvedSource) {
197
+ return;
198
+ }
199
+ const isStatementTypeOnly = statement.isTypeOnly;
200
+
201
+ // For cross-package imports, track visited packages to prevent circular deps
202
+ const newVisitedPackages = visitedPackages ? new Set(visitedPackages) : new Set();
203
+ if (crossPackageSource) {
204
+ newVisitedPackages.add(crossPackageSource.packageName);
205
+ }
206
+
207
+ // Always trace through nested barrels and cross-package exports to find the ultimate source
208
+ const potentialNestedExports = parseBarrelExports({
209
+ barrelFilePath: resolvedSource,
210
+ depth: depth + 1,
211
+ fs,
212
+ workspaceRoot,
213
+ visitedPackages: newVisitedPackages
214
+ });
215
+ const nestedExports = hasReExportsFromOtherFiles({
216
+ exportMap: potentialNestedExports,
217
+ sourceFilePath: resolvedSource
218
+ }) ? potentialNestedExports : null;
219
+ if (statement.exportClause) {
220
+ if (ts.isNamedExports(statement.exportClause)) {
221
+ for (const element of statement.exportClause.elements) {
222
+ processNamedExportElementWithCrossPackage({
223
+ element,
224
+ resolvedSource,
225
+ nestedExports,
226
+ isStatementTypeOnly,
227
+ exports,
228
+ crossPackageSource
229
+ });
230
+ }
231
+ } else if (ts.isNamespaceExport(statement.exportClause)) {
232
+ const name = statement.exportClause.name.text;
233
+ exports.set(name, {
234
+ path: resolvedSource,
235
+ isTypeOnly: isStatementTypeOnly,
236
+ crossPackageSource
237
+ });
238
+ }
239
+ } else {
240
+ processStarExportWithCrossPackage({
241
+ resolvedSource,
242
+ nestedExports,
243
+ exports,
244
+ fs,
245
+ crossPackageSource
246
+ });
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Process local export statements (no module specifier)
252
+ */
253
+ function processLocalExportDeclaration({
254
+ statement,
255
+ barrelFilePath,
256
+ exports
257
+ }) {
258
+ if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
259
+ return;
260
+ }
261
+ const isStatementTypeOnly = statement.isTypeOnly;
262
+ for (const element of statement.exportClause.elements) {
263
+ const isElementTypeOnly = isStatementTypeOnly || element.isTypeOnly;
264
+ exports.set(element.name.text, {
265
+ path: barrelFilePath,
266
+ isTypeOnly: isElementTypeOnly
267
+ });
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Process various declaration statements with export modifiers
273
+ */
274
+ function processExportedDeclaration({
275
+ statement,
276
+ barrelFilePath,
277
+ exports
278
+ }) {
279
+ if (ts.isExportAssignment(statement)) {
280
+ exports.set('default', {
281
+ path: barrelFilePath,
282
+ isTypeOnly: false
283
+ });
284
+ return;
285
+ }
286
+ const hasExportModifier = 'modifiers' in statement && Array.isArray(statement.modifiers) && statement.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
287
+ if (!hasExportModifier) {
288
+ return;
289
+ }
290
+ if (ts.isVariableStatement(statement)) {
291
+ for (const decl of statement.declarationList.declarations) {
292
+ if (ts.isIdentifier(decl.name)) {
293
+ exports.set(decl.name.text, {
294
+ path: barrelFilePath,
295
+ isTypeOnly: false
296
+ });
297
+ }
298
+ }
299
+ } else if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) {
300
+ if (statement.name && ts.isIdentifier(statement.name)) {
301
+ exports.set(statement.name.text, {
302
+ path: barrelFilePath,
303
+ isTypeOnly: false
304
+ });
305
+ }
306
+ } else if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
307
+ if (statement.name && ts.isIdentifier(statement.name)) {
308
+ exports.set(statement.name.text, {
309
+ path: barrelFilePath,
310
+ isTypeOnly: true
311
+ });
312
+ }
313
+ } else if (ts.isEnumDeclaration(statement)) {
314
+ if (statement.name && ts.isIdentifier(statement.name)) {
315
+ exports.set(statement.name.text, {
316
+ path: barrelFilePath,
317
+ isTypeOnly: false
318
+ });
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Parse export statements from a file to find where each export comes from.
325
+ * Returns a map of export name -> ExportInfo.
326
+ *
327
+ * This function recursively traces through nested barrels and cross-package re-exports
328
+ * to find the ultimate source file for each export.
329
+ */
330
+ export function parseBarrelExports({
331
+ barrelFilePath,
332
+ depth = 0,
333
+ fs,
334
+ workspaceRoot,
335
+ visitedPackages
336
+ }) {
337
+ perfInc({
338
+ fs,
339
+ key: 'parseBarrelExports.calls'
340
+ });
341
+ return perfTime({
342
+ fs,
343
+ key: 'parseBarrelExports.totalMs',
344
+ fn: () => {
345
+ if (depth > MAX_BARREL_DEPTH) {
346
+ return new Map();
347
+ }
348
+ if (!fs.cache.barrelExportsByPath) {
349
+ fs.cache.barrelExportsByPath = new Map();
350
+ }
351
+ let currentMtimeMs = 0;
352
+ try {
353
+ var _fs$statSync$mtimeMs;
354
+ currentMtimeMs = (_fs$statSync$mtimeMs = fs.statSync(barrelFilePath).mtimeMs) !== null && _fs$statSync$mtimeMs !== void 0 ? _fs$statSync$mtimeMs : 0;
355
+ } catch {
356
+ currentMtimeMs = 0;
357
+ }
358
+ const cached = fs.cache.barrelExportsByPath.get(barrelFilePath);
359
+ if (cached && cached.mtimeMs === currentMtimeMs) {
360
+ perfInc({
361
+ fs,
362
+ key: 'parseBarrelExports.cacheHit'
363
+ });
364
+ return cached.exports;
365
+ }
366
+ perfInc({
367
+ fs,
368
+ key: 'parseBarrelExports.cacheMiss'
369
+ });
370
+ const exports = new Map();
371
+ const content = readFileContent({
372
+ filePath: barrelFilePath,
373
+ fs
374
+ });
375
+ if (!content) {
376
+ return exports;
377
+ }
378
+ let sourceFile;
379
+ try {
380
+ sourceFile = perfTime({
381
+ fs,
382
+ key: 'parseBarrelExports.tsCreateSourceFileMs',
383
+ fn: () => ts.createSourceFile(barrelFilePath, content, ts.ScriptTarget.Latest, true)
384
+ });
385
+ } catch {
386
+ return exports;
387
+ }
388
+ const barrelDir = dirname(barrelFilePath);
389
+ const resolveModule = moduleSpecifier => {
390
+ if (!isRelativeImport(moduleSpecifier)) {
391
+ return null;
392
+ }
393
+ return resolveImportPath({
394
+ basedir: barrelDir,
395
+ importPath: moduleSpecifier,
396
+ fs
397
+ });
398
+ };
399
+ for (const statement of sourceFile.statements) {
400
+ if (ts.isExportDeclaration(statement)) {
401
+ if (statement.moduleSpecifier) {
402
+ processExportWithModuleSpecifier({
403
+ statement,
404
+ resolveModule,
405
+ depth,
406
+ exports,
407
+ fs,
408
+ workspaceRoot,
409
+ visitedPackages
410
+ });
411
+ } else {
412
+ processLocalExportDeclaration({
413
+ statement,
414
+ barrelFilePath,
415
+ exports
416
+ });
417
+ }
418
+ } else {
419
+ processExportedDeclaration({
420
+ statement,
421
+ barrelFilePath,
422
+ exports
423
+ });
424
+ }
425
+ }
426
+ fs.cache.barrelExportsByPath.set(barrelFilePath, {
427
+ mtimeMs: currentMtimeMs,
428
+ exports
429
+ });
430
+ return exports;
431
+ }
432
+ });
433
+ }
@@ -0,0 +1,174 @@
1
+ import { dirname, join, resolve } from 'path';
2
+ /**
3
+ * The default folder paths that barrel import rules apply to.
4
+ * Only imports from packages within these folders will be checked.
5
+ * This can be overridden via lint rule options.
6
+ */
7
+ export const DEFAULT_TARGET_FOLDERS = ['platform/packages/ai-mate', 'platform/packages/search'];
8
+
9
+ /**
10
+ * Try to read file contents with error handling.
11
+ * Returns null if file cannot be read.
12
+ */
13
+ export function readFileContent({
14
+ filePath,
15
+ fs
16
+ }) {
17
+ if (!fs.cache.fileContentByPath) {
18
+ fs.cache.fileContentByPath = new Map();
19
+ }
20
+ try {
21
+ if (!fs.existsSync(filePath)) {
22
+ return null;
23
+ }
24
+ let currentMtimeMs = null;
25
+ try {
26
+ var _fs$statSync$mtimeMs;
27
+ currentMtimeMs = (_fs$statSync$mtimeMs = fs.statSync(filePath).mtimeMs) !== null && _fs$statSync$mtimeMs !== void 0 ? _fs$statSync$mtimeMs : null;
28
+ } catch {
29
+ // If stat fails, use null to force a re-read (don't use cached value)
30
+ currentMtimeMs = null;
31
+ }
32
+ const cached = fs.cache.fileContentByPath.get(filePath);
33
+ // Only use cache if we have a valid mtime and it matches
34
+ if (cached && currentMtimeMs !== null && cached.mtimeMs === currentMtimeMs) {
35
+ return cached.content;
36
+ }
37
+ const content = fs.readFileSync(filePath, 'utf-8');
38
+ fs.cache.fileContentByPath.set(filePath, {
39
+ mtimeMs: currentMtimeMs,
40
+ content
41
+ });
42
+ return content;
43
+ } catch {
44
+ // Silently fail if file cannot be read
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Check if a path is a relative import (starts with ./ or ../)
51
+ */
52
+ export function isRelativeImport(importPath) {
53
+ return importPath.startsWith('./') || importPath.startsWith('../');
54
+ }
55
+
56
+ /**
57
+ * Resolve the actual file path from an import path.
58
+ * Handles extension inference and index file resolution.
59
+ */
60
+ export function resolveImportPath({
61
+ basedir,
62
+ importPath,
63
+ fs
64
+ }) {
65
+ if (!fs.cache.resolvedImportPathByKey) {
66
+ fs.cache.resolvedImportPathByKey = new Map();
67
+ }
68
+ const cacheKey = `${basedir}::${importPath}`;
69
+ if (fs.cache.resolvedImportPathByKey.has(cacheKey)) {
70
+ var _fs$cache$resolvedImp;
71
+ return (_fs$cache$resolvedImp = fs.cache.resolvedImportPathByKey.get(cacheKey)) !== null && _fs$cache$resolvedImp !== void 0 ? _fs$cache$resolvedImp : null;
72
+ }
73
+ const possibleExtensions = ['.ts', '.tsx', '.js', '.jsx'];
74
+ const absolutePath = resolve(basedir, importPath);
75
+
76
+ // Check exact path first (for explicit extensions)
77
+ if (fs.existsSync(absolutePath)) {
78
+ try {
79
+ const stats = fs.statSync(absolutePath);
80
+ if (stats.isFile()) {
81
+ const resolvedPath = fs.realpathSync(absolutePath);
82
+ fs.cache.resolvedImportPathByKey.set(cacheKey, resolvedPath);
83
+ return resolvedPath;
84
+ }
85
+ } catch {
86
+ // Ignore errors
87
+ }
88
+ }
89
+
90
+ // Try with extensions
91
+ for (const ext of possibleExtensions) {
92
+ const pathWithExt = absolutePath + ext;
93
+ if (fs.existsSync(pathWithExt)) {
94
+ try {
95
+ const resolvedPath = fs.realpathSync(pathWithExt);
96
+ fs.cache.resolvedImportPathByKey.set(cacheKey, resolvedPath);
97
+ return resolvedPath;
98
+ } catch {
99
+ fs.cache.resolvedImportPathByKey.set(cacheKey, pathWithExt);
100
+ return pathWithExt;
101
+ }
102
+ }
103
+ }
104
+
105
+ // Try as directory with index
106
+ for (const ext of possibleExtensions) {
107
+ const indexPath = join(absolutePath, `index${ext}`);
108
+ if (fs.existsSync(indexPath)) {
109
+ try {
110
+ const resolvedPath = fs.realpathSync(indexPath);
111
+ fs.cache.resolvedImportPathByKey.set(cacheKey, resolvedPath);
112
+ return resolvedPath;
113
+ } catch {
114
+ fs.cache.resolvedImportPathByKey.set(cacheKey, indexPath);
115
+ return indexPath;
116
+ }
117
+ }
118
+ }
119
+ fs.cache.resolvedImportPathByKey.set(cacheKey, null);
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Find the workspace root using git rev-parse --show-toplevel.
125
+ * The result is cached on fs.cache.gitRepoRoot to avoid repeated shell calls.
126
+ * Falls back to directory traversal if git command fails.
127
+ */
128
+ export function findWorkspaceRoot({
129
+ startPath,
130
+ fs,
131
+ applyToImportsFrom = DEFAULT_TARGET_FOLDERS
132
+ }) {
133
+ // Return cached value if available
134
+ if (fs.cache.gitRepoRoot) {
135
+ return fs.cache.gitRepoRoot;
136
+ }
137
+
138
+ // Try to get the git repository root
139
+ const gitRoot = fs.execSync('git rev-parse --show-toplevel', {
140
+ cwd: startPath
141
+ });
142
+ if (gitRoot) {
143
+ fs.cache.gitRepoRoot = gitRoot;
144
+ return gitRoot;
145
+ }
146
+
147
+ // Fallback: traverse up looking for workspace markers
148
+ let current = startPath;
149
+ const maxDepth = 20;
150
+ let depth = 0;
151
+ while (depth < maxDepth) {
152
+ // Check if we've found the platform folder (which contains the target folders)
153
+ if (applyToImportsFrom.some(folder => fs.existsSync(join(current, folder)))) {
154
+ fs.cache.gitRepoRoot = current;
155
+ return current;
156
+ }
157
+
158
+ // Check for root-level indicators
159
+ const hasRootPackageJson = fs.existsSync(join(current, 'package.json'));
160
+ const hasYarnLock = fs.existsSync(join(current, 'yarn.lock'));
161
+ if (hasRootPackageJson && hasYarnLock) {
162
+ fs.cache.gitRepoRoot = current;
163
+ return current;
164
+ }
165
+ const parent = dirname(current);
166
+ if (parent === current) {
167
+ // Reached filesystem root
168
+ return startPath;
169
+ }
170
+ current = parent;
171
+ depth++;
172
+ }
173
+ return startPath;
174
+ }