build: ignore type-only circular edges

This commit is contained in:
Mikael Hugo 2026-05-18 00:26:19 +02:00
parent c3b17114f3
commit 4be963fdd1

View file

@ -161,14 +161,35 @@ function extractImports(filePath) {
const visit = (node) => {
// import X from "..." | import "..." | import { X } from "..."
//
// Skip top-level type-only imports (`import type { X } from "..."`) —
// TypeScript erases them at compile time and they cannot cause runtime
// cycles. Same reasoning as skipping dynamic imports below.
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
specs.push(node.moduleSpecifier.text);
const isTypeOnly = node.importClause?.isTypeOnly === true;
if (!isTypeOnly) {
// Also skip if EVERY named specifier is marked `type` individually
// — that's the `import { type X, type Y } from "..."` shape.
const namedBindings = node.importClause?.namedBindings;
const allSpecifiersTypeOnly =
namedBindings &&
ts.isNamedImports(namedBindings) &&
namedBindings.elements.length > 0 &&
namedBindings.elements.every((e) => e.isTypeOnly === true) &&
// If there's a default-import binding (importClause.name), that's
// a runtime binding even if all named ones are type-only.
!node.importClause?.name;
if (!allSpecifiersTypeOnly) {
specs.push(node.moduleSpecifier.text);
}
}
}
// export { X } from "..." | export * from "..."
// export { X } from "..." | export * from "..." — skip `export type`
if (
ts.isExportDeclaration(node) &&
node.moduleSpecifier &&
ts.isStringLiteral(node.moduleSpecifier)
ts.isStringLiteral(node.moduleSpecifier) &&
node.isTypeOnly !== true
) {
specs.push(node.moduleSpecifier.text);
}