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>
This commit is contained in:
@@ -11,6 +11,8 @@ export interface ImportResolverOptions {
|
|||||||
export interface ResolvedImport {
|
export interface ResolvedImport {
|
||||||
declaration: StructuredImportDecl;
|
declaration: StructuredImportDecl;
|
||||||
resolved?: string;
|
resolved?: string;
|
||||||
|
/** `.tsx` imports are React islands, not `.wrn` components. */
|
||||||
|
kind?: "island";
|
||||||
diagnostic?: { code: string; message: string; severity: "error" | "warning" };
|
diagnostic?: { code: string; message: string; severity: "error" | "warning" };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,9 +23,11 @@ function candidates(path: string): string[] {
|
|||||||
path,
|
path,
|
||||||
`${path}.wrn`,
|
`${path}.wrn`,
|
||||||
`${path}.ts`,
|
`${path}.ts`,
|
||||||
|
`${path}.tsx`,
|
||||||
`${path}.d.ts`,
|
`${path}.d.ts`,
|
||||||
join(path, "index.wrn"),
|
join(path, "index.wrn"),
|
||||||
join(path, "index.ts"),
|
join(path, "index.ts"),
|
||||||
|
join(path, "index.tsx"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +60,12 @@ export function resolveWrnImport(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (found) return { declaration, resolved: realpathSync(found) };
|
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";
|
const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning";
|
||||||
return {
|
return {
|
||||||
declaration,
|
declaration,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { resolveWrnImport } from "../src/import-resolver.ts";
|
||||||
|
|
||||||
|
function appWith(files: Record<string, string>) {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-island-"));
|
||||||
|
mkdirSync(join(root, "app"), { recursive: true });
|
||||||
|
for (const [name, contents] of Object.entries(files)) {
|
||||||
|
writeFileSync(join(root, "app", name), contents);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("resolves a .tsx import and tags it as an island", () => {
|
||||||
|
const root = appWith({ "Chart.tsx": "export default function Chart() { return null; }" });
|
||||||
|
const result = resolveWrnImport(
|
||||||
|
{ source: "./Chart", specifiers: [] } as any,
|
||||||
|
join(root, "app", "page.wrn"),
|
||||||
|
{ appRoot: root },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.resolved).toContain("Chart.tsx");
|
||||||
|
expect(result.kind).toBe("island");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not tag a .ts import as an island", () => {
|
||||||
|
const root = appWith({ "helper.ts": "export const value = 1;" });
|
||||||
|
const result = resolveWrnImport(
|
||||||
|
{ source: "./helper", specifiers: [] } as any,
|
||||||
|
join(root, "app", "page.wrn"),
|
||||||
|
{ appRoot: root },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.resolved).toContain("helper.ts");
|
||||||
|
expect(result.kind).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prefers .wrn over .tsx when both exist", () => {
|
||||||
|
const root = appWith({
|
||||||
|
"Widget.wrn": "<template></template>",
|
||||||
|
"Widget.tsx": "export default function Widget() { return null; }",
|
||||||
|
});
|
||||||
|
const result = resolveWrnImport(
|
||||||
|
{ source: "./Widget", specifiers: [] } as any,
|
||||||
|
join(root, "app", "page.wrn"),
|
||||||
|
{ appRoot: root },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.resolved).toContain("Widget.wrn");
|
||||||
|
expect(result.kind).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves an explicit .tsx extension as an island", () => {
|
||||||
|
const root = appWith({ "Chart.tsx": "export default function Chart() { return null; }" });
|
||||||
|
const result = resolveWrnImport(
|
||||||
|
{ source: "./Chart.tsx", specifiers: [] } as any,
|
||||||
|
join(root, "app", "page.wrn"),
|
||||||
|
{ appRoot: root },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.kind).toBe("island");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user