mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Fix get-or-create records, local-class type queries, builtin typeof and CommonJS top-level this (#1303)
This commit is contained in:
@@ -6415,7 +6415,7 @@ export function collectInheritedJsFieldWrites(
|
||||
* hierarchy identifies the original declarations through aliases as well. */
|
||||
export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.SourceFile[]): void {
|
||||
const families: [ts.MethodDeclaration, ts.MethodDeclaration][] = [];
|
||||
collectClassMethodMutations(lowerer, files);
|
||||
lowerer.speculativeTypeQueries(() => collectClassMethodMutations(lowerer, files));
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
||||
const accessors = node.members.filter(
|
||||
|
||||
@@ -203,6 +203,7 @@ import { tryLowerExpression } from "./expressions/try-lower-expression.js";
|
||||
import {
|
||||
fenceNodeModuleMutation,
|
||||
isNodeModuleValue,
|
||||
lowerCjsTopLevelThis,
|
||||
lowerNodeModuleIdentifier,
|
||||
lowerNodeModuleProperty,
|
||||
lowerRequireCacheElement,
|
||||
@@ -690,6 +691,30 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
if (!sym) {
|
||||
return { kind: "strLit", value: "undefined", type: STRING, loc };
|
||||
}
|
||||
// `const { inspect } = require("util")` / `import { inspect } from
|
||||
// "node:util"`: a binding of a builtin module's exported function,
|
||||
// class or object. JavaScript sources represent such a value without
|
||||
// its own lowering as an identity token (a string), so the operand's
|
||||
// representation cannot answer. Node's answer follows the export's
|
||||
// kind, and the binding read has no effects.
|
||||
const imported = isJsSourceFile(tested.getSourceFile())
|
||||
? lowerer.builtinImportOf(tested)
|
||||
: null;
|
||||
if (
|
||||
imported &&
|
||||
builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined
|
||||
) {
|
||||
const t = lowerer.typeOf(tested);
|
||||
if (
|
||||
lowerer.checker.getCallSignatures(t).length > 0 ||
|
||||
lowerer.checker.getConstructSignatures(t).length > 0
|
||||
) {
|
||||
return { kind: "strLit", value: "function", type: STRING, loc };
|
||||
}
|
||||
if ((t.flags & ts.TypeFlags.Object) !== 0) {
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
}
|
||||
}
|
||||
if (lowerer.isStdlibSymbol(sym)) {
|
||||
const t = lowerer.typeOf(expr.expression);
|
||||
if (
|
||||
@@ -1661,6 +1686,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
// constant. TypeScript keeps the fence (noImplicitThis makes it a
|
||||
// compile-time story there).
|
||||
if (isJsSourceFile(expr.getSourceFile())) {
|
||||
// Module scope of a CommonJS file: `this` is module.exports.
|
||||
const exportsThis = lowerCjsTopLevelThis(lowerer, expr);
|
||||
if (exportsThis) return exportsThis;
|
||||
return { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc };
|
||||
}
|
||||
lowerer.unsupported("SC1080", expr);
|
||||
|
||||
@@ -2664,6 +2664,17 @@ export function lowerFileInit(
|
||||
loc: loc0,
|
||||
});
|
||||
}
|
||||
// Top-level `this` of a CommonJS module: its object exists before the
|
||||
// module's first statement (lowerCjsTopLevelThis).
|
||||
const exportsThis = lowerer.cjsExportsThis.get(sf);
|
||||
if (exportsThis) {
|
||||
header.push({
|
||||
kind: "assign",
|
||||
localId: exportsThis.id,
|
||||
value: { kind: "dynObjLit", fields: [], type: DYN, loc: loc0 },
|
||||
loc: loc0,
|
||||
});
|
||||
}
|
||||
for (const { stmt, dep } of orderedImportsOf(lowerer.program, sf)) {
|
||||
const npm = lowerer.npmInitActions.get(sf)?.get(stmt);
|
||||
if (npm !== undefined && npm.length > 0) {
|
||||
|
||||
@@ -8,10 +8,11 @@ import * as win32 from "node:path/win32";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import type { FileParts } from "./lower-modules.js";
|
||||
import { isNodeEsmFile, locOf } from "../program.js";
|
||||
import { isCjsJsFile, isNodeEsmFile, locOf } from "../program.js";
|
||||
import { runtimePathForTarget } from "../runtime-resolve.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
type IrExpr,
|
||||
type IrStmt,
|
||||
@@ -224,6 +225,135 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
return rendered === "ScriptcModule" || rendered === "NodeModule" || rendered === "NodeJS.Module";
|
||||
}
|
||||
|
||||
/** `module.exports` used only for its truthiness (the UMD test `typeof
|
||||
* module !== "undefined" && module.exports`): the exports object starts as
|
||||
* a fresh object, so it stays truthy unless the module replaces it with a
|
||||
* value that can be falsy. True when every `module.exports = value` in the
|
||||
* file assigns an object or function. */
|
||||
function moduleExportsAlwaysTruthy(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean {
|
||||
if (!truthinessOnly(expr)) return false;
|
||||
const sf = expr.getSourceFile();
|
||||
if (isNodeEsmFile(sf, lowerer.program)) return false;
|
||||
let truthy = true;
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (
|
||||
!ts.isBinaryExpression(node) ||
|
||||
node.operatorToken.kind < ts.SyntaxKind.FirstAssignment ||
|
||||
node.operatorToken.kind > ts.SyntaxKind.LastAssignment
|
||||
)
|
||||
return undefined;
|
||||
let target = node.left;
|
||||
while (ts.isParenthesizedExpression(target)) target = target.expression;
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(target) ||
|
||||
target.name.text !== "exports" ||
|
||||
!isCjsModuleGlobal(lowerer, target.expression)
|
||||
)
|
||||
return undefined;
|
||||
const type = lowerer.typeOf(node.right);
|
||||
const objectLike = (t: ts.Type): boolean =>
|
||||
t.isUnionType()
|
||||
? ts.constituentTypes(t).every(objectLike)
|
||||
: (t.flags & ts.TypeFlags.Object) !== 0 || (t.flags & ts.TypeFlags.NonPrimitive) !== 0;
|
||||
if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken || !objectLike(type)) {
|
||||
truthy = false;
|
||||
return "stop";
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return truthy;
|
||||
}
|
||||
|
||||
/** Whether only the truthiness of `node` can be observed: a condition, a
|
||||
* `!` operand, or a `&&`/`||` operand of an expression that is itself used
|
||||
* only for its truthiness. */
|
||||
function truthinessOnly(node: ts.Expression): boolean {
|
||||
let child: ts.Node = node;
|
||||
for (let parent = node.parent; ; child = parent, parent = parent.parent) {
|
||||
if (ts.isParenthesizedExpression(parent)) continue;
|
||||
if (ts.isPrefixUnaryExpression(parent))
|
||||
return parent.operator === ts.SyntaxKind.ExclamationToken;
|
||||
if (
|
||||
ts.isBinaryExpression(parent) &&
|
||||
(parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
||||
parent.operatorToken.kind === ts.SyntaxKind.BarBarToken)
|
||||
)
|
||||
continue;
|
||||
if (ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent))
|
||||
return parent.expression === child;
|
||||
if (ts.isForStatement(parent)) return parent.condition === child;
|
||||
if (ts.isConditionalExpression(parent)) return parent.condition === child;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** `this` at the top level of a CommonJS module (directly, or inside
|
||||
* arrow functions there): Node calls the module wrapper with `this` bound
|
||||
* to the module's original exports object. */
|
||||
export function isCjsTopLevelThis(lowerer: Lowerer, node: ts.Node): boolean {
|
||||
if (node.kind !== ts.SyntaxKind.ThisKeyword) return false;
|
||||
for (let p: ts.Node | undefined = node.parent; p && !ts.isSourceFile(p); p = p.parent) {
|
||||
if (ts.isArrowFunction(p)) continue;
|
||||
if (
|
||||
ts.isFunctionLike(p) ||
|
||||
ts.isClassDeclaration(p) ||
|
||||
ts.isClassExpression(p) ||
|
||||
ts.isClassStaticBlockDeclaration(p)
|
||||
)
|
||||
return false;
|
||||
}
|
||||
return isCjsJsFile(node.getSourceFile(), lowerer.program);
|
||||
}
|
||||
|
||||
export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolean {
|
||||
if (!isCjsJsFile(sf, lowerer.program)) return false;
|
||||
let found = false;
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (
|
||||
node !== sf &&
|
||||
!ts.isArrowFunction(node) &&
|
||||
(ts.isFunctionLike(node) ||
|
||||
ts.isClassDeclaration(node) ||
|
||||
ts.isClassExpression(node) ||
|
||||
ts.isClassStaticBlockDeclaration(node))
|
||||
)
|
||||
return "skip";
|
||||
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
||||
found = true;
|
||||
return "stop";
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/** The value of top-level `this` in a CommonJS module: the module's own
|
||||
* object standing for its original exports, created when the module starts
|
||||
* (lowerFileInit). Exports themselves are the static export table, so a
|
||||
* property access through `this` would read or write an object no requirer
|
||||
* sees; it keeps a named refusal instead. Typeof, truthiness, identity and
|
||||
* passing the value along (the UMD wrapper `(function (root) { ... })(this)`)
|
||||
* behave as in Node. */
|
||||
export function lowerCjsTopLevelThis(lowerer: Lowerer, expr: ts.Expression): IrExpr | null {
|
||||
if (!isCjsTopLevelThis(lowerer, expr)) return null;
|
||||
const global = lowerer.cjsExportsThis.get(expr.getSourceFile());
|
||||
if (!global) return null;
|
||||
let receiver: ts.Node = expr;
|
||||
while (ts.isParenthesizedExpression(receiver.parent)) receiver = receiver.parent;
|
||||
const access = receiver.parent;
|
||||
if (
|
||||
(ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access)) &&
|
||||
access.expression === receiver
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
access,
|
||||
"property access through top-level 'this' in a CommonJS module (it is module.exports; spell exports.<name>)",
|
||||
);
|
||||
}
|
||||
return varRef(global.id, DYN, locOf(expr));
|
||||
}
|
||||
|
||||
function optionalModule(
|
||||
lowerer: Lowerer,
|
||||
node: ts.Expression,
|
||||
@@ -309,6 +439,8 @@ export function lowerNodeModuleProperty(
|
||||
if (!direct && !nodeModuleType(lowerer, expr.expression)) return null;
|
||||
if (expr.questionDotToken) return null;
|
||||
const member = expr.name.text;
|
||||
if (member === "exports" && direct && moduleExportsAlwaysTruthy(lowerer, expr))
|
||||
return boolLit(true, locOf(expr));
|
||||
if (member === "exports" || member === "require") return null;
|
||||
let moduleRef = direct
|
||||
? cjsModuleRef(lowerer, expr.expression)
|
||||
|
||||
@@ -4512,16 +4512,21 @@ export function lowerVarDecl(
|
||||
if (wrapped) return { kind: "assign", localId: g.id, value: wrapped, loc: locOf(decl) };
|
||||
return null;
|
||||
}
|
||||
const raw = lowerVariableInitializer(
|
||||
lowerer,
|
||||
decl.initializer,
|
||||
g.type.kind === "dyn" &&
|
||||
(isNativeBuiltinValueInitializer(lowerer, decl.initializer) ||
|
||||
ts.isArrayLiteralExpression(decl.initializer) ||
|
||||
ts.isObjectLiteralExpression(decl.initializer))
|
||||
? DYN
|
||||
: undefined,
|
||||
);
|
||||
const guardedRead = isGuardedRecordRead(lowerer, decl)
|
||||
? lowerAbsenceProbe(lowerer, decl.initializer)
|
||||
: null;
|
||||
const raw =
|
||||
guardedRead ??
|
||||
lowerVariableInitializer(
|
||||
lowerer,
|
||||
decl.initializer,
|
||||
g.type.kind === "dyn" &&
|
||||
(isNativeBuiltinValueInitializer(lowerer, decl.initializer) ||
|
||||
ts.isArrayLiteralExpression(decl.initializer) ||
|
||||
ts.isObjectLiteralExpression(decl.initializer))
|
||||
? DYN
|
||||
: undefined,
|
||||
);
|
||||
if (
|
||||
isJsSourceFile(decl.getSourceFile()) &&
|
||||
!decl.type &&
|
||||
@@ -5317,29 +5322,111 @@ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclar
|
||||
}
|
||||
|
||||
function immediatelyGuardedAbsenceProbe(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean {
|
||||
if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer) return false;
|
||||
const guard = immediateAbsenceGuard(lowerer, decl);
|
||||
// `if (!x)`: the falsy test sees a missing value.
|
||||
if (guard === "falsy") return true;
|
||||
// `if (x === undefined)` and its spellings compare the binding with the
|
||||
// missing value itself, so the read must be able to produce it. Only
|
||||
// index-signature record reads take this form: array reads already
|
||||
// carry their undefined arm, and their ordinary lowering keeps its
|
||||
// bounds proofs.
|
||||
return guard === "compare" && isIndexSignatureRecordRead(lowerer, decl);
|
||||
}
|
||||
|
||||
/** A file-scope or block-scope `let e = map[k]` over an index-signature
|
||||
* record whose next statement guards `e` for absence (`if (e ===
|
||||
* undefined)`, `if (!e)`, ...): the get-or-create idiom. Its read keeps the
|
||||
* missing key as undefined even without noUncheckedIndexedAccess. */
|
||||
export function isGuardedRecordRead(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean {
|
||||
return immediateAbsenceGuard(lowerer, decl) !== null && isIndexSignatureRecordRead(lowerer, decl);
|
||||
}
|
||||
|
||||
function isIndexSignatureRecordRead(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean {
|
||||
if (!decl.initializer) return false;
|
||||
let init = decl.initializer;
|
||||
while (ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (!ts.isElementAccessExpression(init)) return false;
|
||||
// Under noUncheckedIndexedAccess the checker already spells the missing
|
||||
// arm, and the ordinary read carries it.
|
||||
const read = lowerer.typeOf(init);
|
||||
if (
|
||||
(read.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 ||
|
||||
(read.isUnionType() &&
|
||||
ts.constituentTypes(read).some((t) => (t.flags & ts.TypeFlags.Undefined) !== 0))
|
||||
)
|
||||
return false;
|
||||
const receiver = lowerer.mapTypeOf(lowerer.typeOf(init.expression));
|
||||
const shape = receiver?.kind === "record" ? lowerer.shapes.get(receiver.shapeId) : undefined;
|
||||
return !!shape && !shape.tuple && !!shape.indexValue && shape.indexValue.kind !== "dyn";
|
||||
}
|
||||
|
||||
/** How the statement right after an unannotated declaration tests the
|
||||
* declared binding for absence: `if (!x)` ("falsy"), or a comparison with
|
||||
* the missing value — `x === undefined`, `x == null`, `x !== void 0`,
|
||||
* `typeof x === "undefined"` and their mirrored and negated spellings
|
||||
* ("compare"). Null for anything else. */
|
||||
function immediateAbsenceGuard(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.VariableDeclaration,
|
||||
): "falsy" | "compare" | null {
|
||||
if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer) return null;
|
||||
const statement = decl.parent?.parent;
|
||||
if (!statement || !ts.isVariableStatement(statement)) return false;
|
||||
if (!statement || !ts.isVariableStatement(statement)) return null;
|
||||
const container = statement.parent;
|
||||
const statements =
|
||||
ts.isBlock(container) || ts.isSourceFile(container) ? container.statements : null;
|
||||
if (!statements) return false;
|
||||
if (!statements) return null;
|
||||
const index = statements.indexOf(statement);
|
||||
const next = index >= 0 ? statements[index + 1] : undefined;
|
||||
if (!next || !ts.isIfStatement(next)) return false;
|
||||
let condition = next.expression;
|
||||
while (ts.isParenthesizedExpression(condition)) condition = condition.expression;
|
||||
if (
|
||||
!ts.isPrefixUnaryExpression(condition) ||
|
||||
condition.operator !== ts.SyntaxKind.ExclamationToken
|
||||
)
|
||||
return false;
|
||||
let operand = condition.operand;
|
||||
while (ts.isParenthesizedExpression(operand)) operand = operand.expression;
|
||||
return (
|
||||
ts.isIdentifier(operand) &&
|
||||
lowerer.resolveValueSymbol(operand) === lowerer.resolveValueSymbol(decl.name)
|
||||
);
|
||||
if (!next || !ts.isIfStatement(next)) return null;
|
||||
const binding = lowerer.resolveValueSymbol(decl.name);
|
||||
const peel = (node: ts.Expression): ts.Expression => {
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
return node;
|
||||
};
|
||||
const isBinding = (node: ts.Expression): boolean => {
|
||||
const operand = peel(node);
|
||||
return ts.isIdentifier(operand) && lowerer.resolveValueSymbol(operand) === binding;
|
||||
};
|
||||
const condition = peel(next.expression);
|
||||
if (ts.isPrefixUnaryExpression(condition))
|
||||
return condition.operator === ts.SyntaxKind.ExclamationToken && isBinding(condition.operand)
|
||||
? "falsy"
|
||||
: null;
|
||||
if (!ts.isBinaryExpression(condition)) return null;
|
||||
const op = condition.operatorToken.kind;
|
||||
const strict =
|
||||
op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
||||
op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
||||
const loose =
|
||||
op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken;
|
||||
if (!strict && !loose) return null;
|
||||
const isUndefined = (node: ts.Expression): boolean => {
|
||||
const operand = peel(node);
|
||||
if (ts.isVoidExpression(operand)) return true;
|
||||
return (
|
||||
ts.isIdentifier(operand) &&
|
||||
operand.text === "undefined" &&
|
||||
(lowerer.typeOf(operand).flags & ts.TypeFlags.Undefined) !== 0
|
||||
);
|
||||
};
|
||||
const isNull = (node: ts.Expression): boolean => peel(node).kind === ts.SyntaxKind.NullKeyword;
|
||||
const missing = (node: ts.Expression): boolean => isUndefined(node) || (loose && isNull(node));
|
||||
const isTypeofBinding = (node: ts.Expression): boolean => {
|
||||
const operand = peel(node);
|
||||
return ts.isTypeOfExpression(operand) && isBinding(operand.expression);
|
||||
};
|
||||
const isUndefinedTag = (node: ts.Expression): boolean => {
|
||||
const operand = peel(node);
|
||||
return ts.isStringLiteralLike(operand) && operand.text === "undefined";
|
||||
};
|
||||
const { left, right } = condition;
|
||||
return (isBinding(left) && missing(right)) ||
|
||||
(missing(left) && isBinding(right)) ||
|
||||
(isTypeofBinding(left) && isUndefinedTag(right)) ||
|
||||
(isUndefinedTag(left) && isTypeofBinding(right))
|
||||
? "compare"
|
||||
: null;
|
||||
}
|
||||
|
||||
function falsyExitRuntimeOptionalLocals(lowerer: Lowerer, stmt: ts.IfStatement): IrLocal[] {
|
||||
|
||||
@@ -271,7 +271,7 @@ import {
|
||||
appendForkModules,
|
||||
appendWorkerModules,
|
||||
} from "./lower-modules.js";
|
||||
import { prepareCjsModuleGraph } from "./lower-node-module.js";
|
||||
import { prepareCjsModuleGraph, usesCjsTopLevelThis } from "./lower-node-module.js";
|
||||
import { registerBuiltinEventTargetClasses } from "./lower-event-target.js";
|
||||
import {
|
||||
type ClassInfo,
|
||||
@@ -533,6 +533,7 @@ import {
|
||||
lowerForOf,
|
||||
lowerForStatement,
|
||||
matchGroupsPatternSource,
|
||||
isGuardedRecordRead,
|
||||
} from "./lower-stmts.js";
|
||||
import {
|
||||
type FieldTarget,
|
||||
@@ -1859,6 +1860,9 @@ export class Lowerer {
|
||||
* carry undefined at runtime. Identifier reads keep the union tag so
|
||||
* typeof and later boundaries observe the real value. */
|
||||
readonly runtimeOptionalGlobals = new Set<IrGlobal>();
|
||||
/** Per CommonJS file using top-level `this`: the global holding the
|
||||
* object that stands for its original exports (lowerCjsTopLevelThis). */
|
||||
readonly cjsExportsThis = new Map<ts.SourceFile, IrGlobal>();
|
||||
/** Bindings whose unchecked string arithmetic can produce either a number
|
||||
* (NaN for an absent read) or a string. Preserve that result union at bare
|
||||
* reads instead of applying the checker-bare string narrowing adapter. */
|
||||
@@ -2868,7 +2872,8 @@ export class Lowerer {
|
||||
return null;
|
||||
}
|
||||
const className = this.classNamer(decl);
|
||||
if (!this.collectingExprClasses.has(decl)) this.lowerClassExpressionInfo(decl);
|
||||
if (!this.collectingExprClasses.has(decl) && !this.collectLocalClassForType(decl))
|
||||
return null;
|
||||
return { kind: "object", className };
|
||||
},
|
||||
mixinIntersectionInstance: (widened) => mixinIntersectionInstanceType(this, widened),
|
||||
@@ -3701,6 +3706,16 @@ export class Lowerer {
|
||||
const id = `%g.${tag}%loaded`;
|
||||
this.moduleGuardOf.set(fp.sf, id);
|
||||
this.globalsList.push({ id, name: "%loaded", type: BOOL, mutable: true });
|
||||
if (usesCjsTopLevelThis(this, fp.sf)) {
|
||||
const exportsThis: IrGlobal = {
|
||||
id: `%g.${tag}%exportsThis`,
|
||||
name: "%exportsThis",
|
||||
type: DYN,
|
||||
mutable: true,
|
||||
};
|
||||
this.globalsList.push(exportsThis);
|
||||
this.cjsExportsThis.set(fp.sf, exportsThis);
|
||||
}
|
||||
}
|
||||
|
||||
// A module is intrinsically async when an await/for-await occurs
|
||||
@@ -3955,6 +3970,33 @@ export class Lowerer {
|
||||
}
|
||||
}
|
||||
|
||||
/** A file-scope get-or-create read (`let e = map[k]; if (e ===
|
||||
* undefined) ...` over an index-signature record) keeps a missing key as
|
||||
* undefined (isGuardedRecordRead). Its pre-registered global takes the
|
||||
* undefined arm before any body reads it; the declaration lowers its
|
||||
* initializer as the matching absence probe. */
|
||||
analyzeGuardedRecordReadGlobals(parts: FileParts[]): void {
|
||||
for (const { sf } of parts) {
|
||||
for (const statement of sf.statements) {
|
||||
if (!ts.isVariableStatement(statement)) continue;
|
||||
for (const decl of statement.declarationList.declarations) {
|
||||
if (!ts.isIdentifier(decl.name) || !isGuardedRecordRead(this, decl)) continue;
|
||||
const global = this.globalOf(decl.name);
|
||||
const symbol = this.resolveValueSymbol(decl.name);
|
||||
if (!global || !symbol || global.tdz || this.runtimeOptionalBindingTypes.has(symbol))
|
||||
continue;
|
||||
const widened =
|
||||
global.type.kind === "union"
|
||||
? this.withUndefinedArmOf(global.type)
|
||||
: this.withUndefinedArm(global.type);
|
||||
if (!widened || typeEquals(widened, global.type)) continue;
|
||||
global.type = widened;
|
||||
this.runtimeOptionalBindingTypes.set(symbol, widened);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analyzeRuntimeOptionalArrayReads(parts: FileParts[]): void {
|
||||
const provenance = RuntimeOptionalProvenance.fromEnvironment();
|
||||
// The site whose value the current promotion follows (debug only).
|
||||
@@ -6197,8 +6239,11 @@ export class Lowerer {
|
||||
for (const fp of parts) this.checker.prefetchSourceFile(fp.sf);
|
||||
}
|
||||
this.collectProgram(parts);
|
||||
this.analyzeRuntimeOptionalArrayReads(parts);
|
||||
this.analyzeDateJsonResults(parts);
|
||||
this.speculativeTypeQueries(() => {
|
||||
this.analyzeRuntimeOptionalArrayReads(parts);
|
||||
this.analyzeDateJsonResults(parts);
|
||||
this.analyzeGuardedRecordReadGlobals(parts);
|
||||
});
|
||||
// Decorated classes analyze AFTER the whole collection pass: a
|
||||
// decorator's return type may name a subclass declared below the
|
||||
// class, and reference lowering needs each class's rebindability
|
||||
@@ -6583,8 +6628,11 @@ export class Lowerer {
|
||||
collectionEdges.add(name);
|
||||
};
|
||||
this.collectProgram(parts);
|
||||
this.analyzeRuntimeOptionalArrayReads(parts);
|
||||
this.analyzeDateJsonResults(parts);
|
||||
this.speculativeTypeQueries(() => {
|
||||
this.analyzeRuntimeOptionalArrayReads(parts);
|
||||
this.analyzeDateJsonResults(parts);
|
||||
this.analyzeGuardedRecordReadGlobals(parts);
|
||||
});
|
||||
// Decorated classes analyze post-collection here too: the %init seeds
|
||||
// lower the decoration calls, whose edges (decorator bodies, construct
|
||||
// thunks) reachable emit must see.
|
||||
@@ -9736,6 +9784,49 @@ export class Lowerer {
|
||||
return lowerClassExpressionInfo(this, expr);
|
||||
}
|
||||
|
||||
/** Depth of the whole-program analyses that query types across every
|
||||
* file before reachability is known (speculativeTypeQueries). */
|
||||
private speculativeTypeQueryDepth = 0;
|
||||
|
||||
/** Runs a whole-program analysis whose type queries are speculative: they
|
||||
* visit every file, reached or not, and no statement or declaration owns
|
||||
* a refusal they trigger (collectLocalClassForType). */
|
||||
speculativeTypeQueries(analysis: () => void): void {
|
||||
this.speculativeTypeQueryDepth++;
|
||||
try {
|
||||
analysis();
|
||||
} finally {
|
||||
this.speculativeTypeQueryDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
/** Collects a function-local class because a type query named its
|
||||
* instance type. Collection and body lowering own a refusal of that
|
||||
* class: the poison reaches the declaration or statement that asked, as
|
||||
* before. A speculative whole-program analysis owns none, so there the
|
||||
* class is unmappable for the query and its diagnostics are dropped;
|
||||
* lowering the class's own declaration collects it again and reports
|
||||
* them if the program reaches it. */
|
||||
collectLocalClassForType(decl: ts.ClassLikeDeclaration): boolean {
|
||||
if (this.speculativeTypeQueryDepth === 0 || this.fnStack.length > 0) {
|
||||
this.lowerClassExpressionInfo(decl);
|
||||
return true;
|
||||
}
|
||||
const sink: ScrDiagnostic[] = [];
|
||||
const previousSink = this.diagSink;
|
||||
this.diagSink = sink;
|
||||
try {
|
||||
this.lowerClassExpressionInfo(decl);
|
||||
} catch (e) {
|
||||
if (e instanceof PoisonError) return false;
|
||||
throw e;
|
||||
} finally {
|
||||
this.diagSink = previousSink;
|
||||
}
|
||||
for (const d of sink) this.pushDiag(d);
|
||||
return true;
|
||||
}
|
||||
|
||||
lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr {
|
||||
return lowerClassExpression(this, expr);
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6100-record-get-or-create.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6101-record-get-or-create-unchecked/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6102-local-class-type-query.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6103-typeof-builtin-bindings.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6104-typeof-builtin-bindings.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6105-cjs-top-level-this/lib.js",
|
||||
"<repo>/tests/corpus/6105-cjs-top-level-this/main.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6106-esm-top-level-this.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/local-class-type-query-brand.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/local-class-type-query.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Get-or-create over nested index-signature records without
|
||||
// noUncheckedIndexedAccess: a missing key reads as undefined when the next
|
||||
// statement tests the binding for absence, in every spelling of that test,
|
||||
// at module scope and inside functions. A write with a computed key through
|
||||
// a binding that may hold a missing element throws Node's TypeError after
|
||||
// the key and the value evaluated.
|
||||
type ExportMap = Record<string, Record<string, string>>;
|
||||
|
||||
interface Target {
|
||||
subpath: string;
|
||||
condition: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
function build(targets: Target[]): { exports: ExportMap; unmapped: string[] } {
|
||||
const exports: ExportMap = {};
|
||||
const unmapped: string[] = [];
|
||||
for (const t of targets) {
|
||||
if (t.target === "") {
|
||||
unmapped.push(t.subpath);
|
||||
continue;
|
||||
}
|
||||
let entry = exports[t.subpath];
|
||||
if (entry === undefined) {
|
||||
entry = {};
|
||||
exports[t.subpath] = entry;
|
||||
}
|
||||
entry[t.condition] = `./${t.target}`;
|
||||
entry["types"] = `./${t.target}`;
|
||||
}
|
||||
return { exports: exports, unmapped: unmapped };
|
||||
}
|
||||
|
||||
const result = build([
|
||||
{ subpath: ".", condition: "import", target: "src/index.ts" },
|
||||
{ subpath: ".", condition: "require", target: "src/index.ts" },
|
||||
{ subpath: "./x", condition: "default", target: "" },
|
||||
{ subpath: "./y", condition: "node", target: "src/y.ts" },
|
||||
]);
|
||||
console.log(JSON.stringify(result.exports), result.unmapped.join(","));
|
||||
|
||||
function group(words: string[]): Record<string, string[]> {
|
||||
const byInitial: Record<string, string[]> = {};
|
||||
for (const w of words) {
|
||||
const k = w.slice(0, 1);
|
||||
let list = byInitial[k];
|
||||
if (list == null) {
|
||||
list = [];
|
||||
byInitial[k] = list;
|
||||
}
|
||||
list.push(w);
|
||||
}
|
||||
return byInitial;
|
||||
}
|
||||
console.log(JSON.stringify(group(["apple", "avocado", "banana", "blueberry", "cherry"])));
|
||||
|
||||
function count(words: string[]): Record<string, number> {
|
||||
const counts: Record<string, number> = {};
|
||||
for (const w of words) {
|
||||
const n = counts[w];
|
||||
if (typeof n === "undefined") counts[w] = 1;
|
||||
else counts[w] = n + 1;
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
console.log(JSON.stringify(count(["a", "b", "a", "c", "a"])));
|
||||
|
||||
function lookup(table: Record<string, string>, key: string): string {
|
||||
const hit = table[key];
|
||||
if (hit !== undefined) return hit;
|
||||
return `<${key} missing>`;
|
||||
}
|
||||
console.log(lookup({ a: "A" }, "a"), lookup({ a: "A" }, "b"));
|
||||
|
||||
function rows(table: Record<string, Record<string, number>>, key: string, col: string): number {
|
||||
const row = table[key];
|
||||
if (row != undefined) return row[col] ?? -1;
|
||||
return -2;
|
||||
}
|
||||
console.log(rows({ r: { c: 3 } }, "r", "c"), rows({ r: { c: 3 } }, "r", "d"), rows({}, "x", "c"));
|
||||
|
||||
// Module scope.
|
||||
const nested: Record<string, Record<string, number>> = {};
|
||||
const key = process.argv.length > 99 ? "unused" : "k";
|
||||
let slot = nested[key];
|
||||
if (slot === undefined) {
|
||||
slot = {};
|
||||
nested[key] = slot;
|
||||
}
|
||||
slot[key] = 1;
|
||||
slot["fixed"] = 2;
|
||||
console.log(JSON.stringify(nested));
|
||||
|
||||
const names: Record<string, string> = { present: "yes" };
|
||||
const missing = names[key];
|
||||
if (!missing) console.log("falsy guard:", missing);
|
||||
const present = names["present"];
|
||||
if (present === void 0) console.log("unreachable");
|
||||
else console.log("present:", present);
|
||||
|
||||
// A write with a computed key through a binding that may hold a missing
|
||||
// array element throws Node's TypeError naming the key, after evaluating
|
||||
// the key and the value.
|
||||
function probe(tables: Record<string, string>[]): void {
|
||||
const firstTable = tables[0];
|
||||
const lastTable = tables[5];
|
||||
const pick = (k: string): string => {
|
||||
console.log("key", k);
|
||||
return k;
|
||||
};
|
||||
firstTable[pick("b")] = pick("second");
|
||||
console.log(JSON.stringify(firstTable));
|
||||
try {
|
||||
lastTable[pick("c")] = pick("value");
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as Error).message);
|
||||
}
|
||||
}
|
||||
probe([{ a: "first" }]);
|
||||
@@ -0,0 +1,70 @@
|
||||
// Get-or-create over nested index-signature records under
|
||||
// noUncheckedIndexedAccess: after the presence guard the binding is still
|
||||
// stored with its undefined arm, and reads, writes and absence probes with
|
||||
// computed keys go through it (an internal compiler error before).
|
||||
type ExportMap = Record<string, Record<string, string>>;
|
||||
|
||||
function build(rows: { k: string; c: string; v: string }[]): ExportMap {
|
||||
const map: ExportMap = {};
|
||||
for (const r of rows) {
|
||||
let entry = map[r.k];
|
||||
if (entry === undefined) {
|
||||
entry = {};
|
||||
map[r.k] = entry;
|
||||
}
|
||||
entry[r.c] = r.v;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
build([
|
||||
{ k: "a", c: "x", v: "1" },
|
||||
{ k: "a", c: "y", v: "2" },
|
||||
{ k: "b", c: "x", v: "3" },
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
function touch(map: ExportMap, k: string): string {
|
||||
let e = map[k];
|
||||
if (e === undefined) {
|
||||
e = { [k]: "init" };
|
||||
map[k] = e;
|
||||
}
|
||||
const before = e[k] ?? "none";
|
||||
e[k] = e[k] + "!";
|
||||
return before + (k in e ? "+" : "-");
|
||||
}
|
||||
const m: ExportMap = {};
|
||||
console.log(touch(m, "a"), touch(m, "a"), touch(m, "b"), JSON.stringify(m));
|
||||
|
||||
function tally(counts: Record<string, Record<string, number>>, group: string, item: string): void {
|
||||
let row = counts[group];
|
||||
if (!row) {
|
||||
row = {};
|
||||
counts[group] = row;
|
||||
}
|
||||
row[item] = (row[item] ?? 0) + 1;
|
||||
}
|
||||
const counts: Record<string, Record<string, number>> = {};
|
||||
for (const [g, i] of [
|
||||
["fruit", "apple"],
|
||||
["fruit", "pear"],
|
||||
["veg", "kale"],
|
||||
["fruit", "apple"],
|
||||
]) {
|
||||
tally(counts, g!, i!);
|
||||
}
|
||||
console.log(JSON.stringify(counts));
|
||||
|
||||
// Module scope.
|
||||
const top: ExportMap = {};
|
||||
const key = process.argv.length > 99 ? "unused" : "k";
|
||||
let slot = top[key];
|
||||
if (slot === undefined) {
|
||||
slot = {};
|
||||
top[key] = slot;
|
||||
}
|
||||
slot[key] = "v";
|
||||
console.log(JSON.stringify(top), slot[key], slot["other"] ?? "missing");
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// A whole-program analysis that queries types before reachability is known
|
||||
// (the computed-key write census here) can name a function-local class whose
|
||||
// collection is refused. That query has no owner for the refusal: the class
|
||||
// stays unmapped for it, and the refusal belongs to the class's own
|
||||
// declaration, which this program never executes (it used to crash the
|
||||
// compiler with an uncaught PoisonError).
|
||||
function main(run) {
|
||||
if (!run) return "skipped";
|
||||
const Project = class {
|
||||
constructor() {
|
||||
this.name = "p";
|
||||
}
|
||||
close() {
|
||||
if (this.missingFilesMap) {
|
||||
this.missingFilesMap.clear();
|
||||
this.missingFilesMap = void 0;
|
||||
}
|
||||
}
|
||||
add(k) {
|
||||
(this.missingFilesMap || (this.missingFilesMap = new Map())).set(k, 1);
|
||||
}
|
||||
};
|
||||
const holder = { p: new Project() };
|
||||
const key = String(run);
|
||||
holder.p[key] = "q";
|
||||
return holder.p.name;
|
||||
}
|
||||
|
||||
class Counter {
|
||||
constructor() {
|
||||
this.counts = {};
|
||||
}
|
||||
bump(key) {
|
||||
this.counts[key] = (this.counts[key] || 0) + 1;
|
||||
return this.counts[key];
|
||||
}
|
||||
}
|
||||
|
||||
const counter = new Counter();
|
||||
const word = process.argv.length > 99 ? "unused" : "word";
|
||||
counter.bump(word);
|
||||
console.log(main(0), counter.bump(word));
|
||||
@@ -0,0 +1,15 @@
|
||||
// typeof on bindings destructured from Node builtin modules: Node answers by
|
||||
// the export's kind ("function" for functions and classes, "object" for
|
||||
// namespace objects), never by how the binding is represented.
|
||||
const { inspect, format, promisify, deprecate, isDeepStrictEqual } = require("util");
|
||||
const { join, resolve } = require("path");
|
||||
const { readFileSync, promises } = require("fs");
|
||||
const { EventEmitter } = require("events");
|
||||
const { createHash } = require("crypto");
|
||||
const util = require("node:util");
|
||||
const { inspect: aliased } = util;
|
||||
console.log(typeof inspect, typeof format, typeof promisify, typeof deprecate, typeof isDeepStrictEqual);
|
||||
console.log(typeof join, typeof resolve, typeof readFileSync, typeof promises);
|
||||
console.log(typeof EventEmitter, typeof createHash, typeof aliased, typeof util.inspect);
|
||||
console.log(typeof inspect === "function" ? "callable" : "not callable");
|
||||
console.log(join("a", "b"), format("%s=%d", "x", 1), inspect({ a: [1, 2] }));
|
||||
@@ -0,0 +1,11 @@
|
||||
// typeof on named, default-destructured and namespace-destructured builtin
|
||||
// bindings in an ES module: Node answers by the export's kind.
|
||||
import util, { inspect as namedInspect, format } from "node:util";
|
||||
import * as path from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { promises } from "node:fs";
|
||||
const { inspect, promisify } = util;
|
||||
const { join } = path;
|
||||
console.log(typeof inspect, typeof promisify, typeof namedInspect, typeof format);
|
||||
console.log(typeof join, typeof createHash, typeof util.inspect, typeof promises);
|
||||
console.log(namedInspect("s"), format("%d", 2), join("x", "y"));
|
||||
@@ -0,0 +1,11 @@
|
||||
// A required CommonJS module sees its own exports object as `this`, and the
|
||||
// UMD test for a CommonJS host takes the module.exports branch.
|
||||
const kind = typeof this;
|
||||
let host = "global";
|
||||
if (typeof module !== "undefined" && module.exports) host = "commonjs";
|
||||
(function (root) {
|
||||
console.log("lib wrapper root:", typeof root, root ? "truthy" : "falsy");
|
||||
})(this);
|
||||
exports.describe = function () {
|
||||
return kind + " " + host;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// Top-level `this` in a CommonJS module is module.exports (an object), also
|
||||
// inside arrow functions at module scope.
|
||||
const lib = require("./lib.js");
|
||||
const self = this;
|
||||
const arrow = () => this;
|
||||
console.log(typeof this, self === this, arrow() === this, this ? "truthy" : "falsy");
|
||||
console.log(lib.describe());
|
||||
(function () {
|
||||
console.log("call(this):", typeof this);
|
||||
}).call(this);
|
||||
if ((typeof module !== "undefined" && module !== null) && module.exports) console.log("commonjs host");
|
||||
else console.log("browser host");
|
||||
@@ -0,0 +1 @@
|
||||
{ "type": "commonjs" }
|
||||
@@ -0,0 +1,4 @@
|
||||
// Top-level `this` in an ES module stays undefined, also inside arrow
|
||||
// functions at module scope.
|
||||
const arrow = () => this;
|
||||
console.log(typeof this, this === undefined, typeof arrow());
|
||||
@@ -0,0 +1,14 @@
|
||||
// The same speculative element-type query, reaching a function-local class
|
||||
// with a private brand through the function's returned array.
|
||||
function make() {
|
||||
class Box {
|
||||
#v = 1;
|
||||
read(): number {
|
||||
return this.#v;
|
||||
}
|
||||
}
|
||||
return [new Box()];
|
||||
}
|
||||
const xs = make();
|
||||
const x = xs[0];
|
||||
console.log(x === undefined ? -1 : x.read());
|
||||
@@ -0,0 +1,14 @@
|
||||
// A whole-program analysis queries the element type of `items` before any
|
||||
// body lowers. The function-local class it names is refused: the refusal is
|
||||
// reported once, at the class (it used to crash the compiler with an uncaught
|
||||
// PoisonError).
|
||||
function make() {
|
||||
class Box {
|
||||
static count = 0;
|
||||
v = 1;
|
||||
}
|
||||
return new Box();
|
||||
}
|
||||
const items = [make()];
|
||||
const first = items[0];
|
||||
console.log(first ? first.v : 0);
|
||||
@@ -0,0 +1,6 @@
|
||||
local-class-type-query-brand.ts:4:3 - error SC1090: function-local classes with private brands are not supported yet
|
||||
|
||||
3 | function make() {
|
||||
4 | class Box {
|
||||
| ^~~~~~~~~~~
|
||||
5 | #v = 1;
|
||||
@@ -0,0 +1,6 @@
|
||||
local-class-type-query.ts:6:3 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet
|
||||
|
||||
5 | function make() {
|
||||
6 | class Box {
|
||||
| ^~~~~~~~~~~
|
||||
7 | static count = 0;
|
||||
@@ -2200,6 +2200,13 @@
|
||||
"6042-regex-match-object-destructuring.ts": 3393,
|
||||
"6043-string-index-narrowed-union.ts": 2983,
|
||||
"605-closures-forward-capture-tdz.ts": 3277,
|
||||
"6100-record-get-or-create.ts": 3771,
|
||||
"6101-record-get-or-create-unchecked/main.ts": 2107,
|
||||
"6102-local-class-type-query.js": 2600,
|
||||
"6103-typeof-builtin-bindings.cjs": 2202,
|
||||
"6104-typeof-builtin-bindings.mjs": 2031,
|
||||
"6105-cjs-top-level-this/main.js": 1371,
|
||||
"6106-esm-top-level-this.mjs": 1915,
|
||||
"6140-cjs-require-tdz-throws/main.js": 2731,
|
||||
"6141-cjs-require-tdz-checked/main.js": 2856,
|
||||
"6142-cjs-require-published-early/main.js": 3400,
|
||||
@@ -5222,6 +5229,13 @@
|
||||
"6042-regex-match-object-destructuring.ts": 3091,
|
||||
"6043-string-index-narrowed-union.ts": 3627,
|
||||
"605-closures-forward-capture-tdz.ts": 4638,
|
||||
"6100-record-get-or-create.ts": 3173,
|
||||
"6101-record-get-or-create-unchecked/main.ts": 3228,
|
||||
"6102-local-class-type-query.js": 1967,
|
||||
"6103-typeof-builtin-bindings.cjs": 1796,
|
||||
"6104-typeof-builtin-bindings.mjs": 1715,
|
||||
"6105-cjs-top-level-this/main.js": 3213,
|
||||
"6106-esm-top-level-this.mjs": 1672,
|
||||
"6140-cjs-require-tdz-throws/main.js": 2989,
|
||||
"6141-cjs-require-tdz-checked/main.js": 4062,
|
||||
"6142-cjs-require-published-early/main.js": 5156,
|
||||
|
||||
@@ -73,7 +73,7 @@ const KNOWN: Record<string, Known> = {
|
||||
"node24-env": { run: "SC1090" },
|
||||
"node24-esm-metadata": { run: "SC1090" },
|
||||
"node24-event-emitter": { run: "SC2020" },
|
||||
"node24-file-url-conversion": { build: "SC1090" },
|
||||
"node24-file-url-conversion": { run: "SC2020" },
|
||||
"node24-fs-descriptors": { run: "SC1090" },
|
||||
"node24-mime": { run: "SC2020" },
|
||||
"node24-mime-errors": { run: "SC1090" },
|
||||
@@ -104,7 +104,7 @@ const KNOWN: Record<string, Known> = {
|
||||
"package-imports": { build: "SC2013" },
|
||||
"promise-bridge": { build: "SC1090" },
|
||||
"self-name": { build: "SC2013" },
|
||||
"stream-shims": { build: "SC1090" },
|
||||
"stream-shims": { run: "SC1090" },
|
||||
"util-shims": { run: "SC2020" },
|
||||
"web-streams": { build: "SC2013" },
|
||||
"workspace-copied": { build: "SC2013" },
|
||||
|
||||
Reference in New Issue
Block a user