Files
WRNexusJS/packages/compiler/src/import-resolver.ts
T
ClintchizandClaude Opus 5 b405025f37 feat(compiler): resolve .tsx imports and tag them as islands
.wrn keeps resolution priority so existing components are unaffected
when a .tsx file shares their name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:15:08 +05:30

87 lines
2.5 KiB
TypeScript

import { existsSync, realpathSync, statSync } from "node:fs";
import { dirname, extname, join, resolve } from "node:path";
import type { StructuredImportDecl } from "@wrnexus/syntax";
export type ImportMode = "legacy" | "compatible" | "explicit";
export interface ImportResolverOptions {
appRoot: string;
mode?: ImportMode;
aliases?: Record<string, string>;
}
export interface ResolvedImport {
declaration: StructuredImportDecl;
resolved?: string;
/** `.tsx` imports are React islands, not `.wrn` components. */
kind?: "island";
diagnostic?: { code: string; message: string; severity: "error" | "warning" };
}
function candidates(path: string): string[] {
return extname(path)
? [path]
: [
path,
`${path}.wrn`,
`${path}.ts`,
`${path}.tsx`,
`${path}.d.ts`,
join(path, "index.wrn"),
join(path, "index.ts"),
join(path, "index.tsx"),
];
}
export function resolveWrnImport(
declaration: StructuredImportDecl,
importer: string,
options: ImportResolverOptions,
): ResolvedImport {
const source = declaration.source;
const aliases: Record<string, string> = {
"@": "./app",
...(options.aliases ?? {}),
};
const alias = Object.keys(aliases)
.filter((key) => key.length > 0 && (source === key || source.startsWith(`${key}/`)))
.sort((left, right) => right.length - left.length)[0];
if (!source.startsWith(".") && !alias) return { declaration, resolved: source };
const base = alias
? resolve(
options.appRoot,
aliases[alias]!,
source === alias ? "" : source.slice(alias.length + 1),
)
: resolve(dirname(importer), source);
const found = candidates(base).find((candidate) => {
if (!existsSync(candidate)) return false;
try {
return statSync(candidate).isFile();
} catch {
return false;
}
});
if (found) {
const resolved = realpathSync(found);
return resolved.endsWith(".tsx")
? { declaration, resolved, kind: "island" as const }
: { declaration, resolved };
}
const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning";
return {
declaration,
diagnostic: {
code: "WRN-IMPORT-NOT-FOUND",
message: `Cannot resolve import '${source}' from ${importer}`,
severity,
},
};
}
export function resolveWrnImports(
declarations: StructuredImportDecl[],
importer: string,
options: ImportResolverOptions,
): ResolvedImport[] {
return declarations.map((declaration) => resolveWrnImport(declaration, importer, options));
}