opnet-transform-web
Version:
OP_NET AssemblyScript transformer
113 lines (91 loc) • 4.31 kB
text/typescript
import OPNetTransformer, { SimpleParser, logger } from './OPNetTransformer.js';
import { Parser, NodeKind, MethodDeclaration } from 'assemblyscript/dist/assemblyscript.js';
import parserTypeScript from 'prettier/parser-typescript';
import prettier from 'prettier/standalone';
import prettierPluginEstree from 'prettier/plugins/estree';
export default class OpnetWebTransformer extends OPNetTransformer {
private virtualFs: Map<string, string> = new Map();
public readFile(filename: string, baseDir: string): string | null {
return this.virtualFs.get(`${baseDir}/${filename}`) ?? null;
}
public writeFile(filename: string, contents: string | Uint8Array, baseDir: string): void {
this.virtualFs.set(`${baseDir}/${filename}`, contents.toString());
return;
}
public listFiles(dirname: string, baseDir: string): string[] | null {
const files: string[] = [];
for (const [key] of this.virtualFs.entries()) {
if (key.startsWith(`${baseDir}/${dirname}`)) {
files.push(key.replace(`${baseDir}/`, ''));
}
}
return files.length > 0 ? files : null;
}
public override async afterParse(parser: Parser): Promise<void> {
// 1) Parse AST
for (const source of parser.sources) {
if (source.isLibrary || source.internalPath.startsWith('~lib/')) {
continue;
}
for (const stmt of source.statements) {
this.visitStatement(stmt);
}
}
// 2) Build ABI per class
const abiMap = this.buildAbiPerClass();
// 4) Write one JSON + .d.ts per class
for (const [className, abiObj] of abiMap.entries()) {
if (abiObj.functions.length === 0) continue;
// JSON
const filePath = `abis/${className}.abi.json`;
this.writeFile(filePath, JSON.stringify(abiObj, null, 4), '.');
logger.success(`ABI generated to ${filePath}`);
// DTS
const dtsPath = `abis/${className}.d.ts`;
const dtsContents = this.buildDtsForClass(className, abiObj);
const formattedDts = await prettier.format(dtsContents, {
plugins: [parserTypeScript, prettierPluginEstree],
parser: 'typescript',
printWidth: 100,
trailingComma: 'all',
tabWidth: 4,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
bracketSameLine: true,
arrowParens: 'always',
singleAttributePerLine: true,
});
this.writeFile(dtsPath, formattedDts, '.');
logger.success(`Type definitions generated to ${dtsPath}`);
}
// 5) Inject/overwrite `execute` in each relevant class
for (const [className, methods] of this.methodsByClass.entries()) {
if (!methods.length) continue;
logger.info(`Injecting 'execute' in class ${className}...`);
const classDecl = this.classDeclarations.get(className);
if (!classDecl) {
logger.warn(`ClassDeclaration not found for ${className}`);
continue;
}
const methodText = this.buildExecuteMethod(className, methods);
const newMember = SimpleParser.parseClassMember(methodText, classDecl);
// Overwrite if it exists
const existingIndex = classDecl.members.findIndex((member) => {
return (
member.kind === NodeKind.MethodDeclaration &&
(member as MethodDeclaration).name.text === 'execute'
);
});
if (existingIndex !== -1) {
logger.info(`Overwriting existing 'execute' in class ${className}`);
classDecl.members[existingIndex] = newMember;
} else {
classDecl.members.push(newMember);
}
}
// 6) Check for "unused" events and log warnings
this.checkUnusedEvents();
}
}