mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user