Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions baseline-export-bench.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
ÔùÅ Validation Warning:

Unknown option "reporters" with value ["default"] was found.
This is probably a typing mistake. Fixing it will remove this message.

Configuration Documentation:
https://jestjs.io/docs/configuration

console.log
[Export Management > Export detection] should detect exports x 56,768 ops/sec ┬▒2.09% (48553 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

console.log
[Export Management > Export addition] should add exports x 61,458 ops/sec ┬▒1.81% (54844 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

console.log
[Export Management > Export removal] should remove exports x 56,077 ops/sec ┬▒1.82% (50026 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

PASS benchmarks packages/workspace/src/generators/move-file/benchmarks/export-management.bench.ts
Export Management
Export detection
 should detect exports (25 ms)
Export addition
 should add exports (1 ms)
Export removal
 should remove exports

Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.658 s, estimated 5 s
Ran all test suites matching /export-management/i.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Tree } from '@nx/devkit';
import { treeReadCache } from '../tree-cache';

/**
* Cached export information for index (entrypoint) files to avoid reparsing.
*/
export interface IndexExports {
exports: Set<string>; // direct exports (file paths without extension)
reexports: Set<string>; // re-exported modules (file paths without extension)
}

interface CachedIndexExports extends IndexExports {
content: string; // content snapshot used to build this cache entry
}

// Internal cache keyed by normalized index file path
const indexExportsCache = new Map<string, CachedIndexExports>();

/** Clears all cached index export data. */
export function clearIndexExportsCache(): void {
indexExportsCache.clear();
}

/** Invalidates a single index file from the cache (e.g., after write). */
export function invalidateIndexExportsCacheEntry(indexPath: string): void {
indexExportsCache.delete(indexPath);
}

/**
* Get (and cache) export info for an index/entrypoint file.
* Lightweight regex based extraction – sufficient for current export patterns.
*/
export function getIndexExports(tree: Tree, indexPath: string): IndexExports {
const content = treeReadCache.read(tree, indexPath, 'utf-8') || '';

const cached = indexExportsCache.get(indexPath);
if (cached && cached.content === content) return cached;

const exports = new Set<string>(); // local exports (currently none parsed)
const reexports = new Set<string>(); // export ... from / export * from specifiers

// Match: export * from './path'; OR export { ... } from './path'; OR export {default as X} from './path';
const reExportPattern =
/export\s+(?:\*|\{[^}]+\})\s+from\s+['"](\.\.?\/[^'";]+)['"];?/g;
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern allows paths that don't start with './' or '../' by using \.\.?\/ which makes the second dot optional. This could match invalid paths like ./x incorrectly. The pattern should be (\.\.?\/[^'";]+) or more specifically (\.(?:\.\/|\/)[^'";]+) to properly match relative paths.

Suggested change
/export\s+(?:\*|\{[^}]+\})\s+from\s+['"](\.\.?\/[^'";]+)['"];?/g;
/export\s+(?:\*|\{[^}]+\})\s+from\s+['"](\.(?:\.\/|\/)[^'";]+)['"];?/g;

Copilot uses AI. Check for mistakes.
// Match: export * from './path'; specially capture star exports for potential future distinction
// Simple capture group for path without extension processing here

let match: RegExpExecArray | null;
while ((match = reExportPattern.exec(content))) {
const spec = match[1];
reexports.add(spec);
}

// Note: We do NOT add reexports to the exports set to avoid conflating concepts.
// exports holds normalized local export specifiers (if/when we add direct export collection logic).
// For current patterns (only re-exports), exports remains empty; callers may consult reexports directly.
// Future enhancement: parse local declarations (e.g. export { foo, bar }; without 'from').

const result: CachedIndexExports = { exports, reexports, content };
indexExportsCache.set(indexPath, result);
return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { Tree } from '@nx/devkit';
import type { ProjectConfiguration } from '@nx/devkit';
import { getProjectEntryPointPaths } from '../project-analysis/get-project-entry-point-paths';
import { removeSourceFileExtension } from '../path-utils/remove-source-file-extension';
import { escapeRegex } from '../security-utils/escape-regex';

import { treeReadCache } from '../tree-cache';
import { getIndexExports } from './index-exports-cache';

/**
* Checks if a file is exported from the project's entrypoint.
Expand Down Expand Up @@ -31,7 +32,6 @@ export function isFileExported(
const indexPaths = getProjectEntryPointPaths(tree, project);

const fileWithoutExt = removeSourceFileExtension(file);
const escapedFile = escapeRegex(fileWithoutExt);

return indexPaths.some((indexPath) => {
if (!cachedTreeExists(tree, indexPath)) {
Expand All @@ -41,12 +41,10 @@ export function isFileExported(
if (!content) {
return false;
}
// Support: export ... from "path"
// Support: export * from "path"
// Support: export { Something } from "path"
const exportPattern = new RegExp(
`export\\s+(?:\\*|\\{[^}]+\\}|.+)\\s+from\\s+['"]\\.?\\.?/.*${escapedFile}['"]`,
);
return exportPattern.test(content);
// Use cached export analysis for index file
const indexExports = getIndexExports(tree, indexPath);
// Compare against file path without extension (as stored)
// Since local exports are not yet collected, rely on reexports for detection.
return indexExports.reexports.has(`./${fileWithoutExt}`);
});
}
37 changes: 37 additions & 0 deletions updated-export-bench.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
ÔùÅ Validation Warning:

Unknown option "reporters" with value ["default"] was found.
This is probably a typing mistake. Fixing it will remove this message.

Configuration Documentation:
https://jestjs.io/docs/configuration

console.log
[Export Management > Export detection] should detect exports x 62,156 ops/sec ┬▒1.66% (55877 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

console.log
[Export Management > Export addition] should add exports x 61,446 ops/sec ┬▒1.30% (54407 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

console.log
[Export Management > Export removal] should remove exports x 62,213 ops/sec ┬▒4.02% (56818 runs sampled)

at Object.<anonymous> (tools/tinybench-utils.ts:1323:17)

PASS benchmarks packages/workspace/src/generators/move-file/benchmarks/export-management.bench.ts
Export Management
Export detection
 should detect exports (13 ms)
Export addition
 should add exports (1 ms)
Export removal
 should remove exports (1 ms)

Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.375 s, estimated 5 s
Ran all test suites matching /export-management/i.