mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
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.
60 lines
2.4 KiB
TypeScript
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;
|
|
}
|