Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion packages/compiler/src/frontend/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* that path (no snapshot pins it). */

import { builtinModules } from "node:module";
import { dirname, resolve } from "node:path";
import { dirname, isAbsolute, join, resolve } from "node:path";
import * as ts from "./ts7/adapter.js";
import type { ScrDiagnostic } from "../diagnostics/diagnostic.js";
import {
Expand Down Expand Up @@ -168,6 +168,36 @@ function adoptProjectConfig7(
const value = parsed.options[key];
if (value !== undefined) adopted[key] = value;
}
// `paths` is a real, well-supported tsgo checker option, but `baseUrl`
// itself is not — tsgo rejects it outright ("Option 'baseUrl' has been
// removed. Please remove it from your configuration. Use '"paths": {"*":
// ["./*"]}' instead."), so it never joins `adopted` on its own. The
// synthesized virtual tsconfig (ts7/program.ts) is also written BESIDE THE
// ENTRY FILE, not beside this real tsconfig.json, so relative `paths`
// targets can't simply pass through either: they're resolved to absolute
// paths here, against the real config's resolved `baseUrl` (tsgo's own
// parser already makes that absolute — hence the isAbsolute guard rather
// than a bare join) or, absent one, the config's own directory (tsc's
// default), or tsgo resolves them against the wrong base entirely.
const rawPaths = parsed.options["paths"];
if (rawPaths !== undefined && typeof rawPaths === "object" && rawPaths !== null) {
const configDir = dirname(configFile);
const rawBaseUrl = parsed.options["baseUrl"];
const base =
typeof rawBaseUrl !== "string"
? configDir
: isAbsolute(rawBaseUrl)
? rawBaseUrl
: join(configDir, rawBaseUrl);
const abs = (p: string): string => (isAbsolute(p) ? p : join(base, p));
const paths: Record<string, string[]> = {};
for (const [key, value] of Object.entries(rawPaths as Record<string, unknown>)) {
if (Array.isArray(value)) {
paths[key] = value.filter((v): v is string => typeof v === "string").map(abs);
}
}
adopted["paths"] = paths;
}
const nullChecks = adopted["strictNullChecks"] ?? adopted["strict"] ?? false;
if (nullChecks !== true) {
diags.push(strictNullChecksFloorDiag(configFile));
Expand Down
31 changes: 30 additions & 1 deletion packages/compiler/test/ts7/program.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
Expand Down Expand Up @@ -67,3 +67,32 @@ console.log(required.value);
rmSync(dir, { recursive: true, force: true });
}
});

test("adopts tsconfig paths/baseUrl so tsgo resolves aliased imports", () => {
const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp";
const dir = mkdtempSync(join(tempRoot, "scriptc-preflight-paths-"));
writeFileSync(
join(dir, "tsconfig.json"),
JSON.stringify({
compilerOptions: { strictNullChecks: true, baseUrl: ".", paths: { "@/*": ["./src/*"] } },
}),
);
const srcDir = join(dir, "src");
mkdirSync(srcDir);
writeFileSync(join(dir, "entry.ts"), `import { value } from "@/dep";\nconsole.log(value);\n`);
writeFileSync(join(srcDir, "dep.ts"), "export const value = 1;\n");
const entry = join(dir, "entry.ts");

const load = loadProgram(entry);
try {
// Before the fix, tsgo never learns about `paths`/`baseUrl` and reports
// SC0001 "Cannot find module '@/dep'". SC1010 (own resolver has no
// opinion on bare-specifier aliases outside npm/imports-field) is a
// separate, pre-existing limitation and is unaffected by this fix.
const codes = checkPreflight(load).map((diag) => diag.code);
expect(codes).not.toContain("SC0001");
} finally {
load.dispose();
rmSync(dir, { recursive: true, force: true });
}
});