Files
scriptc/packages/compiler/src/frontend/function-completion.ts
T
Chris Tate 3c749a2895 Read AST lists through their nodes
The frontend's node lists become node lists, { pos, end, nodes }, the
shape of tsc-ts's NodeList, so that the frontend can later read tsc-ts's
own nodes. AstNode's list getters return an AstNodeList, ts.NodeArray<T>
is that class, and the frontend reads a list's elements through nodes.
The lists already carried their positions on the wire.

The class is the type, not a structural interface, because native code
represents a class and a record differently.

A decoded file keeps each list's nodes array, as before, and each read
of a list field returns a new AstNodeList over it, so the file holds no
more objects than before. nodes keeps its identity; a list object is not
the same object on each read.

The change is mechanical: a type-aware rewrite of each list field read,
plus hand edits where lists are read through casts, generic accessors
or test mocks. The generated getters and types follow from
generate-ts7-ast-schema.mjs. AstFile and Ts7Session drop their unused
listMetadata hook.
2026-10-19 06:33:33 -05:00

60 lines
2.4 KiB
TypeScript

import * as ts from "./ts7/adapter.js";
/** JavaScript documentation does not enforce an explicit return. Keep
* implicit completion even when the documented result omits undefined. */
export function functionCanFallThrough(declaration: ts.Node): boolean {
if (
!ts.isFunctionLike(declaration) ||
declaration.asteriskToken ||
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
)
return false;
const body = declaration.body as ts.Block | ts.Expression | undefined;
if (!body || !ts.isBlock(body)) return false;
const completes = (statement: ts.Statement): boolean => {
if (ts.isReturnStatement(statement) || ts.isThrowStatement(statement)) return false;
if (ts.isBlock(statement)) return sequence(statement.statements.nodes);
if (ts.isIfStatement(statement))
return (
completes(statement.thenStatement) ||
!statement.elseStatement ||
completes(statement.elseStatement)
);
if (ts.isTryStatement(statement)) {
if (statement.finallyBlock && !completes(statement.finallyBlock)) return false;
return (
completes(statement.tryBlock) ||
(!!statement.catchClause && completes(statement.catchClause.block))
);
}
if (ts.isSwitchStatement(statement))
return (
!statement.caseBlock.clauses.nodes.some(ts.isDefaultClause) ||
statement.caseBlock.clauses.nodes.some((clause) => sequence(clause.statements.nodes))
);
return true;
};
const sequence = (statements: readonly ts.Statement[]): boolean => statements.every(completes);
return sequence(body.statements.nodes);
}
/** A documented JavaScript result must also represent explicit empty returns. */
export function functionCanReturnUndefined(declaration: ts.Node): boolean {
if (functionCanFallThrough(declaration)) return true;
if (
!ts.isFunctionLike(declaration) ||
declaration.asteriskToken ||
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
)
return false;
const body = declaration.body;
if (!body) return false;
let emptyReturn = false;
ts.walkPreorder(body, (node) => {
if (node !== body && ts.isFunctionLike(node)) return "skip";
if (ts.isReturnStatement(node) && (!node.expression || ts.isVoidExpression(node.expression)))
emptyReturn = true;
});
return emptyReturn;
}