fix(compiler): support implicit top-level await modules (#94)

* fix(compiler): support implicit top-level await modules

- Match Node 24 syntax detection by treating top-level await as an ES module marker.

- Add differential corpus coverage for TypeScript files without explicit imports or exports.

* fix Node ESM classification for top-level await

* fix ESM require diagnostics and classification caching

* fix require binding and package type precedence

* fix: align top-level await module detection with Node
This commit is contained in:
Chris Tate
2026-08-08 11:35:01 -05:00
committed by GitHub
parent d41b5dd3c3
commit 782d3b8451
34 changed files with 635 additions and 54 deletions
@@ -334,6 +334,34 @@ export function tscPassthroughDiag(message: string, loc: SrcLoc): ScrDiagnostic
return { code: "SC0001", message, loc };
}
/** Node's package scope forces CommonJS, but the source contains a marker
* that is legal only when the file is parsed as ESM (static import/export,
* import.meta, top-level await, or a lexical CommonJS-wrapper redeclaration).
* TypeScript's bundler-mode checker does not model this Node loader rule. */
export function commonJsModuleSyntaxDiag(loc: SrcLoc): ScrDiagnostic {
return {
code: "SC1013",
message: "Node classifies this file as CommonJS, where this ES-module syntax marker is a SyntaxError",
loc,
milestone: "later",
hint: "use a .mjs/.mts extension or set the nearest package.json's \"type\" to \"module\"",
};
}
/** Node stops package-scope lookup at the nearest package.json even when it
* is malformed, then refuses the module with ERR_INVALID_PACKAGE_CONFIG.
* The compiler must fail before lowering instead of inheriting a parent
* package's type and producing a runnable binary. */
export function invalidPackageConfigDiag(packageJsonPath: string, loc: SrcLoc): ScrDiagnostic {
return {
code: "SC1013",
message: `Node cannot load this module because '${packageJsonPath}' is an invalid package configuration (ERR_INVALID_PACKAGE_CONFIG)`,
loc,
milestone: "later",
hint: "fix the nearest package.json so it contains valid JSON",
};
}
/** The project's tsconfig disables strictNullChecks, which scriptc cannot
* adopt: null and undefined are DISTINCT STATIC TYPES in the value model
* (union arms with their own runtime representation) — with the knob off,
@@ -4114,7 +4114,7 @@ function isEsModuleStamp(expr: ts.Expression): boolean {
}
}
}
if (isJsSourceFile(sf) && !ts.isExternalModule(sf)) {
if (isCjsJsFile(sf)) {
const init = L.requireInitStmt(spec, expr);
if (init) return init;
return { kind: "block", body: [], loc: locOf(expr) };
+281 -46
View File
@@ -28,11 +28,10 @@
* in tsc codes at all: cjsExportDiscardReason (lowering) names every
* genuinely discarded export, so relaxing the checker's stricter
* modeling loses nothing.
* - isNodeEsmFile: 5.9.3 under bundler sets impliedNodeFormat ONLY for
* .mjs/.cjs-family files (probed) and falls back to syntactic module
* detection for .js/.ts; 7's client SourceFile has no impliedNodeFormat,
* so this lane spells exactly that observed behavior: extensions first,
* then externalModuleIndicator.
* - isNodeEsmFile: 7's client SourceFile has no impliedNodeFormat, so this
* lane spells Node's format decision directly: fixed extensions first,
* then an explicit nearest-package `type`, then syntax detection for
* ambiguous .js/.ts files.
* - Malformed tsconfig.json: tsgo's parseConfigFile swallows JSONC syntax
* errors (recovers to empty options) where 5.9.3 reported a passthrough
* diagnostic, so this lane validates the config's JSONC syntax itself
@@ -46,11 +45,13 @@ import { dirname, resolve } from "node:path";
import * as ts from "./ts7/adapter.js";
import type { ScrDiagnostic } from "../diagnostics/diagnostic.js";
import {
commonJsModuleSyntaxDiag,
invalidPackageConfigDiag,
strictNullChecksFloorDiag,
tscPassthroughDiag,
unsupportedDiag,
} from "../diagnostics/diagnostic.js";
import { isNodeModulesPath, nearestPkgJsonPath, projectDtsRuntimeSibling, resolveBareModule, resolveProjectImport, resolveRelativeModule, resolveTypeDirective, setProjectRealm } from "./resolve.js";
import { isNodeModulesPath, nearestInvalidPackageJsonPath, nearestPackageType, nearestPkgJsonPath, projectDtsRuntimeSibling, resolveBareModule, resolveProjectImport, resolveRelativeModule, resolveTypeDirective, setProjectRealm } from "./resolve.js";
import { probeNodeImportRefusal, probeNodeRequireRefusal } from "./npm.js";
import { isNpmStaticPackage, npmStaticActive, npmStaticFsShadow, npmStaticPackageOfPath, reportNpmStaticOffender, setNpmStaticPackages } from "./npm-static.js";
import { provenanceEntryFor, provenancePaths } from "./provenance-registry.js";
@@ -84,6 +85,14 @@ const FORCED_OPTIONS: ts.Ts7CompilerOptions = {
target: ts.ScriptTarget.ESNext as number,
module: ts.ModuleKind.ESNext as number,
moduleResolution: ts.ModuleResolutionKind.Bundler,
// Every file Node executes has its own lexical module wrapper: ESM has a
// module environment and CommonJS has the per-file function wrapper. In
// particular, an ambiguous file whose ONLY ESM marker is top-level await
// must not remain a TypeScript global script (otherwise declarations in
// sibling files merge and reads cross module boundaries). Runtime format
// 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,
@@ -646,15 +655,213 @@ function nestedBareRequiresOf7(sf: ts.SourceFile): ts.CallExpression[] {
return out;
}
type RequireCallBindingKind7 = "ambient" | "createRequire" | "source";
function stripRequireCasts7(expr: ts.Expression): ts.Expression {
let cur = expr;
while (
ts.isParenthesizedExpression(cur) ||
ts.isAsExpression(cur) ||
ts.isTypeAssertion(cur) ||
ts.isNonNullExpression(cur)
) {
cur = cur.expression;
}
return cur;
}
/** The supported-builtin provenance of an imported identifier, narrowed to
* the one member this preflight distinction needs. Mirrors the lowering's
* builtinImportOf alias handling so a re-export facade remains supported. */
function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): boolean {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
if (decl === undefined || !ts.isImportSpecifier(decl)) return false;
const importDecl = decl.parent.parent.parent;
if (ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
const module = canonicalBuiltinModule(importDecl.moduleSpecifier.text);
const member = decl.propertyName?.text ?? decl.name.text;
if (module === "module" && member === "createRequire") return true;
}
if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
const target = checker.getAliasedSymbol(symbol);
const targetDecl = checker.declarationsOf(target)[0];
if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) {
for (let p: ts.Node | undefined = targetDecl.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) {
return canonicalBuiltinModule(p.name.text) === "module" && target.name === "createRequire";
}
}
}
}
return false;
}
/** True only for the createRequire binding shape the lowering recognizes:
* a const initialized from node:module's createRequire with a base naming
* the current file. Arbitrary source declarations named `require` must not
* ride the module-edge lowering, which would erase their calls. */
function isCreateRequireBinding7(program: ts.Program, callee: ts.Identifier): boolean {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(callee);
const decl = symbol
? checker.declarationsOf(symbol).find((d): d is ts.VariableDeclaration => ts.isVariableDeclaration(d))
: undefined;
if (
decl === undefined ||
decl.initializer === undefined ||
!ts.isVariableDeclarationList(decl.parent) ||
(decl.parent.flags & ts.NodeFlags.Const) === 0
) {
return false;
}
const init = stripRequireCasts7(decl.initializer);
if (
!ts.isCallExpression(init) ||
init.questionDotToken !== undefined ||
!ts.isIdentifier(init.expression) ||
!isCreateRequireImport7(program, init.expression) ||
init.arguments.length !== 1
) {
return false;
}
const base = stripRequireCasts7(init.arguments[0]!);
if (ts.isIdentifier(base) && base.text === "__filename") return true;
return (
ts.isPropertyAccessExpression(base) &&
base.questionDotToken === undefined &&
ts.isMetaProperty(base.expression) &&
base.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
(base.name.text === "url" || base.name.text === "filename")
);
}
/** Classifies a require-shaped call as Node's ambient CommonJS global, the
* one supported createRequire source binding, or another source binding.
* The scanners still collect createRequire calls because they carry static
* module edges; other source bindings get a pointed fence before lowering
* can mistake them for module syntax. */
function requireCallBindingKind7(program: ts.Program, node: ts.Node): RequireCallBindingKind7 {
const call = ts.isVariableDeclaration(node) ? node.initializer : node;
if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) return "ambient";
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(call.expression);
if (symbol === undefined) return "ambient";
const decls = checker.declarationsOf(symbol);
if (decls.length === 0 || decls.every((d) => d.getSourceFile().isDeclarationFile)) return "ambient";
return isCreateRequireBinding7(program, call.expression) ? "createRequire" : "source";
}
function esmRequireFeature7(kind: Exclude<RequireCallBindingKind7, "createRequire">): string {
return kind === "ambient"
? "require() in an ES module (Node throws ReferenceError — use import)"
: "calls through a source binding named 'require' (this spelling is reserved for CommonJS/createRequire module edges — rename the binding)";
}
const CJS_WRAPPER_NAMES = new Set(["require", "module", "exports", "__dirname", "__filename"]);
function bindingDeclaresCjsWrapper7(name: ts.BindingName): ts.Identifier | null {
if (ts.isIdentifier(name)) return CJS_WRAPPER_NAMES.has(name.text) ? name : null;
for (const el of name.elements) {
if (el.name === undefined) continue;
const hit = bindingDeclaresCjsWrapper7(el.name);
if (hit !== null) return hit;
}
return null;
}
function insideFunctionLike7(node: ts.Node): boolean {
for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
if (ts.isFunctionLike(p)) return true;
}
return false;
}
/** The first syntax marker Node's ambiguous-file detector recognizes. This
* cannot use ts.isExternalModule: moduleDetection=force deliberately gives
* EVERY executable file module-local checker scope, including CommonJS.
* Runtime classification remains Node's own narrower question: value-level
* import/export forms, import.meta, top-level await/for-await, or a top-level
* lexical declaration that would redeclare a CommonJS wrapper binding. */
function nodeEsmSyntaxMarker7(sf: ts.SourceFile): ts.Node | null {
for (const stmt of sf.statements) {
if (ts.isImportDeclaration(stmt) && !erasedTypeOnlyImport(stmt)) return stmt;
if (
ts.isExportDeclaration(stmt) &&
!stmt.isTypeOnly &&
!erasedTypeOnlyReexport(stmt)
) {
return stmt;
}
if (ts.isExportAssignment(stmt)) return stmt;
if (
ts.canHaveModifiers(stmt) &&
ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true
) {
return stmt;
}
if (
ts.isVariableStatement(stmt) &&
(stmt.declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0
) {
for (const decl of stmt.declarationList.declarations) {
const hit = bindingDeclaresCjsWrapper7(decl.name);
if (hit !== null) return hit;
}
}
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined && CJS_WRAPPER_NAMES.has(stmt.name.text)) {
return stmt.name;
}
}
let found: ts.Node | null = null;
ts.walkPreorder(sf, (node) => {
if (
ts.isMetaProperty(node) &&
node.keywordToken === ts.SyntaxKind.ImportKeyword
) {
found = node;
return "stop";
}
if (
!insideFunctionLike7(node) &&
(ts.isAwaitExpression(node) ||
(ts.isForOfStatement(node) && node.awaitModifier !== undefined))
) {
found = node;
return "stop";
}
return undefined;
});
return found;
}
const nodeEsmFileCache7 = new WeakMap<ts.SourceFile, boolean>();
/** True when Node would treat this file as an ES MODULE (never defining
* require/__dirname there): .mjs always, .cjs never, and .js/.ts by
* syntactic module detection — spelled to 5.9.3's OBSERVED bundler-mode
* behavior (it set impliedNodeFormat only for the mjs/cjs families; 7's
* client SourceFile has no impliedNodeFormat at all). */
* require/__dirname there): fixed extensions first, then an explicit
* nearest-package type, then Node's syntax markers for ambiguous .js/.ts
* files. TypeScript's external-module bit is forced for checker scoping and
* therefore intentionally plays no part in this runtime decision. */
function isNodeEsmFile7(sf: ts.SourceFile): boolean {
if (sf.fileName.endsWith(".cjs") || sf.fileName.endsWith(".cts")) return false;
if (sf.fileName.endsWith(".mjs") || sf.fileName.endsWith(".mts")) return true;
return ts.isExternalModule(sf);
const cached = nodeEsmFileCache7.get(sf);
if (cached !== undefined) return cached;
let result: boolean;
if (sf.fileName.endsWith(".cjs") || sf.fileName.endsWith(".cts")) {
result = false;
} else if (sf.fileName.endsWith(".mjs") || sf.fileName.endsWith(".mts")) {
result = true;
} else {
const packageType = nearestPackageType(sf.fileName);
result = packageType === "module"
? true
: packageType === "commonjs"
? false
: nodeEsmSyntaxMarker7(sf) !== null;
}
nodeEsmFileCache7.set(sf, result);
return result;
}
/** A JavaScript source file Node treats as CommonJS (not an ES module):
@@ -1611,6 +1818,28 @@ function preflight7(load: LoadResult): {
!islandJsFile(sf.fileName),
);
// Node stops at the nearest package.json even when it is malformed, and
// an explicit CommonJS scope (or .cjs/.cts extension) disables ambiguous-
// file syntax detection entirely. TypeScript's bundler checker models
// neither loader rule, so restore them before any module-edge analysis:
// one malformed-scope diagnostic per package file, and one pointed syntax
// diagnostic per CommonJS source file.
const invalidPackageConfigs = new Set<string>();
for (const sf of userFiles) {
const invalidConfig = nearestInvalidPackageJsonPath(sf.fileName);
if (invalidConfig !== null) {
if (!invalidPackageConfigs.has(invalidConfig)) {
invalidPackageConfigs.add(invalidConfig);
diags.push(invalidPackageConfigDiag(invalidConfig, { file: sf.fileName, start: 0, end: 0 }));
}
continue;
}
if (!isNodeEsmFile7(sf)) {
const marker = nodeEsmSyntaxMarker7(sf);
if (marker !== null) diags.push(commonJsModuleSyntaxDiag(locOf7(marker)));
}
}
const ambientModules = new Set<string>(SUPPORTED_NODE_MODULES);
// Ambient `declare module "name"` declarations anywhere in the program —
@@ -2008,10 +2237,11 @@ function preflight7(load: LoadResult): {
for (const req of requiresOf7(stmt)) {
const loc = { file: sf.fileName, start: req.node.getStart(sf), end: req.node.getEnd() };
if (isNodeEsmFile7(sf)) {
diags.push(
unsupportedDiag("SC1013", loc, "require() in an ES module (Node throws ReferenceError — use import)"),
);
continue;
const bindingKind = requireCallBindingKind7(program, req.node);
if (bindingKind !== "createRequire") {
diags.push(unsupportedDiag("SC1013", loc, esmRequireFeature7(bindingKind)));
continue;
}
}
if (load.externalTypes.has(req.spec)) {
continue;
@@ -2084,38 +2314,43 @@ function preflight7(load: LoadResult): {
if (dep) deps.push({ dep });
}
}
if (!ts.isExternalModule(sf)) {
for (const call of nestedBareRequiresOf7(sf)) {
const spec = requireSpecOf7(call)!;
const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() };
if (load.externalTypes.has(spec)) {
for (const call of nestedBareRequiresOf7(sf)) {
const spec = requireSpecOf7(call)!;
const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() };
if (isNodeEsmFile7(sf)) {
const bindingKind = requireCallBindingKind7(program, call);
if (bindingKind !== "createRequire") {
diags.push(unsupportedDiag("SC1013", loc, esmRequireFeature7(bindingKind)));
continue;
}
if (!isRelativeSpecifier(spec)) {
// --npm-static: opted-in packages ride the program-module edge
// (the statement-level require branch above).
const npmReq = !spec.startsWith("#") ? resolveNpmImport7(sf.fileName, spec) : null;
if (npmReq !== null && isNpmStaticPackage(npmReq.packageName)) {
const nDep = npmStaticProgramDep(program, npmReq.packageName, npmReq.typesFile);
if (nDep !== null) deps.push({ dep: nDep });
continue;
}
if (canonicalBuiltinModule(spec) === null && !processModuleAliasRequire7(spec, null)) {
// Binding-less by construction — same require-site throw
// channel as the statement-level form above.
if (probeNodeRequireRefusal(sf.fileName, spec) === null) {
diags.push(unsupportedDiag("SC1010", loc, unsupportedModuleFeatureOf(spec)));
}
}
continue;
}
const dep = resolveImport7(program, sf, spec);
if (dep && dep.fileName.endsWith(".json")) {
diags.push(unsupportedDiag("SC1012", loc, "require() of JSON modules"));
continue;
}
if (dep) deps.push({ dep });
}
if (load.externalTypes.has(spec)) {
continue;
}
if (!isRelativeSpecifier(spec)) {
// --npm-static: opted-in packages ride the program-module edge
// (the statement-level require branch above).
const npmReq = !spec.startsWith("#") ? resolveNpmImport7(sf.fileName, spec) : null;
if (npmReq !== null && isNpmStaticPackage(npmReq.packageName)) {
const nDep = npmStaticProgramDep(program, npmReq.packageName, npmReq.typesFile);
if (nDep !== null) deps.push({ dep: nDep });
continue;
}
if (canonicalBuiltinModule(spec) === null && !processModuleAliasRequire7(spec, null)) {
// Binding-less by construction — same require-site throw
// channel as the statement-level form above.
if (probeNodeRequireRefusal(sf.fileName, spec) === null) {
diags.push(unsupportedDiag("SC1010", loc, unsupportedModuleFeatureOf(spec)));
}
}
continue;
}
const dep = resolveImport7(program, sf, spec);
if (dep && dep.fileName.endsWith(".json")) {
diags.push(unsupportedDiag("SC1012", loc, "require() of JSON modules"));
continue;
}
if (dep) deps.push({ dep });
}
}
}
+37
View File
@@ -46,6 +46,7 @@ function realpathOr(path: string): string {
interface PkgJson {
name?: string;
version?: string;
type?: string;
types?: string;
typings?: string;
main?: string;
@@ -454,6 +455,21 @@ function nearestPkgDir(dir: string): string | null {
}
}
/** The nearest package.json FILE, whether or not its JSON parses. Runtime
* module-format lookup must stop at a malformed nearest scope: Node reports
* ERR_INVALID_PACKAGE_CONFIG there rather than silently inheriting a parent
* package's `type`. The resolver's historical nearestPkgDir stays parseable-
* package-only because its callers need a package OBJECT to inspect. */
function nearestPackageConfigDir(dir: string): string | null {
let d = dir;
for (;;) {
if (existsSync(join(d, "package.json"))) return d;
const parent = dirname(d);
if (parent === d) return null;
d = parent;
}
}
/** package.json "imports" lookup — the exports machinery over "#" keys
* (exact keys, then '*' patterns, condition objects in key order). */
function resolveImportsField(imports: unknown, specifier: string): string | null {
@@ -477,6 +493,27 @@ export function nearestPkgJsonPath(fromFile: string): string | null {
return dir === null ? null : join(dir, "package.json");
}
/** The explicit module format of the nearest package scope. A package.json
* without a recognized `type` keeps the file ambiguous, so callers may
* apply Node's syntax detector; an explicit commonjs/module answer must win
* over syntax detection for ordinary .js/.ts files. */
export function nearestPackageType(fromFile: string): "module" | "commonjs" | null {
const dir = nearestPackageConfigDir(dirname(resolve(fromFile)));
if (dir === null) return null;
const type = pkgJsonOf(dir)?.type;
return type === "module" || type === "commonjs" ? type : null;
}
/** The malformed nearest package scope Node would reject before loading
* `fromFile`, or null when there is no package.json or it parses as an
* object. Kept separate from nearestPackageType so an absent/typeless valid
* package can still use syntax detection. */
export function nearestInvalidPackageJsonPath(fromFile: string): string | null {
const dir = nearestPackageConfigDir(dirname(resolve(fromFile)));
if (dir === null || pkgJsonOf(dir) !== null) return null;
return join(dir, "package.json");
}
/** Resolves a PROJECT-INTERNAL package.json-mediated specifier from
* `fromFile` — `#alias` (the imports field) or a self-name reference (the
* nearest package.json's own name through its exports) — to an absolute
+19 -6
View File
@@ -8,12 +8,13 @@
* this module.
*
* Placement in 7.0.2 (probed): the AST-world enums ship from unstable/ast,
* the checker-world enums from unstable/sync. ModuleResolutionKind is the one
* census enum the package does not re-export from any public entry point —
* it lives in dist/enums (the #enums/* internal imports), which the exports
* map hides from bare-specifier imports but NOT from a direct file require;
* loadHiddenEnum resolves the package root and requires the enum module by
* path, so the values are still TypeScript 7's own, never copied numbers. */
* the checker-world enums from unstable/sync. ModuleResolutionKind and
* ModuleDetectionKind are the census/options enums the package does not
* re-export from any public entry point — they live in dist/enums (the
* #enums/* internal imports), which the exports map hides from bare-
* specifier imports but NOT from a direct file require; loadHiddenEnum
* resolves the package root and requires the enum module by path, so the
* values are still TypeScript 7's own, never copied numbers. */
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
@@ -74,6 +75,18 @@ export const ModuleResolutionKind: ModuleResolutionKindEnum =
loadHiddenEnum<ModuleResolutionKindEnum>("moduleResolutionKind", "ModuleResolutionKind");
export type ModuleResolutionKind = number;
interface ModuleDetectionKindEnum {
readonly None: number;
readonly Auto: number;
readonly Legacy: number;
readonly Force: number;
readonly [key: string | number]: string | number;
}
export const ModuleDetectionKind: ModuleDetectionKindEnum =
loadHiddenEnum<ModuleDetectionKindEnum>("moduleDetectionKind", "ModuleDetectionKind");
export type ModuleDetectionKind = 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
@@ -26,7 +26,7 @@ import type {
} from "typescript/unstable/sync";
import type { SourceFile } from "typescript/unstable/ast";
import { CheckerFacade } from "./checker.js";
import { enumKeyOf, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js";
import { enumKeyOf, ModuleDetectionKind, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js";
import { tsgoPath } from "../shared.js";
/** The compiler options our createProgram accepts: TS7's CompilerOptions
@@ -61,6 +61,9 @@ function serializeOptions(options: Ts7CompilerOptions): Record<string, unknown>
case "moduleResolution":
out[key] = enumKeyOf(ModuleResolutionKind as never, value as number)?.toLowerCase() ?? value;
break;
case "moduleDetection":
out[key] = enumKeyOf(ModuleDetectionKind as never, value as number)?.toLowerCase() ?? value;
break;
case "lib":
// 5.9.3 spells lib entries "lib.es2025.d.ts"; tsconfig wants "es2025".
out[key] = (value as string[]).map((lib) =>
@@ -5476,6 +5476,44 @@
],
"diags": []
},
"<repo>/tests/corpus/2672-http-request-response-callback.ts": {
"order": [
"<repo>/tests/corpus/2672-http-request-response-callback.ts"
],
"diags": []
},
"<repo>/tests/corpus/2673-top-level-await-implicit-module.ts": {
"order": [
"<repo>/tests/corpus/2673-top-level-await-implicit-module.ts"
],
"diags": []
},
"<repo>/tests/corpus/2674-top-level-for-await-implicit-module.ts": {
"order": [
"<repo>/tests/corpus/2674-top-level-for-await-implicit-module.ts"
],
"diags": []
},
"<repo>/tests/corpus/2675-create-require-nested-esm.js": {
"order": [
"<repo>/tests/corpus/2675-create-require-nested-esm.js"
],
"diags": []
},
"<repo>/tests/corpus/2676-top-level-await-ambiguous/main.ts": {
"order": [
"<repo>/tests/corpus/2676-top-level-await-ambiguous/main.ts"
],
"diags": []
},
"<repo>/tests/corpus/2677-top-level-await-module-scope/main.ts": {
"order": [
"<repo>/tests/corpus/2677-top-level-await-module-scope/a.ts",
"<repo>/tests/corpus/2677-top-level-await-module-scope/b.ts",
"<repo>/tests/corpus/2677-top-level-await-module-scope/main.ts"
],
"diags": []
},
"<repo>/tests/corpus/300-if-else.ts": {
"order": [
"<repo>/tests/corpus/300-if-else.ts"
@@ -6759,6 +6797,24 @@
],
"diags": []
},
"<repo>/tests/diagnostics/esm-syntax-commonjs/main.js": {
"order": [
"<repo>/tests/diagnostics/esm-syntax-commonjs/main.js"
],
"diags": [
{
"code": "SC1013",
"message": "Node classifies this file as CommonJS, where this ES-module syntax marker is a SyntaxError",
"loc": {
"file": "<repo>/tests/diagnostics/esm-syntax-commonjs/main.js",
"start": 0,
"end": 10
},
"milestone": "later",
"hint": "use a .mjs/.mts extension or set the nearest package.json's \"type\" to \"module\""
}
]
},
"<repo>/tests/diagnostics/exceptions.ts": {
"order": [
"<repo>/tests/diagnostics/exceptions.ts"
@@ -7020,6 +7076,24 @@
],
"diags": []
},
"<repo>/tests/diagnostics/invalid-package-config/main.ts": {
"order": [
"<repo>/tests/diagnostics/invalid-package-config/main.ts"
],
"diags": [
{
"code": "SC1013",
"message": "Node cannot load this module because '<repo>/tests/diagnostics/invalid-package-config/package.json' is an invalid package configuration (ERR_INVALID_PACKAGE_CONFIG)",
"loc": {
"file": "<repo>/tests/diagnostics/invalid-package-config/main.ts",
"start": 0,
"end": 0
},
"milestone": "later",
"hint": "fix the nearest package.json so it contains valid JSON"
}
]
},
"<repo>/tests/diagnostics/island-boundary-slots.ts": {
"order": [
"<repo>/tests/diagnostics/island-boundary-slots.ts"
@@ -7099,6 +7173,23 @@
],
"diags": []
},
"<repo>/tests/diagnostics/local-require-binding-esm.js": {
"order": [
"<repo>/tests/diagnostics/local-require-binding-esm.js"
],
"diags": [
{
"code": "SC1013",
"message": "calls through a source binding named 'require' (this spelling is reserved for CommonJS/createRequire module edges — rename the binding) is not supported yet",
"loc": {
"file": "<repo>/tests/diagnostics/local-require-binding-esm.js",
"start": 80,
"end": 100
},
"milestone": "later"
}
]
},
"<repo>/tests/diagnostics/maps-surface.ts": {
"order": [
"<repo>/tests/diagnostics/maps-surface.ts"
@@ -7491,6 +7582,59 @@
],
"diags": []
},
"<repo>/tests/diagnostics/top-level-await-commonjs/main.ts": {
"order": [
"<repo>/tests/diagnostics/top-level-await-commonjs/main.ts"
],
"diags": [
{
"code": "SC1013",
"message": "Node classifies this file as CommonJS, where this ES-module syntax marker is a SyntaxError",
"loc": {
"file": "<repo>/tests/diagnostics/top-level-await-commonjs/main.ts",
"start": 15,
"end": 40
},
"milestone": "later",
"hint": "use a .mjs/.mts extension or set the nearest package.json's \"type\" to \"module\""
}
]
},
"<repo>/tests/diagnostics/top-level-await-implicit-module-nested-require.js": {
"order": [
"<repo>/tests/diagnostics/top-level-await-implicit-module-nested-require.js"
],
"diags": [
{
"code": "SC1013",
"message": "require() in an ES module (Node throws ReferenceError — use import) is not supported yet",
"loc": {
"file": "<repo>/tests/diagnostics/top-level-await-implicit-module-nested-require.js",
"start": 114,
"end": 134
},
"milestone": "later"
}
]
},
"<repo>/tests/diagnostics/top-level-await-module-leak/main.ts": {
"order": [
"<repo>/tests/diagnostics/top-level-await-module-leak/a.ts",
"<repo>/tests/diagnostics/top-level-await-module-leak/b.ts",
"<repo>/tests/diagnostics/top-level-await-module-leak/main.ts"
],
"diags": [
{
"code": "SC0001",
"message": "Cannot find name 'shared'.",
"loc": {
"file": "<repo>/tests/diagnostics/top-level-await-module-leak/b.ts",
"start": 37,
"end": 43
}
}
]
},
"<repo>/tests/diagnostics/tuples.ts": {
"order": [
"<repo>/tests/diagnostics/tuples.ts"
@@ -0,0 +1,4 @@
const first = await Promise.resolve(20);
const second = await Promise.resolve(22);
console.log(first + second);
@@ -0,0 +1,4 @@
let chunks = 0;
for await (const chunk of process.stdin) chunks += chunk.length;
console.log(chunks);
@@ -0,0 +1,16 @@
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
function load() {
// A createRequire binding is available in ESM even though Node's ambient
// CommonJS require global is not.
try {
require("surely-not-installed-anywhere-pr94");
} catch {
return "missing";
}
return "SHOULD NOT PRINT";
}
console.log(load());
@@ -0,0 +1,5 @@
// @exit: 1
const answer = await Promise.resolve(42);
console.log(answer);
process.exit(1);
@@ -0,0 +1 @@
{}
@@ -0,0 +1,3 @@
const value = await Promise.resolve("a");
console.log(value);
@@ -0,0 +1,3 @@
const value = await Promise.resolve("b");
console.log(value);
@@ -0,0 +1,2 @@
import "./a.ts";
import "./b.ts";
@@ -0,0 +1,3 @@
{
"type": "module"
}
@@ -0,0 +1,4 @@
export {};
require("node:path");
console.log("SHOULD NOT RUN");
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
@@ -0,0 +1,2 @@
await Promise.resolve();
console.log("SHOULD NOT RUN");
@@ -0,0 +1,2 @@
{
"type": "module"
@@ -0,0 +1,7 @@
export {};
function require(specifier) {
console.log("local", specifier);
}
require("node:path");
@@ -0,0 +1,2 @@
const answer = await Promise.resolve(42);
console.log(answer);
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
@@ -0,0 +1,8 @@
await Promise.resolve();
function load() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require("node:path");
}
load();
@@ -0,0 +1,2 @@
await Promise.resolve();
var shared = 42;
@@ -0,0 +1,3 @@
await Promise.resolve();
console.log(shared);
console.log("SHOULD NOT RUN");
@@ -0,0 +1,2 @@
import "./a.ts";
import "./b.ts";
@@ -0,0 +1,3 @@
{
"type": "module"
}
@@ -0,0 +1,7 @@
esm-syntax-commonjs/main.js:1:1 - error SC1013: Node classifies this file as CommonJS, where this ES-module syntax marker is a SyntaxError
1 | export {};
| ^~~~~~~~~~
2 | require("node:path");
hint: use a .mjs/.mts extension or set the nearest package.json's "type" to "module"
@@ -0,0 +1,7 @@
invalid-package-config/main.ts:1:1 - error SC1013: Node cannot load this module because 'invalid-package-config/package.json' is an invalid package configuration (ERR_INVALID_PACKAGE_CONFIG)
1 | await Promise.resolve();
| ^
2 | console.log("SHOULD NOT RUN");
hint: fix the nearest package.json so it contains valid JSON
@@ -0,0 +1,6 @@
local-require-binding-esm.js:7:1 - error SC1013: calls through a source binding named 'require' (this spelling is reserved for CommonJS/createRequire module edges — rename the binding) is not supported yet
6 |
7 | require("node:path");
| ^~~~~~~~~~~~~~~~~~~~
8 |
@@ -0,0 +1,7 @@
top-level-await-commonjs/main.ts:1:16 - error SC1013: Node classifies this file as CommonJS, where this ES-module syntax marker is a SyntaxError
1 | const answer = await Promise.resolve(42);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
2 | console.log(answer);
hint: use a .mjs/.mts extension or set the nearest package.json's "type" to "module"
@@ -0,0 +1,6 @@
top-level-await-implicit-module-nested-require.js:5:3 - error SC1013: require() in an ES module (Node throws ReferenceError — use import) is not supported yet
4 | // eslint-disable-next-line @typescript-eslint/no-require-imports
5 | require("node:path");
| ^~~~~~~~~~~~~~~~~~~~
6 | }
@@ -0,0 +1,6 @@
top-level-await-module-leak/b.ts:2:13 - error SC0001: Cannot find name 'shared'.
1 | await Promise.resolve();
2 | console.log(shared);
| ^~~~~~
3 | console.log("SHOULD NOT RUN");