import { computed, mapJoin, memo, SourceDirectoryContext, useContext, } from "@alloy-js/core"; import { relative } from "pathe"; import type { ImportedSymbol, ImportRecords, TSPackageScope, } from "../symbols/index.js"; import { modulePath } from "../utils.js"; import { usePackage } from "./PackageDirectory.js"; export interface ImportStatementsProps { records: ImportRecords; } export function ImportStatements(props: ImportStatementsProps) { const pkg = usePackage(); const imports = computed(() => [...props.records].sort(([a], [b]) => { return a.name.localeCompare(b.name); }), ); return mapJoin( () => imports.value, ([module, importedSymbols]) => { let targetPath: string; if (pkg?.scope !== module.parent) { // importing from another package, so let's calculate the import. const targetPackage = module.parent as TSPackageScope; let foundPath: string | false = false; for (const [ publicPath, exportedModule, ] of targetPackage.exportedSymbols) { // a module could be exported from multiple paths, so here // would be the place to handle that. if (exportedModule === module) { foundPath = publicPath; } } if (!foundPath) { throw new Error("Module not exported from package"); } targetPath = targetPackage.name + foundPath.slice(1); } else { // local package import, so need relative import const currentDir = useContext(SourceDirectoryContext)!.path; // todo: don't allow importing non-exported symbols targetPath = modulePath(relative(currentDir, module.name)); } return ; }, ); } export interface ImportStatementProps { path: string; symbols: Set; } export function ImportStatement(props: ImportStatementProps) { return memo(() => { let defaultImportSymbol: ImportedSymbol | undefined = undefined; const namedImportSymbols: ImportedSymbol[] = []; for (const sym of props.symbols) { if (sym.target.default) { defaultImportSymbol = sym; } else { namedImportSymbols.push(sym); } } const parts: any[] = ["import "]; if (defaultImportSymbol) { parts.push( , ); if (namedImportSymbols.length > 0) { parts.push(", "); } } if (namedImportSymbols.length > 0) { namedImportSymbols.sort((a, b) => { return a.local.name.localeCompare(b.local.name); }); const allNamedImportsAreTypes = namedImportSymbols.every( (nis) => nis.local.isTypeSymbol && !nis.local.isValueSymbol, ); if (allNamedImportsAreTypes) { parts.push("type "); } parts.push( {"{"} {mapJoin( () => namedImportSymbols, (nis) => ( ), { joiner: ( <> {","} ), }, )} {","} {"}"} , ); } parts.push(` from "${props.path}";`); return parts; }); } interface ImportBindingProps { importedSymbol: ImportedSymbol; default?: boolean; inTypeImport?: boolean; } function ImportBinding(props: Readonly) { const text = memo(() => { const localName = props.importedSymbol.local.name; const targetName = props.importedSymbol.target.name; if (localName === targetName) { return targetName; } else if (props.default) { return localName; } else { return `${targetName} as ${localName}`; } }); const prefix = memo(() => !props.inTypeImport && props.importedSymbol.local.isTypeSymbol && !props.importedSymbol.local.isValueSymbol ? "type " : "", ); return ( <> {prefix} {text} ); }