mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
- Parse standalone source with an isolated native TypeScript session and preserve AST ownership across edits. - Move package declaration projection and import scanning onto statically compilable syntax passes. - Support forwarded visitor results and guarded native iteration while preserving JSDoc annotations.
98 lines
5.2 KiB
TypeScript
98 lines
5.2 KiB
TypeScript
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { Ts7SourceParser, type Ts7SourceKind } from "../../../packages/compiler/src/frontend/ts7/source-parser.js";
|
|
import * as syntax from "../../../packages/compiler/src/frontend/ts7/syntax.js";
|
|
import * as declarations from "../../../packages/compiler/src/frontend/npm-static-declaration-syntax.js";
|
|
import { moduleSpecifiersOfFile, sourceImportsOfFile } from "../../../packages/compiler/src/frontend/module-syntax.js";
|
|
|
|
interface SyntaxRequest {
|
|
files: { path: string; text: string; kind: Ts7SourceKind }[];
|
|
packages: { declarations: string; source: string }[];
|
|
}
|
|
|
|
export function runSourceParser(parser: Ts7SourceParser, input: string, output: string): void {
|
|
const request = JSON.parse(readFileSync(input, "utf8")) as SyntaxRequest;
|
|
const files = request.files.map((item) => {
|
|
const file = parser.parse(item.path, item.text, item.kind);
|
|
let nodes = 0;
|
|
let optional = 0;
|
|
syntax.walkPreorder(file, (node) => {
|
|
nodes++;
|
|
if (node.questionToken !== undefined) optional++;
|
|
});
|
|
return {
|
|
text: file.text,
|
|
nodes,
|
|
optional,
|
|
declaration: file.isDeclarationFile,
|
|
statements: file.statements.map((statement) => ({
|
|
text: statement.getText(), start: statement.getStart(), end: statement.end,
|
|
})),
|
|
imports: moduleSpecifiersOfFile(file),
|
|
sourceImports: sourceImportsOfFile(file),
|
|
};
|
|
});
|
|
const packages = request.packages.map((item) => {
|
|
const declaredFile = parser.parse("package.d.ts", item.declarations, "ts");
|
|
const overloads = declarations.parseNpmStaticDeclarationOverloads(declaredFile);
|
|
const properties = declarations.parseNpmStaticDeclarationProperties(declaredFile);
|
|
const file = parser.parse("package.js", item.source, "js");
|
|
const names = new Set<string>();
|
|
for (const name of overloads.keys()) names.add(name);
|
|
for (const name of properties.keys()) names.add(name);
|
|
const targets = declarations.npmStaticRuntimeClassTargets(file, item.source, names);
|
|
const annotatedMethods = declarations.applyNpmStaticDeclarationOverloads(file, item.source, overloads);
|
|
const annotatedFields = declarations.applyNpmStaticDeclarationProperties(file, item.source, properties);
|
|
const nullable = declarations.applyNpmStaticNullableClassFields(file, item.source);
|
|
const find = declarations.applyNpmStaticFindReturnWidening(file, item.source);
|
|
const methodAgain = annotatedMethods === null ? null : declarations.applyNpmStaticDeclarationOverloads(
|
|
parser.parse("package.js", annotatedMethods.text, "js"), annotatedMethods.text, overloads,
|
|
);
|
|
const fieldAgain = annotatedFields === null ? null : declarations.applyNpmStaticDeclarationProperties(
|
|
parser.parse("package.js", annotatedFields.text, "js"), annotatedFields.text, properties,
|
|
);
|
|
const nullableAgain = nullable === null ? null : declarations.applyNpmStaticNullableClassFields(
|
|
parser.parse("package.js", nullable.text, "js"), nullable.text,
|
|
);
|
|
const findAgain = find === null ? null : declarations.applyNpmStaticFindReturnWidening(
|
|
parser.parse("package.js", find.text, "js"), find.text,
|
|
);
|
|
const overloadReport: { name: string; methods: { method: string; signatures: readonly declarations.NpmStaticOverloadSignature[] }[] }[] = [];
|
|
for (const [name, methods] of overloads) {
|
|
const methodReport: { method: string; signatures: readonly declarations.NpmStaticOverloadSignature[] }[] = [];
|
|
for (const [method, signatures] of methods) methodReport.push({ method, signatures });
|
|
overloadReport.push({ name, methods: methodReport });
|
|
}
|
|
const propertyReport: { name: string; fields: { field: string; type: string }[] }[] = [];
|
|
for (const [name, fields] of properties) {
|
|
const fieldReport: { field: string; type: string }[] = [];
|
|
for (const [field, type] of fields) fieldReport.push({ field, type });
|
|
propertyReport.push({ name, fields: fieldReport });
|
|
}
|
|
const targetReport: { name: string; target: declarations.NpmStaticRuntimeClassTarget }[] = [];
|
|
for (const [name, target] of targets) targetReport.push({ name, target });
|
|
return {
|
|
overloads: overloadReport,
|
|
properties: propertyReport,
|
|
reexports: declarations.npmStaticDeclarationReexports(declaredFile),
|
|
targets: targetReport,
|
|
annotatedMethods, annotatedFields, nullable, find,
|
|
methodAgain, fieldAgain, nullableAgain, findAgain,
|
|
};
|
|
});
|
|
// Parse a file, retire its snapshot, edit the same path, and retain both
|
|
// independent ASTs through subsequent requests and session shutdown.
|
|
const original = parser.parse("versions.ts", "export const before = 1;", "ts");
|
|
const updated = parser.parse("versions.ts", "export const after = 2;", "ts");
|
|
for (let version = 0; version < 32; version++) {
|
|
const source = `export const version = ${version};`;
|
|
const file = parser.parse("bounded.ts", source, "ts");
|
|
if (file.text !== source) throw new Error("stale source parser result");
|
|
}
|
|
parser.close();
|
|
parser.close();
|
|
const versions = [original.statements[0]!.getText(), updated.statements[0]!.getText()];
|
|
let closed = false;
|
|
try { parser.parse("late.ts", "", "ts"); } catch { closed = true; }
|
|
writeFileSync(output, JSON.stringify({ files, packages, versions, closed }));
|
|
}
|