@atlaskit/eslint-plugin-platform 2.7.2 → 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.
- package/CHANGELOG.md +7 -0
- package/dist/cjs/index.js +14 -3
- package/dist/cjs/rules/feature-gating/valid-gate-name/index.js +60 -0
- package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +871 -0
- package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +1384 -0
- package/dist/cjs/rules/import/no-conversation-assistant-barrel-imports/index.js +43 -0
- package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +1401 -0
- package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +777 -0
- package/dist/cjs/rules/import/shared/barrel-parsing.js +511 -0
- package/dist/cjs/rules/import/shared/file-system.js +186 -0
- package/dist/cjs/rules/import/shared/jest-utils.js +191 -0
- package/dist/cjs/rules/import/shared/package-registry.js +263 -0
- package/dist/cjs/rules/import/shared/package-resolution.js +185 -0
- package/dist/cjs/rules/import/shared/perf.js +89 -0
- package/dist/cjs/rules/import/shared/types.js +67 -0
- package/dist/es2019/index.js +14 -3
- package/dist/es2019/rules/feature-gating/valid-gate-name/index.js +52 -0
- package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +801 -0
- package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +1113 -0
- package/dist/es2019/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
- package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +1179 -0
- package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +738 -0
- package/dist/es2019/rules/import/shared/barrel-parsing.js +433 -0
- package/dist/es2019/rules/import/shared/file-system.js +174 -0
- package/dist/es2019/rules/import/shared/jest-utils.js +159 -0
- package/dist/es2019/rules/import/shared/package-registry.js +240 -0
- package/dist/es2019/rules/import/shared/package-resolution.js +161 -0
- package/dist/es2019/rules/import/shared/perf.js +83 -0
- package/dist/es2019/rules/import/shared/types.js +57 -0
- package/dist/esm/index.js +14 -3
- package/dist/esm/rules/feature-gating/valid-gate-name/index.js +53 -0
- package/dist/esm/rules/import/no-barrel-entry-imports/index.js +864 -0
- package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +1375 -0
- package/dist/esm/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
- package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +1391 -0
- package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +770 -0
- package/dist/esm/rules/import/shared/barrel-parsing.js +500 -0
- package/dist/esm/rules/import/shared/file-system.js +176 -0
- package/dist/esm/rules/import/shared/jest-utils.js +179 -0
- package/dist/esm/rules/import/shared/package-registry.js +256 -0
- package/dist/esm/rules/import/shared/package-resolution.js +175 -0
- package/dist/esm/rules/import/shared/perf.js +80 -0
- package/dist/esm/rules/import/shared/types.js +61 -0
- package/dist/types/index.d.ts +16 -2
- package/dist/types/rules/feature-gating/valid-gate-name/index.d.ts +3 -0
- package/dist/types/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
- package/dist/types/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
- package/dist/types/rules/import/no-conversation-assistant-barrel-imports/index.d.ts +3 -0
- package/dist/types/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
- package/dist/types/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
- package/dist/types/rules/import/shared/barrel-parsing.d.ts +30 -0
- package/dist/types/rules/import/shared/file-system.d.ts +38 -0
- package/dist/types/rules/import/shared/jest-utils.d.ts +47 -0
- package/dist/types/rules/import/shared/package-registry.d.ts +26 -0
- package/dist/types/rules/import/shared/package-resolution.d.ts +38 -0
- package/dist/types/rules/import/shared/perf.d.ts +13 -0
- package/dist/types/rules/import/shared/types.d.ts +131 -0
- package/dist/types-ts4.5/index.d.ts +16 -2
- package/dist/types-ts4.5/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
- package/dist/types-ts4.5/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
- package/dist/types-ts4.5/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
- package/dist/types-ts4.5/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
- package/dist/types-ts4.5/rules/import/shared/barrel-parsing.d.ts +30 -0
- package/dist/types-ts4.5/rules/import/shared/file-system.d.ts +38 -0
- package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +47 -0
- package/dist/types-ts4.5/rules/import/shared/package-registry.d.ts +26 -0
- package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +38 -0
- package/dist/types-ts4.5/rules/import/shared/perf.d.ts +13 -0
- package/dist/types-ts4.5/rules/import/shared/types.d.ts +131 -0
- package/package.json +4 -2
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -158
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -146
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -151
- /package/dist/types-ts4.5/rules/{ensure-native-and-af-exports-synced → feature-gating/valid-gate-name}/index.d.ts +0 -0
- /package/dist/{types/rules/ensure-native-and-af-exports-synced → types-ts4.5/rules/import/no-conversation-assistant-barrel-imports}/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
|
+
}
|