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
23 changes: 22 additions & 1 deletion packages/compiler/src/frontend/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import { trackedFileExists } from "./input-tracker.js";

const BASE_OPTIONS: ts.Ts7CompilerOptions = {
strict: true,
// Default lib surface — overridden below by the project's own tsconfig
// `lib` when it sets one (BASE_OPTIONS loses to `adopted` in the merge).
// A project that never declares `lib` keeps exactly this narrow, no-DOM
// default.
lib: ["lib.es2025.d.ts"],
};

const FORCED_OPTIONS: ts.Ts7CompilerOptions = {
Expand All @@ -93,7 +98,6 @@ const FORCED_OPTIONS: ts.Ts7CompilerOptions = {
// classification is intentionally independent below; this option is only
// the binder's scope decision.
moduleDetection: ts.ModuleDetectionKind.Force,
lib: ["lib.es2025.d.ts"],
types: [],
allowImportingTsExtensions: true,
allowJs: true,
Expand Down Expand Up @@ -168,6 +172,23 @@ function adoptProjectConfig7(
const value = parsed.options[key];
if (value !== undefined) adopted[key] = value;
}
const rawJsx = parsed.options["jsx"];
if (typeof rawJsx === "number") adopted["jsx"] = rawJsx;
const rawJsxImportSource = parsed.options["jsxImportSource"];
if (typeof rawJsxImportSource === "string") adopted["jsxImportSource"] = rawJsxImportSource;
// A project's own `lib` choice (e.g. `dom` for code that reuses browser
// component prop types via `import type`, even where the runtime path is
// dead for a given compile target) was previously unreachable: `lib` was
// FORCED to a narrow no-DOM default with no override. Adopting it here
// (BASE_OPTIONS still supplies that narrow default for projects that never
// set `lib`) lets a whole-program compile satisfy type-only-imported code
// outside its own reachable surface without every such project needing to
// avoid `import type` reuse across platform-specific implementations.
const rawLib = parsed.options["lib"];
if (Array.isArray(rawLib)) {
const lib = rawLib.filter((v): v is string => typeof v === "string");
if (lib.length > 0) adopted["lib"] = lib;
}
const nullChecks = adopted["strictNullChecks"] ?? adopted["strict"] ?? false;
if (nullChecks !== true) {
diags.push(strictNullChecksFloorDiag(configFile));
Expand Down
21 changes: 21 additions & 0 deletions packages/compiler/src/frontend/ts7/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ export const ModuleDetectionKind: ModuleDetectionKindEnum =
loadHiddenEnum<ModuleDetectionKindEnum>("moduleDetectionKind", "ModuleDetectionKind");
export type ModuleDetectionKind = number;

/* JsxEmit isn't re-exported from unstable/ast or unstable/sync either — same
* hidden dist/enums placement as ModuleResolutionKind/ModuleDetectionKind
* above. Worth calling out by name: 7.0.2 renumbers React/ReactNative
* relative to 5.9.3 (None=0 Preserve=1 ReactNative=2 React=3 ReactJSX=4
* ReactJSXDev=5, vs 5.9.3's React=2/ReactNative=3) — exactly the silent-lie
* risk this file's own top comment warns about, so this goes through the
* same symbolic reverse-mapping as everything else here rather than a
* hardcoded positional table. */
interface JsxEmitEnum {
readonly None: number;
readonly Preserve: number;
readonly React: number;
readonly ReactNative: number;
readonly ReactJSX: number;
readonly ReactJSXDev: number;
readonly [key: string | number]: string | number;
}

export const JsxEmit: JsxEmitEnum = loadHiddenEnum<JsxEmitEnum>("jsxEmit", "JsxEmit");
export type JsxEmit = number;

/** Reverse-maps a numeric enum value to its TS7 key name ("ESNext",
* "Bundler") — the spelling tsgo's tsconfig JSON parser accepts (lowercased
* by the caller where needed). Symbolic by construction: the name comes from
Expand Down
25 changes: 24 additions & 1 deletion packages/compiler/src/frontend/ts7/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type {
} from "typescript/unstable/sync";
import type { SourceFile } from "typescript/unstable/ast";
import { CheckerFacade } from "./checker.js";
import { enumKeyOf, ModuleDetectionKind, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js";
import { enumKeyOf, JsxEmit, ModuleDetectionKind, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js";
import { tsgoPath } from "../shared.js";
import { trackedAccessibleEntries, trackedDirectoryExists, trackedFileExists, trackedReadFile, trackedRealpath } from "../input-tracker.js";

Expand Down Expand Up @@ -71,6 +71,29 @@ function serializeOptions(options: Ts7CompilerOptions): Record<string, unknown>
lib.startsWith("lib.") && lib.endsWith(".d.ts") ? lib.slice(4, -5) : lib,
);
break;
// Same reverse-mapping need as target/module/moduleResolution/
// moduleDetection above — JsxEmit is one more hidden enum (enums.ts),
// reverse-mapped the same symbolic way rather than a hardcoded
// positional table (7.0.2 renumbers React/ReactNative relative to
// 5.9.3; see enums.ts's JsxEmit comment). Unlike the other enums here,
// JsxEmit's tsconfig spelling isn't a plain lowercase of its key
// (ReactNative -> "react-native", ReactJSX -> "react-jsx", ReactJSXDev
// -> "react-jsxdev") — the enumKeyOf lookup still comes from the
// enum's own symbolic reverse mapping (never a hardcoded number), only
// the KEY-NAME-TO-SPELLING step below is a fixed table.
case "jsx": {
const jsxKey = enumKeyOf(JsxEmit as never, value as number);
const jsxSpelling: Record<string, string> = {
None: "none",
Preserve: "preserve",
React: "react",
ReactNative: "react-native",
ReactJSX: "react-jsx",
ReactJSXDev: "react-jsxdev",
};
out[key] = (jsxKey && jsxSpelling[jsxKey]) ?? value;
break;
}
default: {
if (typeof value === "number" && key !== "maxNodeModuleJsDepth") {
throw new Error(`ts7 createProgram: unhandled enum-valued compiler option '${key}'`);
Expand Down