feat(compiler): use owned native types throughout the frontend

- Generate native AST views, syntax guards, and token spellings from pinned TypeScript 7.
- Connect frontend analysis directly to owned AST, checker, and session objects.
- Preserve node identity and handle absent parents across lowering.
This commit is contained in:
Chris Tate
2026-09-28 08:31:57 -05:00
parent bd12146234
commit 25d83ca424
45 changed files with 4711 additions and 207 deletions
@@ -29,7 +29,7 @@ export function isBuiltinMemberImport(
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
if (decl !== undefined && ts.isImportSpecifier(decl)) {
const importDecl = decl.parent.parent.parent;
const importDecl = decl.parent?.parent?.parent;
if (ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
const member = decl.propertyName?.text ?? decl.name.text;
if (canonicalBuiltinModule(importDecl.moduleSpecifier.text) === moduleName && member === memberName) {
@@ -64,7 +64,7 @@ function isBuiltinNamespaceImport(
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
let importDecl: ts.ImportDeclaration | undefined;
if (decl !== undefined && ts.isNamespaceImport(decl)) {
const candidate = decl.parent.parent;
const candidate = decl.parent?.parent;
if (ts.isImportDeclaration(candidate)) importDecl = candidate;
} else if (decl !== undefined && ts.isImportClause(decl) && decl.name !== undefined) {
if (ts.isImportDeclaration(decl.parent)) importDecl = decl.parent;
@@ -165,8 +165,8 @@ function localTypeBindings(file: ts.SourceFile): Set<string> {
function isUnshadowedGlobalType(file: ts.SourceFile, node: ts.TypeNode, name: string): boolean {
if (localTypeBindings(file).has(name)) return false;
for (let scope = node.parent; scope !== undefined && scope !== file; scope = scope.parent) {
const params = (scope as ts.Node & { typeParameters?: readonly ts.TypeParameterDeclaration[] }).typeParameters;
if (params?.some((p) => p.name.text === name) === true) return false;
const params = scope.typeParameters;
if (params?.some((p) => ts.isTypeParameterDeclaration(p) && p.name.text === name) === true) return false;
}
return true;
}
@@ -633,7 +633,7 @@ export function lowerObjectLiteral(lowerer: Lowerer, expr: ts.ObjectLiteralExpre
// keeps the contextual fallback (a bare-null field whose satisfies
// target names the wider slot type).
{
let p: ts.Node = expr.parent;
let p: ts.Node | undefined = expr.parent;
while (ts.isParenthesizedExpression(p)) p = p.parent;
if (ts.isSatisfiesExpression(p)) {
const own = lowerer.typeOf(expr);
@@ -861,8 +861,8 @@ export function lowerObjectLiteral(lowerer: Lowerer, expr: ts.ObjectLiteralExpre
// omitted property from a present property whose value is undefined.
const topLevelJsDecl = ts.isVariableDeclaration(expr.parent) &&
expr.parent.initializer === expr &&
ts.isVariableStatement(expr.parent.parent.parent) &&
ts.isSourceFile(expr.parent.parent.parent.parent);
ts.isVariableStatement(expr.parent?.parent?.parent) &&
ts.isSourceFile(expr.parent?.parent?.parent?.parent);
const assignedProperties = expr.properties.filter(
(prop): prop is ts.PropertyAssignment | ts.ShorthandPropertyAssignment =>
ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop),
@@ -1995,7 +1995,7 @@ export function lowerShorthandValue(lowerer: Lowerer, prop: ts.ShorthandProperty
const decl = lowerer.checker.declarationsOf(resolved)[0];
if (
sig && decl && ts.isFunctionDeclaration(decl) &&
(ts.isSourceFile(decl.parent) || lowerer.nsBlocks.get(decl.parent) === "flattened")
(ts.isSourceFile(decl.parent) || (decl.parent !== undefined && lowerer.nsBlocks.get(decl.parent) === "flattened"))
) {
lowerer.noteEdge(sig.name);
const funcType: IrType = {
@@ -244,7 +244,7 @@ function lowerBuiltinOptionalDefault(
// binding (`const { readFileSync } = require("fs")`, renames via
// `{ readFileSync: rf }`) keys the same tables.
if (ts.isBindingElement(decl) && decl.name !== undefined && ts.isIdentifier(decl.name)) {
const varDecl = decl.parent.parent;
const varDecl = decl.parent?.parent;
if (
ts.isObjectBindingPattern(decl.parent) &&
ts.isVariableDeclaration(varDecl) &&
@@ -285,7 +285,7 @@ function lowerBuiltinOptionalDefault(
}
if (!ts.isImportSpecifier(decl) && !ts.isExportSpecifier(decl)) return null;
if (ts.isImportSpecifier(decl)) {
const importDecl = decl.parent.parent.parent;
const importDecl = decl.parent?.parent?.parent;
if (!ts.isImportDeclaration(importDecl) || !ts.isStringLiteral(importDecl.moduleSpecifier)) {
return null;
}
@@ -5048,9 +5048,9 @@ function lowerJsonCallback(lowerer: Lowerer, node: ts.Expression, role: "replace
}
}
}
let importDecl: ts.Node;
if (ts.isImportSpecifier(decl)) importDecl = decl.parent.parent.parent;
else if (ts.isNamespaceImport(decl)) importDecl = decl.parent.parent;
let importDecl: ts.Node | undefined;
if (ts.isImportSpecifier(decl)) importDecl = decl.parent?.parent?.parent;
else if (ts.isNamespaceImport(decl)) importDecl = decl.parent?.parent;
else if (ts.isImportClause(decl)) importDecl = decl.parent;
else return null;
if (!ts.isImportDeclaration(importDecl) || !ts.isStringLiteral(importDecl.moduleSpecifier)) {
@@ -2425,11 +2425,11 @@ export function implicitMethodCallInfersReturn(lowerer: Lowerer, call: ts.CallEx
if (!sym) lowerer.unsupported("SC1090", decl.name, "this binding form");
const implicit = implicitAnyParamSymbolsOf(lowerer, fnNode);
if (!implicit) lowerer.unsupported("SC1090", fnNode, "this function form"); // defensive: the probe proved it
const stmt = decl.parent.parent;
const stmt = decl.parent?.parent;
const info: GenericFnInfo = {
decl: fnNode,
baseName: name,
qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt, decl) + `${name}%l${decl.getStart()}`),
qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt ?? decl, decl) + `${name}%l${decl.getStart()}`),
typeParams: [],
instances: new Map(),
implicitParams: implicit,
@@ -6783,7 +6783,7 @@ function loweredTemplateStrings(
return (
!!decl &&
ts.isFunctionDeclaration(decl) &&
(ts.isSourceFile(decl.parent) || lowerer.nsBlocks.get(decl.parent) === "flattened")
(ts.isSourceFile(decl.parent) || (decl.parent !== undefined && lowerer.nsBlocks.get(decl.parent) === "flattened"))
);
}
@@ -9556,7 +9556,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
// arrow/function-expression properties skip that walk and the compiled
// instances are plain module functions).
if (fnNode.body) lowerer.rejectThisInObjectMethod(fnNode.body);
for (let n: ts.Node = literal.parent; n && !ts.isSourceFile(n); n = n.parent) {
for (let n: ts.Node | undefined = literal.parent; n && !ts.isSourceFile(n); n = n.parent) {
if (ts.isFunctionLike(n)) {
lowerer.unsupported(
"SC1090",
@@ -9695,7 +9695,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
if (
!decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) ||
decl.getSourceFile().isDeclarationFile || decl.initializer === undefined ||
!ts.isVariableStatement(decl.parent.parent)
!ts.isVariableStatement(decl.parent?.parent)
) {
return null;
}
@@ -9866,7 +9866,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
// lowerVarDeclList's contract requires a lowered statement for it, so
// the no-storage family never claims it (catch bindings sit outside a
// variable statement too and stay out the same way).
if (!ts.isVariableStatement(decl.parent.parent)) return false;
if (!ts.isVariableStatement(decl.parent?.parent)) return false;
const sf = decl.getSourceFile();
if (sf.isDeclarationFile || isJsSourceFile(sf)) return false;
if (decl.initializer !== undefined && !sideEffectFreeValueExpr(lowerer, decl.initializer)) return false;
@@ -9987,7 +9987,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
if (existing) return existing;
const name = (decl.name as ts.Identifier).text;
if (fnNode.asteriskToken) lowerer.unsupported("SC1071", fnNode);
for (let n: ts.Node = decl.parent; n !== undefined && !ts.isSourceFile(n); n = n.parent) {
for (let n: ts.Node | undefined = decl.parent; n !== undefined && !ts.isSourceFile(n); n = n.parent) {
if (ts.isFunctionLike(n)) {
lowerer.unsupported(
"SC1090",
@@ -10041,11 +10041,11 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
lowerer.unsupported("SC1031", param);
}
}
const stmt = decl.parent.parent; // declarator → list → statement (nsPathPrefix wants the statement)
const stmt = decl.parent?.parent; // declarator → list → statement (nsPathPrefix wants the statement)
const info: GenericFnInfo = {
decl: fnNode,
baseName: name,
qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt, decl) + name),
qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt ?? decl, decl) + name),
typeParams,
instances: new Map(),
};
@@ -10347,7 +10347,6 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I
thisT.kind !== "dyn" &&
thisT.kind !== "jsval" &&
(!thisArg || ts.isIdentifier(thisArg) || ts.isLiteralExpression(thisArg) ||
thisArg.kind === ts.SyntaxKind.UndefinedKeyword ||
thisArg.kind === ts.SyntaxKind.NullKeyword ||
isUnitType(thisT));
if (provablyNot) {
@@ -3141,7 +3141,7 @@ export function staticFieldWriteTarget(lowerer: Lowerer, access: ts.PropertyAcce
* Node for each shape. */
function namedEvaluationName(expr: ts.ClassExpression): string {
if (expr.name) return expr.name.text;
let p: ts.Node = expr.parent;
let p: ts.Node | undefined = expr.parent;
while (ts.isParenthesizedExpression(p)) p = p.parent;
if (ts.isVariableDeclaration(p) && ts.isIdentifier(p.name) && p.initializer !== undefined) return p.name.text;
if (ts.isBinaryExpression(p) && p.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(p.left)) return p.left.text;
@@ -3185,7 +3185,7 @@ export function staticFieldWriteTarget(lowerer: Lowerer, access: ts.PropertyAcce
"class expressions inside generic functions (each instantiation would need its own class)",
);
}
for (let p: ts.Node = expr.parent; !ts.isSourceFile(p); p = p.parent) {
for (let p: ts.Node | undefined = expr.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
if (ts.isFunctionLike(p) || ts.isClassStaticBlockDeclaration(p)) {
lowerer.unsupported(
"SC1090",
@@ -3213,7 +3213,7 @@ export function staticFieldWriteTarget(lowerer: Lowerer, access: ts.PropertyAcce
// Anything subtler (multi-declarator statements, arguments evaluated
// after other side effects) is a named fence, never a reordering.
if (info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0) {
let holder: ts.Node = expr.parent;
let holder: ts.Node | undefined = expr.parent;
while (
ts.isParenthesizedExpression(holder) || ts.isClassExpression(holder) ||
ts.isHeritageClause(holder) || ts.isExpressionWithTypeArguments(holder)
@@ -3720,7 +3720,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou
const seen = new Set<ts.Type>();
const markBases = (type: ts.Type): void => {
const target = type.isTypeReference() ? type.getTarget() : type;
if (!target.isClassOrInterface() || seen.has(target)) return;
if (target === undefined || !target.isClassOrInterface() || seen.has(target)) return;
seen.add(target);
for (const base of lowerer.checker.getBaseTypes(target)) {
for (const name of names) {
@@ -6156,7 +6156,7 @@ const ITER_TERMINALS = new Set(["toArray", "forEach", "reduce", "some", "every",
// when nothing observes the intersection: the result is DISCARDED
// (expression-statement position — the mutate-in-place spelling), or
// the intersection collapses back to the target's own mapped record.
let parent: ts.Node = call.parent;
let parent: ts.Node | undefined = call.parent;
while (ts.isParenthesizedExpression(parent) || ts.isVoidExpression(parent)) parent = parent.parent;
const discarded = ts.isExpressionStatement(parent);
if (!discarded) {
@@ -1340,7 +1340,7 @@ function isEmitForwardSpread(lowerer: Lowerer, method: ts.MethodDeclaration, id:
function isWriteTarget(id: ts.Identifier): boolean {
let n: ts.Node = id;
for (;;) {
const p: ts.Node = n.parent;
const p: ts.Node | undefined = n.parent;
if (ts.isBinaryExpression(p)) {
const k = p.operatorToken.kind;
return p.left === n && k >= ts.SyntaxKind.FirstAssignment && k <= ts.SyntaxKind.LastAssignment;
@@ -87,7 +87,7 @@ function expandoFnSymbolOf(lowerer: Lowerer, recv: ts.Expression): ts.Symbol | n
return ts.isSourceFile(decl.parent) ? sym : null;
}
if (ts.isVariableDeclaration(decl)) {
if (!ts.isVariableStatement(decl.parent.parent) || !ts.isSourceFile(decl.parent.parent.parent)) return null;
if (!ts.isVariableStatement(decl.parent?.parent) || !ts.isSourceFile(decl.parent?.parent?.parent)) return null;
if ((ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0) return null;
// The const's VALUE must be a function created here (arrow/function
// initializer) — a callable TYPE alone can be satisfied by island
@@ -1677,7 +1677,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
const whole = cjsClassExprWholeExportOf(expr.getSourceFile());
if (whole) {
let inBody = false;
for (let p: ts.Node = expr.parent; !ts.isSourceFile(p); p = p.parent) {
for (let p: ts.Node | undefined = expr.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
if (ts.isFunctionLike(p) || ts.isClassDeclaration(p) || ts.isClassExpression(p)) {
inBody = true;
break;
@@ -7416,7 +7416,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
if (!ts.isParameter(valDecl) || !ts.isIdentifier(valDecl.name)) continue;
if (!bindingNeverReassigned(lowerer, valSym, valDecl)) continue;
const fn = valDecl.parent;
if (!(e.pos >= fn.pos && e.end <= fn.end)) continue;
if (fn === undefined || !(e.pos >= fn.pos && e.end <= fn.end)) continue;
const valT = lowerer.typeOf(opnd); // override-aware: implicit bindings compose
const mapped = lowerer.mapTypeOf(valT);
if (mapped?.kind !== "union") continue;
@@ -7707,7 +7707,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
if (!isJsSourceFile(expr.getSourceFile())) return null;
// The enclosing accessor, crossing only arrow boundaries (arrows
// inherit `this`; any other function form rebinds it).
let n: ts.Node = expr.parent;
let n: ts.Node | undefined = expr.parent;
while (n !== undefined && !ts.isGetAccessorDeclaration(n) && !ts.isSetAccessorDeclaration(n)) {
if (ts.isFunctionLike(n) && !ts.isArrowFunction(n)) return null;
if (ts.isSourceFile(n)) return null;
@@ -9538,7 +9538,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
// Module level only: a unique-symbol const inside a FUNCTION is a
// fresh runtime identity per call — one static slot would conflate
// what JS keeps distinct.
if (!ts.isVariableStatement(decl.parent.parent) || !ts.isSourceFile(decl.parent.parent.parent)) return null;
if (!ts.isVariableStatement(decl.parent?.parent) || !ts.isSourceFile(decl.parent?.parent?.parent)) return null;
const init = decl.initializer;
if (!init || !ts.isCallExpression(init) || init.questionDotToken) return null;
if (!ts.isIdentifier(init.expression) || init.expression.text !== "Symbol") return null;
@@ -3116,11 +3116,11 @@ export function lowerStaticReadableStreamReaderCall(
// `export type { x }` / `export { type x }`: erased at runtime.
for (const d of lowerer.checker.declarationsOf(sym)) {
if (ts.isExportSpecifier(d)) {
const exportDecl = d.parent.parent;
const exportDecl = d.parent?.parent;
if (d.isTypeOnly || (ts.isExportDeclaration(exportDecl) && exportDecl.isTypeOnly)) return null;
}
if (ts.isImportSpecifier(d)) {
const clause = d.parent.parent;
const clause = d.parent?.parent;
if (d.isTypeOnly || (ts.isImportClause(clause) && clause.phaseModifier === ts.SyntaxKind.TypeKeyword)) return null;
}
}
@@ -3145,7 +3145,7 @@ export function lowerStaticReadableStreamReaderCall(
}
const sig = lowerer.fnSigsBySymbol.get(resolved);
const decl0 = lowerer.checker.declarationsOf(resolved).find(
(d) => ts.isFunctionDeclaration(d) && (ts.isSourceFile(d.parent) || lowerer.nsBlocks.get(d.parent) === "flattened"),
(d) => ts.isFunctionDeclaration(d) && (ts.isSourceFile(d.parent) || (d.parent !== undefined && lowerer.nsBlocks.get(d.parent) === "flattened")),
);
if (sig && decl0) {
if (!sig.params.every((p) => p.mode === "required")) {
@@ -399,7 +399,7 @@ function instantiateMixinCall(lowerer: Lowerer, call: ts.CallExpression, shape:
// enters through its heritage clause (the extends expression of a
// once-evaluated declaration).
let prev: ts.Node = call;
for (let p: ts.Node = call.parent; !ts.isSourceFile(p); prev = p, p = p.parent) {
for (let p: ts.Node | undefined = call.parent; p !== undefined && !ts.isSourceFile(p); prev = p, p = p.parent) {
if (ts.isFunctionLike(p) || ts.isClassStaticBlockDeclaration(p)) {
lowerer.unsupported(
"SC1090",
@@ -571,7 +571,7 @@ export function mixinIntersectionInstanceType(lowerer: Lowerer, widened: ts.Type
* discovery/emit drift. */
function pinnedMixinCallPosition(call: ts.CallExpression): boolean {
let n: ts.Node = call;
for (let p: ts.Node = n.parent; !ts.isSourceFile(p); n = p, p = p.parent) {
for (let p: ts.Node | undefined = n.parent; p !== undefined && !ts.isSourceFile(p); n = p, p = p.parent) {
if (ts.isParenthesizedExpression(p)) continue;
if (ts.isCallExpression(p) && p.arguments.length === 1 && p.arguments[0] === n) continue;
if (ts.isVariableDeclaration(p) && p.initializer === n) {
@@ -589,7 +589,7 @@ function pinnedMixinCallPosition(call: ts.CallExpression): boolean {
* statement, or a top-level class declaration's heritage clause. */
function staticsEvalStatementOf(call: ts.CallExpression): ts.Statement | null {
let n: ts.Node = call;
for (let p: ts.Node = n.parent; ; n = p, p = p.parent) {
for (let p: ts.Node | undefined = n.parent; p !== undefined; n = p, p = p.parent) {
if (ts.isParenthesizedExpression(p)) continue;
if (ts.isCallExpression(p) && p.arguments.length === 1 && p.arguments[0] === n) continue;
if (
@@ -604,4 +604,5 @@ function staticsEvalStatementOf(call: ts.CallExpression): ts.Statement | null {
if (ts.isClassDeclaration(p) && ts.isSourceFile(p.parent) && ts.isHeritageClause(n)) return p;
return null;
}
return null;
}
@@ -493,7 +493,7 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile)
while (ts.isParenthesizedExpression(lhs)) lhs = lhs.expression;
const throwsBeforeTheWrite = (): boolean => {
if (k !== ts.SyntaxKind.EqualsToken || !ts.isIdentifier(lhs)) return false;
let p: ts.Node = n.parent;
let p: ts.Node | undefined = n.parent;
while (ts.isParenthesizedExpression(p)) p = p.parent;
if (!ts.isExpressionStatement(p)) return false;
return ambientUndefVarRootOf(lowerer, n.right) !== null;
@@ -3256,8 +3256,9 @@ function neverReassigned(lowerer: Lowerer, sym: ts.Symbol): boolean {
// source file for module-level bindings. Writes anywhere inside
// (nested closures included) disqualify.
let scope: ts.Node = decl;
while (!ts.isFunctionLike(scope.parent) && !ts.isSourceFile(scope.parent)) scope = scope.parent;
const root: ts.Node = ts.isFunctionLike(scope.parent) ? scope.parent : scope.parent;
while (scope.parent !== undefined && !ts.isFunctionLike(scope.parent) && !ts.isSourceFile(scope.parent)) scope = scope.parent;
const root = scope.parent;
if (root === undefined) return false;
let written = false;
const hitsSym = (n: ts.Node): boolean => {
if (ts.isIdentifier(n) && lowerer.checker.getSymbolAtLocation(n) === sym) return true;
@@ -327,7 +327,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean
export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean {
const sf = decl.getSourceFile();
if (!isProvenanceSourceFile(sf.fileName)) return false;
const stmt = decl.parent.parent;
const stmt = decl.parent?.parent;
if (!ts.isVariableStatement(stmt) || !pureAnnotatedDeadConst(stmt, sf)) return false;
const diagsBefore = lowerer.diags.length;
let mapped = false;
@@ -553,7 +553,7 @@ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDec
// `const id = setInterval(cb)` self-capture — the global slot is the
// binding). Function-scope declarations have no global; they predeclare.
if (lowerer.globalsBySymbol.has(symbol)) return false;
const varStmt = decl.parent.parent;
const varStmt = decl.parent?.parent;
if (!ts.isVariableStatement(varStmt)) return false; // for-initializers stay out
for (let i = lowerer.activeStmtLists.length - 1; i >= 0; i--) {
const entry = lowerer.activeStmtLists[i]!;
@@ -645,7 +645,7 @@ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDec
* enclosing binding patterns), or null when the name belongs to some
* other declaration form — a parameter pattern, a catch binding. */
function hostVariableDeclarationOf(name: ts.Identifier): ts.VariableDeclaration | null {
let n: ts.Node = name.parent;
let n: ts.Node | undefined = name.parent;
while (ts.isBindingElement(n) || ts.isArrayBindingPattern(n) || ts.isObjectBindingPattern(n)) {
n = n.parent;
}
@@ -3338,7 +3338,7 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool
function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration): IrType | null {
if (!ts.isIdentifier(decl.name)) return null;
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
const scope = decl.parent.parent.parent;
const scope = decl.parent?.parent?.parent;
if (symbol === undefined || (!ts.isBlock(scope) && !ts.isSourceFile(scope))) return null;
const isPlainAssignmentTarget = (node: ts.Identifier): boolean => {
@@ -3485,8 +3485,8 @@ export function lowerVarDecl(lowerer: Lowerer, decl: ts.VariableDeclaration, isL
if (
!decl.initializer &&
(decl.name.text === "module" || decl.name.text === "exports") &&
ts.isVariableStatement(decl.parent.parent) &&
ts.isSourceFile(decl.parent.parent.parent) &&
ts.isVariableStatement(decl.parent?.parent) &&
ts.isSourceFile(decl.parent?.parent?.parent) &&
!isJsSourceFile(decl.getSourceFile()) &&
!decl.getSourceFile().statements.some(
(s) =>
@@ -2289,7 +2289,7 @@ export class Lowerer {
? this.checker.declarationsOf(symbol).find(ts.isNamespaceImport)
: undefined;
if (namespaceDecl === undefined) return null;
const importDecl = namespaceDecl.parent.parent;
const importDecl = namespaceDecl.parent?.parent;
if (!ts.isImportDeclaration(importDecl) || !ts.isStringLiteral(importDecl.moduleSpecifier)) return null;
if (this.externalTypes.has(importDecl.moduleSpecifier.text)) return importDecl.moduleSpecifier.text;
const dep = this.moduleSourceFileOf(importDecl.getSourceFile(), importDecl.moduleSpecifier);
@@ -2314,22 +2314,22 @@ export class Lowerer {
let specNode: ts.Expression | undefined;
let importedName: string | null = null;
if (ts.isImportSpecifier(decl)) {
const importDecl: ts.Node = decl.parent.parent.parent;
const importDecl: ts.Node | undefined = decl.parent?.parent?.parent;
if (ts.isImportDeclaration(importDecl)) specNode = importDecl.moduleSpecifier;
importedName = (decl.propertyName ?? decl.name).text;
} else if (ts.isImportClause(decl)) {
if (ts.isImportDeclaration(decl.parent)) specNode = decl.parent.moduleSpecifier;
importedName = "default";
} else if (ts.isNamespaceImport(decl)) {
const importDecl: ts.Node = decl.parent.parent;
const importDecl: ts.Node | undefined = decl.parent?.parent;
if (ts.isImportDeclaration(importDecl)) specNode = importDecl.moduleSpecifier;
importedName = null;
} else if (ts.isExportSpecifier(decl)) {
const exportDecl: ts.Node = decl.parent.parent;
const exportDecl: ts.Node | undefined = decl.parent?.parent;
if (ts.isExportDeclaration(exportDecl)) specNode = exportDecl.moduleSpecifier;
importedName = (decl.propertyName ?? decl.name).text;
} else if (ts.isNamespaceExport(decl)) {
const exportDecl: ts.Node = decl.parent;
const exportDecl: ts.Node | undefined = decl.parent;
if (ts.isExportDeclaration(exportDecl)) specNode = exportDecl.moduleSpecifier;
importedName = "*";
} else {
@@ -2383,11 +2383,11 @@ export class Lowerer {
spec = d.parent.moduleSpecifier;
name = "default";
} else if (d && ts.isImportSpecifier(d)) {
const idecl: ts.Node = d.parent.parent.parent;
const idecl: ts.Node | undefined = d.parent?.parent?.parent;
if (ts.isImportDeclaration(idecl)) spec = idecl.moduleSpecifier;
name = (d.propertyName ?? d.name).text;
} else if (d && ts.isExportSpecifier(d)) {
const edecl: ts.Node = d.parent.parent;
const edecl: ts.Node | undefined = d.parent?.parent;
if (ts.isExportDeclaration(edecl)) spec = edecl.moduleSpecifier;
name = (d.propertyName ?? d.name).text;
}
@@ -10118,7 +10118,7 @@ export class Lowerer {
}
}
if (!ts.isNamespaceImport(decl)) return null;
const importDecl = decl.parent.parent;
const importDecl = decl.parent?.parent;
if (!ts.isImportDeclaration(importDecl) || !ts.isStringLiteral(importDecl.moduleSpecifier)) return null;
return canonicalBuiltinModule(importDecl.moduleSpecifier.text);
}
+2 -2
View File
@@ -1117,7 +1117,7 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool
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;
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;
@@ -1181,7 +1181,7 @@ function stableCreateRequireBindingReason7(program: ts.Program, decl: ts.Variabl
if (!cache) stableCreateRequireReasons7.set(program, cache = new Map());
if (cache.has(decl)) return cache.get(decl)!;
const check = (): string | null => {
const stmt = decl.parent.parent;
const stmt = decl.parent?.parent;
if (!ts.isIdentifier(decl.name) || !ts.isVariableStatement(stmt) || !ts.isSourceFile(stmt.parent)) {
return "its mutable createRequire binding is outside the module's top level";
}
+6 -37
View File
@@ -1,15 +1,11 @@
/* The TS7 adapter: the census's ts.* surface (the survey's top-40 table plus
* the long tail of census-ts-members.tsv) re-exported over typescript@7.0.2's
* unstable API, shaped so the phase-2 mechanical port swaps
/* The frontend's ts.* surface uses scriptc's concrete native AST, semantic
* objects and session lifecycle over the pinned TypeScript 7 protocol. It
* Lowering modules use a namespace import to retain both values and types:
*
* import ts from "typescript";
* for
* import * as ts from "./ts7/adapter.js"; // path per file
*
* per file and keeps every `ts.name` spelling — values (guards, enums,
* helpers, createProgram) and types (ts.Expression, ts.Node, ts.Symbol, ...)
* alike. The namespace-import form is the swap because ESM has no way to
* hang types off a default export.
* This preserves familiar `ts.name` spellings for guards, enums, helpers,
* createProgram, Expression, Node, Symbol, and the other frontend types.
*
* TWO-WORLD DISCIPLINE. typescript@7.0.2 is the REAL "typescript"
* dependency; typescript@5.9.3 stays installed under the "typescript5"
@@ -44,8 +40,6 @@
* (`ts.Types` in the census tsv is a comment-text artifact, not an API.) */
export * from "./enums.js";
// Prefer the owned runtime declarations over the legacy AST type barrel.
export { InternalSymbolName, ModifierFlags, NodeFlags, ScriptKind, ScriptTarget, SyntaxKind, TokenFlags } from "./enums.js";
export * from "./ast.js";
export * from "./checker.js";
export * from "./program-adapter.js";
@@ -54,32 +48,7 @@ export * from "./program-adapter.js";
export type { CheckerFacade as TypeChecker } from "./checker.js";
export type { Ts7Program as Program } from "./program-adapter.js";
/* Checker-world object types under their census names. Symbol and Signature
* are 7's client classes (identity-bearing — the registry dedupes by server
* handle); Type and friends are the client interfaces. TupleTypeReference
* aliases 7's TupleType: in 5.9.3 a tuple's reference and its target were
* split, in 7 the client hands back one object playing both roles (it
* satisfies getTypeArguments and elementFlags alike, the two things the
* census does with it). */
export type {
CompilerOptions,
Diagnostic,
IndexInfo,
InterfaceType,
StringLiteralType,
NumberLiteralType,
BooleanLiteralType,
ObjectType,
Signature,
Symbol,
TupleType,
TupleType as TupleTypeReference,
Type,
TypePredicate,
TypeReference,
UnionOrIntersectionType,
UnionType,
} from "typescript/unstable/sync";
export * from "./semantic-types.js";
/* No default export, deliberately: ESM cannot hang the TYPE side of the
* census (ts.Expression, ts.Node, ...) off a default binding, so a default
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { expect, test } from "vitest";
import * as guards from "./ast-guards.generated.js";
import { tokenToString } from "./ast-tokens.generated.js";
import { NodeFlags, OuterExpressionKinds, ScriptKind, SyntaxKind } from "./enums.js";
const require = createRequire(import.meta.url);
const sdkRoot = dirname(require.resolve("typescript/package.json"));
type Predicate = (value: unknown, kinds?: number) => boolean;
const upstream = require(join(sdkRoot, "dist/ast/is.js")) as Record<string, Predicate>;
const owned = guards as unknown as Record<string, Predicate>;
const signatures = ["is.generated.d.ts", "is.d.ts"].flatMap((file) =>
[...readFileSync(join(sdkRoot, "dist/ast", file), "utf8").matchAll(/export declare function (is\w+)\((node|kind):/g)]
.map((match) => ({ name: match[1]!, node: match[2] === "node" })),
);
test("all owned guards match the pinned kind and flag predicates", () => {
expect(signatures.length).toBeGreaterThan(300);
expect(Object.keys(guards).filter((name) => name.startsWith("is")).sort()).toEqual(signatures.map((entry) => entry.name).sort());
for (const entry of signatures) {
const oracle = upstream[entry.name]!;
const actual = owned[entry.name]!;
for (let kind = -1; kind <= SyntaxKind.Count + 1; kind++) {
for (const scriptKind of [ScriptKind.TS, ScriptKind.JS]) {
const expression = { kind: SyntaxKind.AsExpression, type: { flags: NodeFlags.Reparsed } };
const value = entry.node ? { kind, expression, getSourceFile: () => ({ scriptKind }) } : kind;
expect(actual(value), `${entry.name}(${kind}, ${scriptKind})`).toBe(oracle(value));
if (entry.name === "isOuterExpression") {
for (const mask of [0, OuterExpressionKinds.Parentheses, OuterExpressionKinds.ExcludeJSDocTypeAssertion | OuterExpressionKinds.All]) {
expect(actual(value, mask), `${entry.name}(${kind}, ${scriptKind}, ${mask})`).toBe(oracle(value, mask));
}
}
}
}
if (entry.node) expect(actual(undefined), `${entry.name}(missing parent)`).toBe(false);
}
});
test("native token spelling preserves every SDK token and unknown values", () => {
const oracle = require(join(sdkRoot, "dist/ast/scanner.js")) as { tokenToString: typeof tokenToString };
for (let kind = -1; kind <= SyntaxKind.Count + 1; kind++) expect(tokenToString(kind), String(kind)).toBe(oracle.tokenToString(kind));
for (const kind of [0.5, NaN, Infinity, -Infinity]) expect(tokenToString(kind)).toBeUndefined();
});
@@ -5,10 +5,11 @@ import { dirname, join } from "node:path";
import { afterAll, beforeAll, expect, test } from "vitest";
import type { SourceFile, Node } from "typescript/unstable/ast";
import { AstFile, AstNode } from "./ast-node.js";
import { AstKind, KIND_NODE_LIST, astChildNames } from "./ast-schema.generated.js";
import { AstKind, KIND_NODE_LIST, astChildNames, HEADER_OFFSET_NODES, NODE_LEN } from "./ast-schema.generated.js";
import { ts7Executable } from "./rpc-api.js";
import { Ts7RpcClient } from "./rpc-client.js";
import { spawnTs7Wire } from "./rpc-process.js";
import { isJSDocTypeLiteral, isJSDocPropertyTag } from "./ast-guards.generated.js";
const require = createRequire(import.meta.url);
const sdkRoot = dirname(require.resolve("typescript/package.json"));
@@ -50,6 +51,7 @@ const cases: Record<string, string> = {
"view.tsx": 'const view = <main aria-label="a"><p>Hello 😀</p>{value}<Thing {...props} /></main>;\nconst fragment = <><span /> text </>;',
"types.d.ts": 'export interface Thing { name: string; }\ndeclare module "ambient.name" { export const a: number; }\n',
"docs.js": '/** @typedef {{ name: string, count?: number }} Item */\n/** @param {Item} item Description\n * @returns {string} result\n * @deprecated use another\n */\nexport function show(item) { return item.name; }',
"properties.js": '/** @typedef {Object} Options\n * @property {string} name\n * @property {number} count\n */\n/** @type {Options} */ const options = { name: "x", count: 2 };',
"missing.ts": 'const incomplete = ;\nfunction f( {\n',
"empty.ts": "",
};
@@ -78,6 +80,23 @@ function ids(nodes: readonly Node[] | readonly AstNode[] | undefined): (number |
return nodes?.map((n) => (n as OracleNode).index);
}
test("JSDoc property refinements describe the pinned native wire", () => {
const { file, oracle } = decoded.get("properties.js")!;
let checked = 0;
for (let index = 1; index < file.wire.nodeCount; index++) {
if (file.wire.kind(index) !== AstKind.JSDocTypeLiteral) continue;
const node = file.node(index);
if (!isJSDocTypeLiteral(node)) throw new Error("invalid JSDoc kind");
const tag = node.jsdocPropertyTags;
expect(isJSDocPropertyTag(tag)).toBe(true);
expect(tag?.kind).toBe(AstKind.JSDocPropertyTag);
expect(tag?.index).toBe(Reflect.get(oracle.getOrCreateNodeAtIndex(index), "jsdocPropertyTags").index);
expect(tag).toBe(node.childNode("jsdocPropertyTags"));
checked++;
}
expect(checked).toBeGreaterThan(0);
});
for (const name of [...Object.keys(cases), "model.ts"]) {
test(`nodes, named children, flags, text and spans match TypeScript: ${name}`, () => {
const { file, oracle } = decoded.get(name)!;
@@ -150,3 +169,12 @@ test("checker handles reject cross-file, wrong-kind and nil identities", () => {
expect(() => file.resolve(`1.${AstKind.SourceFile}.${file.root.path}.other`)).toThrow("another source file");
expect(() => file.resolve(`0.${AstKind.SourceFile}.${file.root.path}`)).toThrow("nil");
});
test("a source view requires a source-file root", () => {
const bytes = decoded.get("main.ts")!.bytes.slice();
const words = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const nodes = words.getUint32(HEADER_OFFSET_NODES, true);
words.setUint32(nodes + NODE_LEN, AstKind.Identifier, true);
expect(() => new AstFile(bytes)).toThrow("expected a source file root");
expect(decoded.get("empty.ts")!.file.root.statements).toEqual([]);
});
+27 -24
View File
@@ -2,13 +2,15 @@ import { AstDecodeError } from "./ast-bytes.js";
import { AstKind, AstModifierFlags, AstNodeFlags, KIND_NODE_LIST } from "./ast-schema.generated.js";
import { astLineOfPosition, astLineStarts, astSkipTrivia } from "./ast-text.js";
import { AstWireFile, parseAstNodeHandle, type AstFileReference } from "./ast-wire.js";
import type { SourceFile } from "./ast-types.js";
import { SyntaxKind, NodeFlags } from "./enums.js";
/** One immutable AST response owns one object per node and one array per
* list. Parent access is lazy, so resolving a checker handle near the bottom
* of a deep tree never recursively constructs its ancestors. */
export class AstFile {
readonly wire: AstWireFile;
readonly root: AstNode;
readonly root: SourceFile;
private readonly nodes = new Map<number, AstNode>();
private readonly lists = new Map<number, AstNode[]>();
private readonly references = new Map<number, AstFileReference[]>();
@@ -22,7 +24,8 @@ export class AstFile {
private readonly materialized?: () => void,
) {
this.wire = new AstWireFile(bytes);
this.root = new AstNode(this, 1);
if (this.wire.kind(1) !== AstKind.SourceFile) throw new AstDecodeError("expected a source file root");
this.root = new AstNode(this, 1) as SourceFile;
this.nodes.set(1, this.root);
}
@@ -95,10 +98,10 @@ export class AstFile {
export class AstNode {
constructor(readonly file: AstFile, readonly index: number) {}
get kind(): number { return this.file.wire.kind(this.index); }
get kind(): SyntaxKind { return this.file.wire.kind(this.index); }
get pos(): number { return this.file.wire.pos(this.index); }
get end(): number { return this.file.wire.end(this.index); }
get flags(): number { return this.file.wire.flags(this.index); }
get flags(): NodeFlags { return this.file.wire.flags(this.index); }
get data(): number { return this.file.wire.data(this.index); }
get id(): string { return `${this.index}.${this.kind}.${this.file.root.path}`; }
get parent(): AstNode | undefined {
@@ -122,7 +125,7 @@ export class AstNode {
return this.file.wire.kind(index) === KIND_NODE_LIST ? this.file.list(index) : this.file.node(index);
}
forEachChild<T>(visitNode: (node: AstNode) => T, visitList?: (nodes: AstNode[]) => T): T | undefined {
forEachChild<T>(visitNode: (node: AstNode) => T, visitList?: (nodes: readonly AstNode[]) => T): T | undefined {
for (const index of this.file.wire.children(this.index)) {
const kind = this.file.wire.kind(index);
if (kind === KIND_NODE_LIST) {
@@ -152,7 +155,7 @@ export class AstNode {
return docs.length === 0 ? undefined : docs;
}
getSourceFile(): AstNode { return this.file.root; }
getSourceFile(): SourceFile { return this.file.root; }
getStart(sourceFile?: AstNode, includeJsDocComment?: boolean): number {
if (this.pos === this.end && this.pos >= 0 && this.kind !== AstKind.EndOfFile) return this.pos;
const source = sourceFile ?? this.file.root;
@@ -235,31 +238,31 @@ export class AstNode {
// Generated from typescript@7.0.2; run scripts/generate-ts7-ast-schema.mjs.
get argument(): AstNode | undefined { return this.childNode("argument"); }
get argumentExpression(): AstNode | undefined { return this.childNode("argumentExpression"); }
get arguments(): AstNode[] | undefined { return this.childList("arguments"); }
get arguments(): readonly AstNode[] | undefined { return this.childList("arguments"); }
get assertsModifier(): AstNode | undefined { return this.childNode("assertsModifier"); }
get asteriskToken(): AstNode | undefined { return this.childNode("asteriskToken"); }
get attributes(): AstNode | AstNode[] | undefined { return this.child("attributes"); }
get attributes(): AstNode | readonly AstNode[] | undefined { return this.child("attributes"); }
get awaitModifier(): AstNode | undefined { return this.childNode("awaitModifier"); }
get block(): AstNode | undefined { return this.childNode("block"); }
get body(): AstNode | undefined { return this.childNode("body"); }
get caseBlock(): AstNode | undefined { return this.childNode("caseBlock"); }
get catchClause(): AstNode | undefined { return this.childNode("catchClause"); }
get checkType(): AstNode | undefined { return this.childNode("checkType"); }
get children(): AstNode | AstNode[] | undefined { return this.child("children"); }
get children(): AstNode | readonly AstNode[] | undefined { return this.child("children"); }
get className(): AstNode | undefined { return this.childNode("className"); }
get clauses(): AstNode[] | undefined { return this.childList("clauses"); }
get clauses(): readonly AstNode[] | undefined { return this.childList("clauses"); }
get closingElement(): AstNode | undefined { return this.childNode("closingElement"); }
get closingFragment(): AstNode | undefined { return this.childNode("closingFragment"); }
get colonToken(): AstNode | undefined { return this.childNode("colonToken"); }
get comment(): AstNode[] | undefined { return this.childList("comment"); }
get comment(): readonly AstNode[] | undefined { return this.childList("comment"); }
get condition(): AstNode | undefined { return this.childNode("condition"); }
get constraint(): AstNode | undefined { return this.childNode("constraint"); }
get declarationList(): AstNode | undefined { return this.childNode("declarationList"); }
get declarations(): AstNode[] | undefined { return this.childList("declarations"); }
get declarations(): readonly AstNode[] | undefined { return this.childList("declarations"); }
get defaultType(): AstNode | undefined { return this.childNode("defaultType"); }
get dotDotDotToken(): AstNode | undefined { return this.childNode("dotDotDotToken"); }
get elementType(): AstNode | undefined { return this.childNode("elementType"); }
get elements(): AstNode[] | undefined { return this.childList("elements"); }
get elements(): readonly AstNode[] | undefined { return this.childList("elements"); }
get elseStatement(): AstNode | undefined { return this.childNode("elseStatement"); }
get endOfFileToken(): AstNode | undefined { return this.childNode("endOfFileToken"); }
get equalsGreaterThanToken(): AstNode | undefined { return this.childNode("equalsGreaterThanToken"); }
@@ -272,7 +275,7 @@ export class AstNode {
get falseType(): AstNode | undefined { return this.childNode("falseType"); }
get finallyBlock(): AstNode | undefined { return this.childNode("finallyBlock"); }
get head(): AstNode | undefined { return this.childNode("head"); }
get heritageClauses(): AstNode[] | undefined { return this.childList("heritageClauses"); }
get heritageClauses(): readonly AstNode[] | undefined { return this.childList("heritageClauses"); }
get importClause(): AstNode | undefined { return this.childNode("importClause"); }
get incrementor(): AstNode | undefined { return this.childNode("incrementor"); }
get indexType(): AstNode | undefined { return this.childNode("indexType"); }
@@ -281,8 +284,8 @@ export class AstNode {
get label(): AstNode | undefined { return this.childNode("label"); }
get left(): AstNode | undefined { return this.childNode("left"); }
get literal(): AstNode | undefined { return this.childNode("literal"); }
get members(): AstNode[] | undefined { return this.childList("members"); }
get modifiers(): AstNode[] | undefined { return this.childList("modifiers"); }
get members(): readonly AstNode[] | undefined { return this.childList("members"); }
get modifiers(): readonly AstNode[] | undefined { return this.childList("modifiers"); }
get moduleReference(): AstNode | undefined { return this.childNode("moduleReference"); }
get moduleSpecifier(): AstNode | undefined { return this.childNode("moduleSpecifier"); }
get name(): AstNode | undefined { return this.childNode("name"); }
@@ -297,9 +300,9 @@ export class AstNode {
get operand(): AstNode | undefined { return this.childNode("operand"); }
get operatorToken(): AstNode | undefined { return this.childNode("operatorToken"); }
get parameterName(): AstNode | undefined { return this.childNode("parameterName"); }
get parameters(): AstNode[] | undefined { return this.childList("parameters"); }
get parameters(): readonly AstNode[] | undefined { return this.childList("parameters"); }
get postfixToken(): AstNode | undefined { return this.childNode("postfixToken"); }
get properties(): AstNode[] | undefined { return this.childList("properties"); }
get properties(): readonly AstNode[] | undefined { return this.childList("properties"); }
get propertyName(): AstNode | undefined { return this.childNode("propertyName"); }
get qualifier(): AstNode | undefined { return this.childNode("qualifier"); }
get questionDotToken(): AstNode | undefined { return this.childNode("questionDotToken"); }
@@ -307,24 +310,24 @@ export class AstNode {
get readonlyToken(): AstNode | undefined { return this.childNode("readonlyToken"); }
get right(): AstNode | undefined { return this.childNode("right"); }
get statement(): AstNode | undefined { return this.childNode("statement"); }
get statements(): AstNode[] | undefined { return this.childList("statements"); }
get statements(): readonly AstNode[] | undefined { return this.childList("statements"); }
get tag(): AstNode | undefined { return this.childNode("tag"); }
get tagName(): AstNode | undefined { return this.childNode("tagName"); }
get tags(): AstNode[] | undefined { return this.childList("tags"); }
get tags(): readonly AstNode[] | undefined { return this.childList("tags"); }
get template(): AstNode | undefined { return this.childNode("template"); }
get templateSpans(): AstNode[] | undefined { return this.childList("templateSpans"); }
get templateSpans(): readonly AstNode[] | undefined { return this.childList("templateSpans"); }
get thenStatement(): AstNode | undefined { return this.childNode("thenStatement"); }
get thisArg(): AstNode | undefined { return this.childNode("thisArg"); }
get trueType(): AstNode | undefined { return this.childNode("trueType"); }
get tryBlock(): AstNode | undefined { return this.childNode("tryBlock"); }
get tupleNameSource(): AstNode | undefined { return this.childNode("tupleNameSource"); }
get type(): AstNode | undefined { return this.childNode("type"); }
get typeArguments(): AstNode[] | undefined { return this.childList("typeArguments"); }
get typeArguments(): readonly AstNode[] | undefined { return this.childList("typeArguments"); }
get typeExpression(): AstNode | undefined { return this.childNode("typeExpression"); }
get typeName(): AstNode | undefined { return this.childNode("typeName"); }
get typeParameter(): AstNode | undefined { return this.childNode("typeParameter"); }
get typeParameters(): AstNode[] | undefined { return this.childList("typeParameters"); }
get types(): AstNode[] | undefined { return this.childList("types"); }
get typeParameters(): readonly AstNode[] | undefined { return this.childList("typeParameters"); }
get types(): readonly AstNode[] | undefined { return this.childList("types"); }
get value(): AstNode | undefined { return this.childNode("value"); }
get variableDeclaration(): AstNode | undefined { return this.childNode("variableDeclaration"); }
get whenFalse(): AstNode | undefined { return this.childNode("whenFalse"); }
@@ -0,0 +1,156 @@
// Generated from typescript@7.0.2 by scripts/generate-ts7-ast-schema.mjs.
// TypeScript is Copyright Microsoft Corporation, licensed under Apache-2.0.
// Regenerate when changing the TypeScript pin; do not edit by hand.
import { SyntaxKind } from "./enums.js";
export function tokenToString(kind: SyntaxKind): string | undefined {
switch (kind) {
case 18: return "{";
case 19: return "}";
case 20: return "(";
case 21: return ")";
case 22: return "[";
case 23: return "]";
case 24: return ".";
case 25: return "...";
case 26: return ";";
case 27: return ",";
case 28: return "?.";
case 29: return "<";
case 30: return "</";
case 31: return ">";
case 32: return "<=";
case 33: return ">=";
case 34: return "==";
case 35: return "!=";
case 36: return "===";
case 37: return "!==";
case 38: return "=>";
case 39: return "+";
case 40: return "-";
case 41: return "*";
case 42: return "**";
case 43: return "/";
case 44: return "%";
case 45: return "++";
case 46: return "--";
case 47: return "<<";
case 48: return ">>";
case 49: return ">>>";
case 50: return "&";
case 51: return "|";
case 52: return "^";
case 53: return "!";
case 54: return "~";
case 55: return "&&";
case 56: return "||";
case 57: return "?";
case 58: return ":";
case 59: return "@";
case 60: return "??";
case 61: return "`";
case 62: return "#";
case 63: return "=";
case 64: return "+=";
case 65: return "-=";
case 66: return "*=";
case 67: return "**=";
case 68: return "/=";
case 69: return "%=";
case 70: return "<<=";
case 71: return ">>=";
case 72: return ">>>=";
case 73: return "&=";
case 74: return "|=";
case 75: return "||=";
case 76: return "&&=";
case 77: return "??=";
case 78: return "^=";
case 82: return "break";
case 83: return "case";
case 84: return "catch";
case 85: return "class";
case 86: return "const";
case 87: return "continue";
case 88: return "debugger";
case 89: return "default";
case 90: return "delete";
case 91: return "do";
case 92: return "else";
case 93: return "enum";
case 94: return "export";
case 95: return "extends";
case 96: return "false";
case 97: return "finally";
case 98: return "for";
case 99: return "function";
case 100: return "if";
case 101: return "import";
case 102: return "in";
case 103: return "instanceof";
case 104: return "new";
case 105: return "null";
case 106: return "return";
case 107: return "super";
case 108: return "switch";
case 109: return "this";
case 110: return "throw";
case 111: return "true";
case 112: return "try";
case 113: return "typeof";
case 114: return "var";
case 115: return "void";
case 116: return "while";
case 117: return "with";
case 118: return "implements";
case 119: return "interface";
case 120: return "let";
case 121: return "package";
case 122: return "private";
case 123: return "protected";
case 124: return "public";
case 125: return "static";
case 126: return "yield";
case 127: return "abstract";
case 128: return "accessor";
case 129: return "as";
case 130: return "asserts";
case 131: return "assert";
case 132: return "any";
case 133: return "async";
case 134: return "await";
case 135: return "boolean";
case 136: return "constructor";
case 137: return "declare";
case 138: return "get";
case 140: return "infer";
case 141: return "intrinsic";
case 142: return "is";
case 143: return "keyof";
case 144: return "module";
case 145: return "namespace";
case 146: return "never";
case 147: return "out";
case 148: return "readonly";
case 149: return "require";
case 150: return "number";
case 151: return "object";
case 152: return "satisfies";
case 153: return "set";
case 154: return "string";
case 155: return "symbol";
case 156: return "type";
case 157: return "undefined";
case 158: return "unique";
case 159: return "unknown";
case 160: return "using";
case 161: return "from";
case 162: return "global";
case 163: return "bigint";
case 164: return "override";
case 165: return "of";
case 166: return "defer";
default: return undefined;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
import type { AstNode } from "./ast-node.js";
import { SyntaxKind } from "./enums.js";
import type { EndOfFile, EntityName, Identifier, PropertyAccessExpression, Statement, ThisExpression } from "./ast-types.generated.js";
export * from "./ast-types.generated.js";
export type Node = AstNode;
export type NodeArray<T extends Node> = readonly T[];
export type Path = string;
export type __String = string;
export interface TextRange { pos: number; end: number; }
export interface ReadonlyTextRange { readonly pos: number; readonly end: number; }
export interface LineAndCharacter { readonly line: number; readonly character: number; }
export interface FileReference extends ReadonlyTextRange { readonly fileName: string; readonly resolutionMode: number; readonly preserve: boolean; }
/** A source file is the validated root of an AstFile, with the same identity
* and methods as every other node. Lists carry native array storage only. */
export interface SourceFile extends AstNode {
readonly kind: SyntaxKind.SourceFile;
readonly text: string;
readonly statements: NodeArray<Statement>;
readonly endOfFileToken: EndOfFile;
}
export interface PropertyAccessEntityNameExpression extends PropertyAccessExpression {
readonly expression: EntityNameExpression;
readonly name: Identifier;
}
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
readonly expression: Identifier | ThisExpression | JsxTagNamePropertyAccess;
}
+25 -23
View File
@@ -1,6 +1,7 @@
/* The AST layer of the census under the 5.9.3 names, over 7.0.2's client
* AST (typescript/unstable/ast — fully client-side, no IPC anywhere in this
* module).
/* Frontend AST helpers over the owned native TypeScript 7 node model.
* Kind-specific interfaces refine its concrete getters and preserve object
* identity. Guards and token spellings come from the pinned SDK generator;
* this module does not import an SDK runtime implementation.
*
* Guards: 82 of the census's 90 is* guards re-export same-name; seven were
* renamed in 7 (isParameter -> isParameterDeclaration and friends) and alias
@@ -29,7 +30,7 @@ import type {
SignatureDeclaration,
SourceFile,
StringLiteralLikeNode,
} from "typescript/unstable/ast";
} from "./ast-types.js";
import {
isAccessorDeclaration,
isBindingElement,
@@ -43,38 +44,39 @@ import {
isVariableDeclaration,
isVariableDeclarationList,
isVariableStatement,
} from "typescript/unstable/ast/is";
import type { Diagnostic } from "typescript/unstable/sync";
} from "./ast-guards.generated.js";
import type { Ts7DiagnosticData as Diagnostic } from "./session-schema.generated.js";
import { ModifierFlags, SyntaxKind } from "./enums.js";
/* ---- everything 7 kept under the same name ---- */
// The full client AST: every census type-position name (Expression,
// CallExpression, Statement, SourceFile, NodeArray, ...), all same-name
// guards, tokenToString, escape helpers, the scanner, and the visitor.
export * from "typescript/unstable/ast";
// The frontend's node types, guards and token spellings share the concrete
// AstNode layout. Parser and transform factories remain outside this client.
export * from "./ast-types.js";
export * from "./ast-guards.generated.js";
export { tokenToString } from "./ast-tokens.generated.js";
/* ---- the seven renamed guards, under their 5.9.3 names ---- */
export const isParameter: (node: Node) => node is ParameterDeclaration = isParameterDeclaration;
export const isPropertySignature: (node: Node) => node is PropertySignatureDeclaration =
export const isParameter: (node: Node | undefined) => node is ParameterDeclaration = isParameterDeclaration;
export const isPropertySignature: (node: Node | undefined) => node is PropertySignatureDeclaration =
isPropertySignatureDeclaration;
export const isStringLiteralLike: (node: Node) => node is StringLiteralLikeNode = isStringLiteralLikeNode;
export const isGetAccessor: (node: Node) => node is GetAccessorDeclaration = isGetAccessorDeclaration;
export const isSetAccessor: (node: Node) => node is SetAccessorDeclaration = isSetAccessorDeclaration;
export const isAccessor: (node: Node) => node is AccessorDeclaration = isAccessorDeclaration;
export const isStringLiteralLike: (node: Node | undefined) => node is StringLiteralLikeNode = isStringLiteralLikeNode;
export const isGetAccessor: (node: Node | undefined) => node is GetAccessorDeclaration = isGetAccessorDeclaration;
export const isSetAccessor: (node: Node | undefined) => node is SetAccessorDeclaration = isSetAccessorDeclaration;
export const isAccessor: (node: Node | undefined) => node is AccessorDeclaration = isAccessorDeclaration;
/* ---- renamed types, under their 5.9.3 names ---- */
export type StringLiteralLike = StringLiteralLikeNode;
export type MethodSignature = import("typescript/unstable/ast").MethodSignatureDeclaration;
export type MethodSignature = import("./ast-types.js").MethodSignatureDeclaration;
/** 5.9.3's isFunctionLike: true for every SignatureDeclaration kind. 7's
* same-set guard is isSignatureDeclaration (its isFunctionLikeDeclaration is
* the narrower declarations-only check); JSDocSignature joins because 5.9.3
* counts it. 5.9.3 also accepted JSDocFunctionType — a node kind tsgo no
* longer produces, so no walk over a 7 AST can present one. */
export function isFunctionLike(node: Node): node is SignatureDeclaration {
export function isFunctionLike(node: Node | undefined): node is SignatureDeclaration {
return isSignatureDeclaration(node) || isJSDocSignature(node);
}
@@ -162,7 +164,7 @@ const MODIFIER_HOSTS: ReadonlySet<number> = new Set<number>([
SyntaxKind.ExportDeclaration,
]);
export function canHaveModifiers(node: Node): node is Node & { modifiers?: NodeArray<Node> } {
export function canHaveModifiers(node: Node): boolean {
return MODIFIER_HOSTS.has(node.kind);
}
@@ -212,17 +214,17 @@ function modifierFlagsOfNode(node: Node): number {
* getCombinedModifierFlags, mirrored exactly: a binding element climbs to
* the declaration that hosts its outermost pattern; a variable declaration
* merges its list's flags, the list its statement's. */
function walkUpBindingElementsAndPatterns(bindingElement: Node): Node {
function walkUpBindingElementsAndPatterns(bindingElement: Node): Node | undefined {
let node = bindingElement.parent; // the binding pattern
while (isBindingElement(node.parent)) {
while (node !== undefined && isBindingElement(node.parent)) {
node = node.parent.parent;
}
return node.parent; // VariableDeclaration or ParameterDeclaration
return node?.parent; // VariableDeclaration or ParameterDeclaration
}
function getCombinedFlags(node: Node, getFlags: (n: Node) => number): number {
let n: Node | undefined = isBindingElement(node) ? walkUpBindingElementsAndPatterns(node) : node;
let flags = getFlags(n);
let flags = n === undefined ? 0 : getFlags(n);
if (isVariableDeclaration(n)) n = n.parent;
if (n !== undefined && isVariableDeclarationList(n)) {
flags |= getFlags(n);
@@ -35,18 +35,18 @@ import { InternalCompilerError } from "../../errors.js";
* repeats an IPC request. All paths are verified against the raw checker
* and against 5.9.3 by the adapter's suites. */
import type { Node, SourceFile } from "typescript/unstable/ast";
import type { Node, SourceFile } from "./ast-types.js";
import type {
Checker,
IndexInfo,
InterfaceType,
Project,
Signature,
Symbol as Ts7Symbol,
Type,
TypePredicate,
TypeReference,
} from "typescript/unstable/sync";
} from "./semantic-types.js";
import type { SemanticChecker as Checker } from "./semantic-checker.js";
import type { SemanticProject as Project } from "./semantic-model.js";
import { walkPreorder } from "./ast.js";
import { SignatureKind, SyntaxKind, TypeFlags } from "./enums.js";
@@ -832,7 +832,7 @@ export class CheckerFacade {
* programs see the es2025 lib's Promise (the ambient world forces it). */
private promiseArgumentOf(type: Type): Type | null {
if (!type.isTypeReference()) return null;
const name = type.getTarget().getSymbol()?.name;
const name = type.getTarget()?.getSymbol()?.name;
if (name !== "Promise" && name !== "PromiseLike") return null;
const args = this.getTypeArguments(type);
return args[0] ?? null;
@@ -1294,3 +1294,24 @@ export function moduleDetectionKindName(value: number): string | undefined {
default: return undefined;
}
}
export enum OuterExpressionKinds {
Parentheses = 1,
TypeAssertions = 2,
NonNullAssertions = 4,
PartiallyEmittedExpressions = 8,
ExpressionsWithTypeArguments = 16,
Satisfies = 32,
ExcludeJSDocTypeAssertion = 64,
Assignments = 128,
Comma = 256,
Assertions = 38,
All = 63,
AllExceptAssertionsOrExpressionsWithTypeArguments = 9,
ExpressionTypePassthrough = 385
}
export enum LanguageVariant {
Standard = 0,
JSX = 1
}
@@ -22,10 +22,9 @@ import { Ts7Api } from "./rpc-api.js";
import type {
CompilerOptions,
Diagnostic,
Project,
Snapshot,
} from "typescript/unstable/sync";
import type { SourceFile } from "typescript/unstable/ast";
} from "./semantic-types.js";
import type { SourceFile } from "./ast-types.js";
import type { Ts7SessionProject as Project, Ts7SessionSnapshot as Snapshot } from "./session.js";
import { CheckerFacade } from "./checker.js";
import { moduleDetectionKindName, moduleKindName, moduleResolutionKindName, scriptTargetName, ScriptTarget } from "./enums.js";
import { tsgoPath } from "../dts-paths.js";
@@ -236,7 +235,7 @@ export class Ts7Program {
}
getTypeChecker(): CheckerFacade {
this.checkerFacade ??= new CheckerFacade(this.project.checker, { project: this.project });
this.checkerFacade ??= new CheckerFacade(this.project.checker, { project: this.project.checker.project });
return this.checkerFacade;
}
+5 -14
View File
@@ -1,12 +1,11 @@
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import type { Snapshot, TimingInfo } from "typescript/unstable/sync";
import type { ConfigResponse } from "typescript/unstable/proto";
import { AstNode } from "./ast-node.js";
import type { Ts7ConfigData as ConfigResponse } from "./session-schema.generated.js";
import type { Ts7TimingInfo as TimingInfo } from "./session-timing.js";
import { Ts7RpcClient } from "./rpc-client.js";
import { registerTs7FileSystem, TS7_FILE_SYSTEM_CALLBACKS, type Ts7FileSystem } from "./rpc-filesystem.js";
import { spawnTs7Wire } from "./rpc-process.js";
import { Ts7Session, type Ts7Update } from "./session.js";
import { Ts7Session, type Ts7SessionSnapshot as Snapshot, type Ts7Update } from "./session.js";
const require = createRequire(import.meta.url);
const packageRoot = dirname(require.resolve("typescript/package.json"));
@@ -16,12 +15,6 @@ const { default: getExePath } = require(join(packageRoot, "lib/getExePath.js"))
* boundary. The session itself uses no SDK implementation or Node process. */
export function ts7Executable(): string { return getExePath(); }
function listMetadata(nodes: AstNode[], pos: number, end: number): void {
// Legacy frontend NodeArray interfaces attach properties to JS arrays.
// Native clients consume AstNode arrays directly and omit this adapter.
Object.assign(nodes, { pos, end, transformFlags: 0 });
}
export class Ts7Api {
private readonly session: Ts7Session;
@@ -31,14 +24,12 @@ export class Ts7Api {
if (timing) args.push("--timing");
const rpc = new Ts7RpcClient(spawnTs7Wire(ts7Executable(), args));
registerTs7FileSystem(rpc, options.fs);
this.session = new Ts7Session(rpc, timing, listMetadata);
this.session = new Ts7Session(rpc, timing);
}
parseConfigFile(file: string): ConfigResponse { return this.session.parseConfigFile(file); }
updateSnapshot(params: Ts7Update): Snapshot {
// Existing frontend discriminated interfaces remain a type-only bridge;
// every object behind them now belongs to the statically compiled client.
return this.session.updateSnapshot(params) as unknown as Snapshot;
return this.session.updateSnapshot(params);
}
getTimingInfo(): TimingInfo { return this.session.getTimingInfo(); }
close(): void { this.session.close(); }
@@ -305,6 +305,11 @@ export interface SemanticStringLiteralType extends SemanticLiteralType { readonl
export interface SemanticNumberLiteralType extends SemanticLiteralType { readonly value: number; }
export interface SemanticBigIntLiteralType extends SemanticLiteralType { readonly value: bigint; }
export interface SemanticBooleanLiteralType extends SemanticLiteralType { readonly value: boolean; }
export interface SemanticTupleType extends SemanticObjectType {
readonly elementFlags: number[];
readonly fixedLength: number;
readonly readonly: boolean;
}
/** Kind-specific accessors share one nominal layout. */
export class SemanticType {
@@ -0,0 +1,19 @@
/** Frontend names over the concrete native client. No SDK object or
* structural cast participates in the production checker boundary. */
export type {
SemanticType as Type,
SemanticType as InterfaceType,
SemanticType as TypeReference,
SemanticType as UnionOrIntersectionType,
SemanticType as UnionType,
SemanticTupleType as TupleType,
SemanticTupleType as TupleTypeReference,
SemanticStringLiteralType as StringLiteralType,
SemanticNumberLiteralType as NumberLiteralType,
SemanticBooleanLiteralType as BooleanLiteralType,
SemanticObjectType as ObjectType,
SemanticSignature as Signature,
SemanticSymbol as Symbol,
} from "./semantic-model.js";
export type { SemanticIndexInfo as IndexInfo, SemanticTypePredicate as TypePredicate } from "./semantic-checker.js";
export type { Ts7CompilerOptionsData as CompilerOptions, Ts7DiagnosticData as Diagnostic } from "./session-schema.generated.js";
@@ -1,14 +1,14 @@
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { expect, test } from "vitest";
import type { AstNode } from "./ast-node.js";
import type { SourceFile } from "./ast-types.js";
import { Ts7SourceCache, type Ts7SnapshotChanges } from "./session-cache.js";
const require = createRequire(import.meta.url);
const { SourceFileCache } = require(join(dirname(require.resolve("typescript/package.json")), "dist/api/sourceFileCache.js")) as {
SourceFileCache: new () => {
getRetained(path: string, snapshot: number, project: string): AstNode | undefined;
set(path: string, file: AstNode, options: string, hash: string, snapshot: number, project: string): AstNode;
getRetained(path: string, snapshot: number, project: string): SourceFile | undefined;
set(path: string, file: SourceFile, options: string, hash: string, snapshot: number, project: string): SourceFile;
retainForSnapshot(snapshot: number, previous: number, changes?: Ts7SnapshotChanges): void;
releaseSnapshot(snapshot: number): void;
clear(): void;
@@ -18,8 +18,8 @@ const { SourceFileCache } = require(join(dirname(require.resolve("typescript/pac
// Cache tests need identity and the two wire keys, not a parser response.
// The live native session test supplies real immutable AstFile instances.
function source(hash: string, options = "0"): AstNode {
return { file: { wire: { contentHash: hash, parseOptionsKey: options } } } as AstNode;
function source(hash: string, options = "0"): SourceFile {
return { file: { wire: { contentHash: hash, parseOptionsKey: options } } } as SourceFile;
}
test("the cache separates content, parser options, paths and retention owners", () => {
@@ -1,9 +1,9 @@
import { AstNode } from "./ast-node.js";
import type { SourceFile } from "./ast-types.js";
import type { Ts7SnapshotChangeData as Ts7SnapshotChanges } from "./session-schema.generated.js";
export type { Ts7SnapshotChangeData as Ts7SnapshotChanges } from "./session-schema.generated.js";
interface CachedSource {
file: AstNode;
file: SourceFile;
parseOptions: string;
hash: string;
references: Set<string>;
@@ -21,12 +21,12 @@ export class Ts7SourceCache {
private readonly files = new Map<string, CachedSource[]>();
private readonly paths = new Map<number, SnapshotPaths>();
get(path: string, snapshot: number, project: string): AstNode | undefined {
get(path: string, snapshot: number, project: string): SourceFile | undefined {
const ref = reference(snapshot, project);
return this.files.get(path)?.find((entry) => entry.references.has(ref))?.file;
}
set(path: string, file: AstNode, snapshot: number, project: string): AstNode {
set(path: string, file: SourceFile, snapshot: number, project: string): SourceFile {
let entries = this.files.get(path);
if (entries === undefined) {
entries = [];
@@ -1,3 +1,4 @@
import type { SourceFile } from "./ast-types.js";
import { AstFile, AstNode } from "./ast-node.js";
import { Ts7RpcClient } from "./rpc-client.js";
import { SemanticChecker } from "./semantic-checker.js";
@@ -205,7 +206,7 @@ export class Ts7SessionProgram {
return { snapshot: this.snapshot.id, project: this.project.id, ...(file === undefined ? {} : { file }) };
}
getCompilerOptions(): Ts7CompilerOptionsData { this.ensureActive(); return this.project.compilerOptions; }
getSourceFile(file: SemanticDocument): AstNode | undefined {
getSourceFile(file: SemanticDocument): SourceFile | undefined {
this.ensureActive();
const path = this.snapshot.paths.canonical(ts7DocumentFile(file));
const retained = this.session.cache.get(path, this.snapshot.id, this.project.id);
@@ -946,7 +946,7 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
* family (every SITE using the value already carries its own fence). */
function classExprNeverRegisters(decl: ts.ClassLikeDeclaration): boolean {
if (!ts.isClassExpression(decl)) return false;
for (let p: ts.Node = decl.parent; !ts.isSourceFile(p); p = p.parent) {
for (let p: ts.Node | undefined = decl.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
if (ts.isFunctionLike(p) || ts.isClassStaticBlockDeclaration(p)) return true;
}
return false;
@@ -3036,7 +3036,7 @@ function mapClassView(type: ts.Type, ctx: TypeMapperCtx, seen = new Set<ts.Type>
(decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()),
)) return undefined;
const target = type.isTypeReference() ? type.getTarget() : type;
if (!target.isClassOrInterface()) return undefined;
if (target === undefined || !target.isClassOrInterface()) return undefined;
bases = checker.getBaseTypes(target);
if (type.isTypeReference() && target.isTypeReference()) {
const params = checker.getTypeArguments(target);
@@ -3116,7 +3116,7 @@ function mapCollectionView(type: ts.Type, ctx: TypeMapperCtx, seen = new Set<ts.
(decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()),
)) return null;
const target = type.getTarget();
if (!target.isClassOrInterface() || seen.has(target) || seen.size >= MAP_TYPE_MAX_DEPTH) return null;
if (target === undefined || !target.isClassOrInterface() || seen.has(target) || seen.size >= MAP_TYPE_MAX_DEPTH) return null;
const bases = checker.getBaseTypes(target);
if (bases.length !== 1) return null;
if (checker.getCallSignatures(type).length || checker.getConstructSignatures(type).length ||
@@ -3234,8 +3234,8 @@ function mapNarrowedTypeParam(type: ts.Type, ctx: TypeMapperCtx): IrType | null
function mapBoundIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
const { checker, resolveTypeParamTs } = ctx;
if (!resolveTypeParamTs || !type.isIndexedAccessType()) return null;
const resolveSide = (t: ts.Type): ts.Type | null =>
t.flags & ts.TypeFlags.TypeParameter ? resolveTypeParamTs(t) : t;
const resolveSide = (t: ts.Type | undefined): ts.Type | null =>
t === undefined ? null : t.flags & ts.TypeFlags.TypeParameter ? resolveTypeParamTs(t) : t;
const objT = resolveSide(type.getObjectType());
const idxT = resolveSide(type.getIndexType());
if (!objT || !idxT) return null;
@@ -3281,7 +3281,7 @@ export function isConstAssertionTypeNode(t: ts.TypeNode): boolean {
* structure between (object/array literals, property assignments,
* parens) — the positions `as const` makes deeply readonly. */
export function underConstAssertion(n: ts.Node): boolean {
for (let cur: ts.Node = n; cur !== undefined && !ts.isSourceFile(cur); cur = cur.parent) {
for (let cur: ts.Node | undefined = n; cur !== undefined && !ts.isSourceFile(cur); cur = cur.parent) {
if (ts.isAsExpression(cur)) return isConstAssertionTypeNode(cur.type);
if (
!ts.isPropertyAssignment(cur) &&
@@ -3349,12 +3349,13 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu
const { resolveTypeParam, shapes } = ctx;
if (!resolveTypeParam || !type.isIndexedAccessType()) return null;
const obj = type.getObjectType();
if (!(obj.flags & ts.TypeFlags.TypeParameter)) return null;
if (obj === undefined || !(obj.flags & ts.TypeFlags.TypeParameter)) return null;
const bound = resolveTypeParam(obj);
if (!bound || bound.kind !== "record") return null;
const shape = shapes.get(bound.shapeId);
if (!shape || shape.tuple) return null;
const idx = type.getIndexType();
if (idx === undefined) return null;
let covered: IrType[] | null = null;
if (idx.isStringLiteralType()) {
const f = shape.fields.find((x) => x.name === idx.value);
+2 -2
View File
@@ -17,8 +17,8 @@
import { afterAll, expect, test } from "vitest";
import { fileURLToPath } from "node:url";
import type { Node } from "typescript/unstable/ast";
import type { Signature, Symbol as Ts7Symbol, Type, TypeReference } from "typescript/unstable/sync";
import type { Node } from "../../src/frontend/ts7/ast-types.js";
import type { Signature, Symbol as Ts7Symbol, Type, TypeReference } from "../../src/frontend/ts7/semantic-types.js";
import { CheckerFacade } from "../../src/frontend/ts7/checker.js";
import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../../src/frontend/program.js";
import { ad, options7 } from "./harness.js";
+8 -7
View File
@@ -7,8 +7,9 @@ import { afterAll, expect, test } from "vitest";
import { lowerToIr } from "../../src/frontend/lowering/lowerer.js";
import { clearWorkspacePackages, registerWorkspacePackage } from "../../src/frontend/workspace-registry.js";
import { CheckerFacade } from "../../src/frontend/ts7/checker.js";
import type { Node } from "typescript/unstable/ast";
import type { Checker, Type } from "typescript/unstable/sync";
import type { SemanticChecker as Checker } from "../../src/frontend/ts7/semantic-checker.js";
import type { Node } from "../../src/frontend/ts7/ast-types.js";
import type { Type } from "../../src/frontend/ts7/semantic-types.js";
import { ad, buildTwoWorlds } from "./harness.js";
import type { TwoWorlds } from "./harness.js";
import { RICH_TS } from "./fixtures.js";
@@ -489,7 +490,7 @@ console.log("ok");
}, host);
worlds.push(w);
const { proxy, calls } = countingChecker(w.p7.project.checker);
const facade = new CheckerFacade(proxy, { project: w.p7.project });
const facade = new CheckerFacade(proxy, { project: w.p7.project.checker.project });
(w.p7 as unknown as { checkerFacade: CheckerFacade | null }).checkerFacade = facade;
const sf = w.p7.getSourceFile(w.files[0]!)!;
const cls = sf.statements.find(ad.isClassDeclaration)!;
@@ -519,7 +520,7 @@ test("signature collection batches exact types of deferred function defaults", (
const w = buildTwoWorlds({ "defaults.ts": `${defaults}\nconsole.log("ok");\n` }, host);
worlds.push(w);
const { proxy, calls } = countingChecker(w.p7.project.checker);
const facade = new CheckerFacade(proxy, { project: w.p7.project });
const facade = new CheckerFacade(proxy, { project: w.p7.project.checker.project });
(w.p7 as unknown as { checkerFacade: CheckerFacade | null }).checkerFacade = facade;
const sf = w.p7.getSourceFile(w.files[0]!)!;
const initializers = sf.statements
@@ -548,7 +549,7 @@ test("class-shape collection batches deferred method default types", () => {
}, host);
worlds.push(w);
const { proxy, calls } = countingChecker(w.p7.project.checker);
const facade = new CheckerFacade(proxy, { project: w.p7.project });
const facade = new CheckerFacade(proxy, { project: w.p7.project.checker.project });
(w.p7 as unknown as { checkerFacade: CheckerFacade | null }).checkerFacade = facade;
const sf = w.p7.getSourceFile(w.files[0]!)!;
const cls = sf.statements.find(ad.isClassDeclaration)!;
@@ -579,7 +580,7 @@ console.log(pick(42));
}, host);
worlds.push(w);
const { proxy, calls } = countingChecker(w.p7.project.checker);
const facade = new CheckerFacade(proxy, { project: w.p7.project });
const facade = new CheckerFacade(proxy, { project: w.p7.project.checker.project });
(w.p7 as unknown as { checkerFacade: CheckerFacade | null }).checkerFacade = facade;
const sf = w.p7.getSourceFile(w.files[0]!)!;
const fn = sf.statements.find(ad.isFunctionDeclaration)!;
@@ -620,7 +621,7 @@ console.log(reached(1));
}, host);
worlds.push(w);
const { proxy, calls } = countingChecker(w.p7.project.checker);
const facade = new CheckerFacade(proxy, { project: w.p7.project });
const facade = new CheckerFacade(proxy, { project: w.p7.project.checker.project });
// Ts7Program owns one shared facade; install the counting instance so
// lowering and this assertion observe the same memo/batch traffic.
(w.p7 as unknown as { checkerFacade: CheckerFacade | null }).checkerFacade = facade;
@@ -6,7 +6,8 @@ import { checkPreflight, loadProgram } from "../../src/frontend/program.js";
import { tsgoPath } from "../../src/frontend/dts-paths.js";
import * as ts from "../../src/frontend/ts7/adapter.js";
import { CheckerFacade } from "../../src/frontend/ts7/checker.js";
import type { Checker, Project } from "typescript/unstable/sync";
import type { SemanticChecker as Checker } from "../../src/frontend/ts7/semantic-checker.js";
import type { Ts7SessionProject as Project } from "../../src/frontend/ts7/session.js";
describe("tsgo virtual filesystem paths", () => {
test("matches slash-normalized Windows callback paths", () => {
@@ -124,7 +125,7 @@ console.log(required.value);
};
},
}) as Checker;
program.checkerFacade = new CheckerFacade(proxy, { project: program.project });
program.checkerFacade = new CheckerFacade(proxy, { project: program.project.checker.project });
expect(checkPreflight(load).map((diag) => diag.code)).toContain("SC1013");
expect(calls.some(([arg]) => Array.isArray(arg) && arg.length > 24)).toBe(true);
+12 -7
View File
@@ -60,7 +60,7 @@ test("production RPC bridge preserves pinned SDK parser/checker behavior", () =>
expect(nativeClient.parseConfigFile(config)).toEqual(sdk.parseConfigFile(config));
const expected = sdk.updateSnapshot({ openProjects: [config] });
const actual = nativeClient.updateSnapshot({ openProjects: [config] });
const facts = programFacts(actual, config, path);
const facts = programFacts(actual as unknown as Snapshot, config, path);
expect(facts).toEqual(programFacts(expected, config, path));
expect(facts.type).toBe("42");
expect(facts.symbol).toBe("answer");
@@ -80,14 +80,19 @@ test("production RPC bridge preserves pinned SDK parser/checker behavior", () =>
expect(symbol!.declarations[0]!.resolve()).toBe(symbol!.declarations[0]!.resolve());
const declaration = symbol!.declarations[0]!.resolve()!;
expect(declaration).toBeInstanceOf(AstNode);
const variable = source.statements[0]! as import("typescript/unstable/ast").VariableStatement;
const variable = source.statements[0]! as import("../../src/frontend/ts7/ast-types.js").VariableStatement;
expect(variable.declarationList.declarations[0]).toBe(declaration);
expect(project.checker.getSymbolAtLocation(variable.declarationList.declarations[0]!.name)).toBe(symbol);
expect(project.checker.getTypeAtLocation(variable.declarationList.declarations[0]!.name)).toBe(first);
const expectedSource = expected.getProject(config)!.program.getSourceFile(path)!;
expect(source.statements.pos).toBe(expectedSource.statements.pos);
expect(source.statements.end).toBe(expectedSource.statements.end);
expect(source.statements.transformFlags).toBe(expectedSource.statements.transformFlags);
expect(source.statements.map((node) => [node.kind, node.pos, node.end])).toEqual(
expectedSource.statements.map((node) => [node.kind, node.pos, node.end]),
);
// Native arrays retain element identity; SDK-only properties no longer
// need a JavaScript Object.assign adapter on production source lists.
expect(Array.isArray(source.statements)).toBe(true);
expect(Object.hasOwn(source.statements, "pos")).toBe(false);
expect(Object.hasOwn(source.statements, "transformFlags")).toBe(false);
expect(source.referencedFiles).toBe(source.referencedFiles);
expect(source.imports).toBe(source.imports);
} finally {
@@ -164,12 +169,12 @@ test("native semantic registries preserve project scope and snapshot lifetimes",
}
try {
const first = api.updateSnapshot({ openProjects: [firstConfig, secondConfig] });
const actual = facts(first);
const actual = facts(first as unknown as Snapshot);
expect(actual.shape).toEqual(facts(sdk.updateSnapshot({ openProjects: [firstConfig, secondConfig] })).shape);
expect(actual.signatureA).toBeInstanceOf(SemanticSignature);
expect(actual.typeA).toBeInstanceOf(SemanticType);
const second = api.updateSnapshot({ openProjects: [] });
const next = facts(second);
const next = facts(second as unknown as Snapshot);
expect(next.fileA).toBe(actual.fileA);
expect(next.typeA).not.toBe(actual.typeA);
expect(next.symbolA).not.toBe(actual.symbolA);
+4 -2
View File
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { generateAstViews } from "./generate-ts7-ast-views.mjs";
// The binary AST is an unstable TypeScript protocol. Generate its constants
// from the installed, pinned package; never translate TypeScript 5 numbers.
@@ -22,7 +23,7 @@ const { ModifierFlags } = require(join(packageRoot, "dist/enums/modifierFlags.js
const enumNames = [
"InternalSymbolName", "ModifierFlags", "NodeFlags", "ScriptKind", "ScriptTarget", "SyntaxKind", "TokenFlags",
"DiagnosticCategory", "ElementFlags", "ModuleKind", "NodeBuilderFlags", "ObjectFlags", "SignatureFlags",
"SignatureKind", "SymbolFlags", "TypeFlags", "TypePredicateKind", "ModuleResolutionKind", "ModuleDetectionKind",
"SignatureKind", "SymbolFlags", "TypeFlags", "TypePredicateKind", "ModuleResolutionKind", "ModuleDetectionKind", "OuterExpressionKinds", "LanguageVariant",
];
const enumLines = [
"/* eslint-disable @typescript-eslint/no-duplicate-enum-values -- TypeScript aliases are part of the protocol. */",
@@ -166,7 +167,7 @@ const names = [...new Set(Object.values(protocol.childProperties).flat())].sort(
const getters = names.map((name) => {
const match = declarations.match(new RegExp(`get ${name}\\(\\): ([^;]+);`));
if (!match) throw new Error(`Missing AST child declaration: ${name}`);
const type = match[1].replaceAll("RemoteNodeList", "AstNode[]").replaceAll("RemoteNode", "AstNode");
const type = match[1].replaceAll("RemoteNodeList", "readonly AstNode[]").replaceAll("RemoteNode", "AstNode");
const method = type.includes("AstNode |") && type.includes("AstNode[]") ? "child" : type.includes("AstNode[]") ? "childList" : "childNode";
return ` get ${name}(): ${type} { return this.${method}(${JSON.stringify(name)}); }`;
});
@@ -184,3 +185,4 @@ if (process.argv.includes("--check")) {
} else {
writeFileSync(nodeTarget, after);
}
generateAstViews(root, packageRoot, version, process.argv.includes("--check"));
+153
View File
@@ -0,0 +1,153 @@
import { createRequire } from "node:module";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
/** Generate class views rather than structural copies of native AST nodes.
* The build-time parser reads declarations only; no parser joins the client. */
export function generateAstViews(root, packageRoot, version, check) {
const require = createRequire(join(root, "package.json"));
const ts = require("typescript");
const read = (name) => readFileSync(join(packageRoot, "dist/ast", name), "utf8");
const parse = (name, source = read(name)) => ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true);
const generated = parse("ast.generated.d.ts");
const header = [
`// Generated from typescript@${version} by scripts/generate-ts7-ast-schema.mjs.`,
"// TypeScript is Copyright Microsoft Corporation, licensed under Apache-2.0.",
"// Regenerate when changing the TypeScript pin; do not edit by hand.",
];
const write = (name, lines) => {
const path = join(root, "packages/compiler/src/frontend/ts7", name);
const output = lines.join("\n") + "\n";
if (check) {
if (readFileSync(path, "utf8") !== output) throw new Error(`${name} is stale; run node scripts/generate-ts7-ast-schema.mjs`);
} else writeFileSync(path, output);
};
const interfaces = new Map(generated.statements.filter(ts.isInterfaceDeclaration).map((node) => [node.name.text, node]));
const replace = (text, bindings) => text.replace(/\b[A-Za-z_$][\w$]*\b/g, (word) => bindings.get(word) ?? word);
const members = (name, bindings = new Map()) => {
if (name === "Node") return new Map();
const declaration = interfaces.get(name);
if (!declaration) throw new Error(`Unknown AST base ${name}`);
const fields = new Map();
for (const clause of declaration.heritageClauses ?? []) for (const base of clause.types) {
const baseName = base.expression.getText(generated);
const params = interfaces.get(baseName)?.typeParameters ?? [];
const args = base.typeArguments ?? [];
const inherited = new Map();
params.forEach((param, index) => {
const argument = args[index] ?? param.default;
if (!argument) throw new Error(`Missing AST type argument ${baseName}.${param.name.text}`);
inherited.set(param.name.text, replace(argument.getText(generated), bindings));
});
for (const [key, value] of members(baseName, inherited)) fields.set(key, value);
}
for (const member of declaration.members) {
if (!ts.isPropertySignature(member) || !member.type || !ts.isIdentifier(member.name)) throw new Error(`Unexpected AST member in ${name}`);
const key = member.name.text;
// Brands and synthetic-factory fields do not exist on remote nodes.
if (/^_.*Brand$/.test(key) ||
(name === "FunctionLikeBase" && key === "fullSignature") ||
(name === "SyntheticExpression" && (key === "isSpread" || key === "type"))) continue;
let type = replace(member.type.getText(generated), bindings);
// The pinned native wire and RemoteNode getter encode one tag, despite
// the SDK factory-oriented interface declaring an array of tags.
if (key === "jsdocPropertyTags") type = "JSDocPropertyTag";
if (member.questionToken) type += " | undefined";
fields.set(key, ` readonly ${key}: ${type};`);
}
return fields;
};
const types = [...header, "",
'import type { AstNode } from "./ast-node.js";',
'import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js";',
'import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js";',
"type Node = AstNode;",
];
// Brand-only bases otherwise become identical to Node, making the false
// side of a predicate narrow every node to never. Use actual descendant
// kind discriminants instead of inventing fields in native storage.
const derivesFrom = (name, base) => name === base || (interfaces.get(name)?.heritageClauses ?? []).some((clause) => clause.types.some((type) => derivesFrom(type.expression.getText(generated), base)));
for (const node of generated.statements) {
if (ts.isTypeAliasDeclaration(node)) types.push("", node.name.text === "TypeNode"
? "export type TypeNode = TypeNodeBase | ExpressionWithTypeArguments;"
: node.getText(generated));
if (!ts.isInterfaceDeclaration(node)) continue;
const params = node.typeParameters?.length ? `<${node.typeParameters.map((param) => param.getText(generated)).join(", ")}>` : "";
const fields = members(node.name.text);
if (!fields.has("kind")) {
const kinds = new Set();
for (const [name, child] of interfaces) {
if (!derivesFrom(name, node.name.text)) continue;
let kind = child.members.find((member) => member.name?.getText(generated) === "kind")?.type?.getText(generated);
if (kind === undefined) continue;
for (const parameter of child.typeParameters ?? []) kind = replace(kind, new Map([[parameter.name.text, parameter.constraint?.getText(generated) ?? "never"]]));
kinds.add(kind);
}
if (kinds.size > 0) fields.set("kind", ` readonly kind: ${[...kinds].join(" | ")};`);
}
types.push("", `export interface ${node.name.text}${params} extends Node {`, ...fields.values(), "}");
}
write("ast-types.generated.ts", types);
// Pair upstream executable bodies with their declared predicates. This
// preserves the pinned kind/flag semantics without loading SDK JavaScript.
const guards = [...header, "", 'import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js";'];
for (const stem of ["is.generated", "is"]) {
const declarations = parse(`${stem}.d.ts`);
const signatures = new Map();
for (const node of declarations.statements) {
if (ts.isImportDeclaration(node) && node.importClause?.isTypeOnly) {
guards.push(node.getText(declarations).replace('"./ast.ts"', '"./ast-types.js"'));
}
if (ts.isTypeAliasDeclaration(node)) guards.push(node.getText(declarations)
.replace("WrappedExpression<T extends Expression>", "WrappedExpression")
.replace("WrappedExpression<Expression>", "WrappedExpression"));
if (ts.isFunctionDeclaration(node)) signatures.set(node.name.text, node);
}
const source = parse(`${stem}.js`);
for (const fn of source.statements) {
if (!ts.isFunctionDeclaration(fn) || !fn.body || !fn.name) continue;
const name = fn.name.text;
const signature = signatures.get(name);
let head;
if (signature) {
head = signature.getText(declarations).replace("declare ", "").replace(/;$/, "");
if (fn.parameters.some((param) => param.initializer)) {
head = head.replace("kinds?: OuterExpressionKinds", "kinds: OuterExpressionKinds = OuterExpressionKinds.All");
}
} else {
if (name === "isJSDocTypeAssertion") head = "function isJSDocTypeAssertion(node: Node): boolean";
else {
if (fn.parameters.length !== 1 || fn.parameters[0].name.getText(source) !== "kind") throw new Error(`Unrecognized private guard ${name}`);
head = `function ${name}(kind: SyntaxKind): boolean`;
}
}
let body = fn.body.getText(source);
if (name === "isJSDocTypeAssertion") body = body.replace("expression.kind !==", "expression === undefined || expression.kind !==");
if (head.includes("node: Node") && name.startsWith("is")) {
head = head.replace("node: Node", "node: Node | undefined");
body = body.replace("{", "{\n if (node === undefined) return false;");
}
guards.push("", head + " " + body);
}
}
// Merge duplicate type-only imports before writing the generated module.
const imported = new Set();
const content = guards.filter((line) => {
if (!line.startsWith("import type {")) return true;
for (const name of line.slice(line.indexOf("{") + 1, line.indexOf("}")).split(",")) imported.add(name.trim());
return false;
});
content.splice(header.length + 1, 0, `import type { ${[...imported].sort().join(", ")} } from "./ast-types.js";`);
write("ast-guards.generated.ts", content);
const { tokenToString } = require(join(packageRoot, "dist/ast/scanner.js"));
const { SyntaxKind } = require(join(packageRoot, "dist/enums/syntaxKind.js"));
const tokens = [...header, "", 'import { SyntaxKind } from "./enums.js";', "", "export function tokenToString(kind: SyntaxKind): string | undefined {", " switch (kind) {"];
for (let kind = 0; kind <= SyntaxKind.Count; kind++) {
const text = tokenToString(kind);
if (text !== undefined) tokens.push(` case ${kind}: return ${JSON.stringify(text)};`);
}
tokens.push(" default: return undefined;", " }", "}");
write("ast-tokens.generated.ts", tokens);
}
+60
View File
@@ -0,0 +1,60 @@
import type { AstFile } from "../../../packages/compiler/src/frontend/ts7/ast-node.js";
import type { Node, SourceFile, NodeArray, Statement } from "../../../packages/compiler/src/frontend/ts7/ast-types.js";
import * as ts from "../../../packages/compiler/src/frontend/ts7/ast-guards.generated.js";
import { KIND_NODE_LIST } from "../../../packages/compiler/src/frontend/ts7/ast-schema.generated.js";
import { tokenToString } from "../../../packages/compiler/src/frontend/ts7/ast-tokens.generated.js";
import { SyntaxKind } from "../../../packages/compiler/src/frontend/ts7/enums.js";
function check(value: boolean, message: string): void { if (!value) throw new Error(message); }
function checkStatements(nodes: NodeArray<Statement>, source: SourceFile): void {
check(nodes === source.statements, "typed statement array identity");
for (const statement of nodes) {
check(statement.parent === source && statement.getSourceFile() === source, "typed source identity");
check(ts.isStatement(statement), "statement kind predicate");
}
}
/** All narrowed values are consumed by native operations, so a structural
* record copy or a wrong getter ABI cannot pass as a mere successful load. */
export function checkAstViews(file: AstFile): void {
const source: SourceFile = file.root;
checkStatements(source.statements, source);
check(source.parent === undefined && !ts.isIdentifier(source.parent), "missing root parent");
let identifiers = 0;
let strings = 0;
let declarations = 0;
let methods = 0;
let calls = 0;
let binaries = 0;
for (let index = 1; index < file.wire.nodeCount; index++) {
if (file.wire.kind(index) === KIND_NODE_LIST) continue;
const node: Node = file.node(index);
if (ts.isIdentifier(node)) {
identifiers += node.text.length;
check(node.getSourceFile() === source, "identifier source identity");
check(!ts.isTypeNode(node), "identifier is not a type node");
}
if (ts.isStringLiteral(node)) strings += node.text.toUpperCase().length;
if (ts.isVariableDeclarationList(node)) {
declarations += node.declarations.length;
for (const declaration of node.declarations) check(declaration.parent === node, "declaration identity");
}
if (ts.isClassDeclaration(node)) {
for (const member of node.members) {
if (ts.isMethodDeclaration(member)) methods += member.parameters.length;
}
}
if (ts.isCallExpression(node)) {
calls += node.arguments.length;
check(node.expression.parent === node, "call callee identity");
}
if (ts.isBinaryExpression(node)) {
binaries++;
check(node.left === node.childNode("left") && node.right === node.childNode("right"), "binary child identity");
check(tokenToString(node.operatorToken.kind) !== undefined, "operator spelling");
}
}
check(identifiers > 10 && strings > 0 && declarations > 3 && methods > 0 && calls > 0 && binaries > 0, "typed AST paths executed");
check(tokenToString(SyntaxKind.EqualsGreaterThanToken) === "=>", "native token lookup");
}
+3 -1
View File
@@ -11,6 +11,7 @@ import { Ts7Session } from "../../../packages/compiler/src/frontend/ts7/session.
import { Ts7Paths, ts7DocumentFile } from "../../../packages/compiler/src/frontend/ts7/session-path.js";
import { parseSemanticJson } from "../../../packages/compiler/src/frontend/ts7/semantic-json.js";
import { checkSemanticModel, semanticSource } from "./ts7-semantic-cases.js";
import { checkAstViews } from "./ts7-ast-views.js";
// The harness connects these inherited descriptors straight to native tsgo.
// No JavaScript helper reads, interprets, or relays protocol messages.
@@ -240,6 +241,7 @@ try {
check(!names.includes(hidden), "hidden file remains absent");
const root = project.program.getSourceFile(file)!;
const tree = root.file;
checkAstViews(tree);
check(project.program.getSourceFile(file) === root, "cached source identity");
check(project.program.getCompilerOptions().strict === true, "parsed compiler options");
check(session.parseConfigFile(configPath).options["strict"] === true, "raw config option decoding");
@@ -301,7 +303,7 @@ try {
typeText, symbol: ownedSymbol.name, diagnostics: diagnostics.map((diagnostic) => diagnostic.code),
surrogateBoundary: checkSurrogateBoundary(),
semanticSurrogateBoundary: checkSemanticSurrogateBoundary(), semanticModel: true, sessionLifecycle: true,
echo: true, binaryAst: true, astIdentity: true, virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true,
echo: true, binaryAst: true, astIdentity: true, astViews: true, virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true,
}));
} finally {
session.close();
@@ -102,7 +102,7 @@ for (const backend of ["c", "llvm"] as const) {
expect(nativeSemanticSurrogates).toBe("refused");
expect(nativeFacts).toEqual(oracleFacts);
expect(nativeFacts).toEqual({
typeText: "42", symbol: "answer", diagnostics: [2322], echo: true, binaryAst: true, astIdentity: true, semanticModel: true, sessionLifecycle: true,
typeText: "42", symbol: "answer", diagnostics: [2322], echo: true, binaryAst: true, astIdentity: true, astViews: true, semanticModel: true, sessionLifecycle: true,
virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true,
});
} finally {