@safetnsr/vet 1.17.0 → 1.17.1
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/dist/checks/debt.js +39 -4
- package/package.json +1 -1
package/dist/checks/debt.js
CHANGED
|
@@ -426,14 +426,18 @@ function findOrphanedExports(cwd, files) {
|
|
|
426
426
|
// Also scan for dynamic imports: require('x'), import('x') — to catch non-static usage
|
|
427
427
|
const dynamicImportRe = /(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
428
428
|
const allSourceFiles = files.filter(f => isSourceFile(f));
|
|
429
|
+
// Track which files are re-exported via `export * from './x'`
|
|
430
|
+
const reExportedFiles = new Set();
|
|
431
|
+
const exportFromRe = /export\s+(?:type\s+)?\*\s+from\s+['"]([^'"]+)['"]/g;
|
|
432
|
+
const exportNamedFromRe = /export\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
429
433
|
for (const file of allSourceFiles) {
|
|
430
434
|
const content = readFile(join(cwd, file));
|
|
431
435
|
if (!content)
|
|
432
436
|
continue;
|
|
433
437
|
let match;
|
|
438
|
+
// Standard imports
|
|
434
439
|
importRe.lastIndex = 0;
|
|
435
440
|
while ((match = importRe.exec(content)) !== null) {
|
|
436
|
-
// Named imports: { a, b as c }
|
|
437
441
|
const namedParts = [match[1], match[3]].filter(Boolean);
|
|
438
442
|
for (const part of namedParts) {
|
|
439
443
|
for (const name of part.split(',')) {
|
|
@@ -442,10 +446,38 @@ function findOrphanedExports(cwd, files) {
|
|
|
442
446
|
importedNames.add(trimmed);
|
|
443
447
|
}
|
|
444
448
|
}
|
|
445
|
-
// Default import
|
|
446
449
|
if (match[2])
|
|
447
450
|
importedNames.add(match[2]);
|
|
448
451
|
}
|
|
452
|
+
// `export * from './module'` — all exports from that module are consumed
|
|
453
|
+
exportFromRe.lastIndex = 0;
|
|
454
|
+
while ((match = exportFromRe.exec(content)) !== null) {
|
|
455
|
+
// Resolve the re-exported file relative to current file
|
|
456
|
+
const specifier = match[1];
|
|
457
|
+
if (specifier.startsWith('.')) {
|
|
458
|
+
const dir = dirname(file);
|
|
459
|
+
const candidates = [
|
|
460
|
+
join(dir, specifier),
|
|
461
|
+
join(dir, specifier + '.ts'), join(dir, specifier + '.tsx'),
|
|
462
|
+
join(dir, specifier + '.js'), join(dir, specifier + '.jsx'),
|
|
463
|
+
join(dir, specifier, 'index.ts'), join(dir, specifier, 'index.tsx'),
|
|
464
|
+
join(dir, specifier, 'index.js'),
|
|
465
|
+
];
|
|
466
|
+
for (const c of candidates) {
|
|
467
|
+
const normalized = c.replace(/\\/g, '/');
|
|
468
|
+
reExportedFiles.add(normalized);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// `export { name } from './module'` — named re-exports count as imports
|
|
473
|
+
exportNamedFromRe.lastIndex = 0;
|
|
474
|
+
while ((match = exportNamedFromRe.exec(content)) !== null) {
|
|
475
|
+
for (const name of match[1].split(',')) {
|
|
476
|
+
const trimmed = name.trim().split(/\s+as\s+/)[0].trim();
|
|
477
|
+
if (trimmed)
|
|
478
|
+
importedNames.add(trimmed);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
449
481
|
}
|
|
450
482
|
// Build a cross-reference map: for each exported name, check if it appears in other files
|
|
451
483
|
// This catches hook returns ({ Component } = useHook()), dynamic usage, re-exports, JSX, etc.
|
|
@@ -468,14 +500,17 @@ function findOrphanedExports(cwd, files) {
|
|
|
468
500
|
const mono = isMonorepo(cwd);
|
|
469
501
|
for (const exp of exports) {
|
|
470
502
|
if (!importedNames.has(exp.name)) {
|
|
503
|
+
// Skip if the file is re-exported via `export * from './file'`
|
|
504
|
+
const normalizedFile = exp.file.replace(/\\/g, '/');
|
|
505
|
+
if (reExportedFiles.has(normalizedFile))
|
|
506
|
+
continue;
|
|
471
507
|
// Cross-reference check: if the export name appears in a different file, it's likely used
|
|
472
|
-
// (catches hook returns, JSX usage, dynamic imports, re-exports)
|
|
473
508
|
const refs = nameToFiles.get(exp.name);
|
|
474
509
|
if (refs) {
|
|
475
510
|
const otherFiles = new Set(refs);
|
|
476
511
|
otherFiles.delete(exp.file);
|
|
477
512
|
if (otherFiles.size > 0)
|
|
478
|
-
continue;
|
|
513
|
+
continue;
|
|
479
514
|
}
|
|
480
515
|
// Skip framework convention exports (Next.js, Remix, SvelteKit, Nuxt)
|
|
481
516
|
if (FRAMEWORK_CONVENTION_EXPORTS.has(exp.name) && isFrameworkConventionFile(exp.file))
|