mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-02 09:45:21 +00:00
* Add --continue-on-error flag to codegen for skipping unhandled data types * Use solc version defined in config * Fix codegen log for unhandled array types * Log solc compilation errors --------- Co-authored-by: Dhruv Srivastava <dhruvdhs.ds@gmail.com>
236 lines
7.5 KiB
TypeScript
236 lines
7.5 KiB
TypeScript
//
|
|
// Copyright 2021 Vulcanize, Inc.
|
|
//
|
|
|
|
import { Writable } from 'stream';
|
|
import assert from 'assert';
|
|
import { utils } from 'ethers';
|
|
|
|
import { Database } from './database';
|
|
import { Entity } from './entity';
|
|
import { Indexer } from './indexer';
|
|
import { Resolvers } from './resolvers';
|
|
import { Schema } from './schema';
|
|
import { Client } from './client';
|
|
import { Param } from './utils/types';
|
|
import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants';
|
|
import { parseSubgraphSchema } from './utils/subgraph';
|
|
import { Types } from './types';
|
|
|
|
export class Visitor {
|
|
_schema: Schema;
|
|
_resolvers: Resolvers;
|
|
_indexer: Indexer;
|
|
_entity: Entity;
|
|
_database: Database;
|
|
_client: Client;
|
|
_types: Types;
|
|
_continueOnError: boolean;
|
|
|
|
_contract?: { name: string, kind: string };
|
|
|
|
constructor (continueOnErrorFlag = false) {
|
|
this._schema = new Schema();
|
|
this._resolvers = new Resolvers();
|
|
this._indexer = new Indexer();
|
|
this._entity = new Entity();
|
|
this._database = new Database();
|
|
this._client = new Client();
|
|
this._types = new Types();
|
|
this._continueOnError = continueOnErrorFlag;
|
|
}
|
|
|
|
setContract (name: string, kind: string): void {
|
|
this._contract = {
|
|
name,
|
|
kind
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Visitor function for function definitions.
|
|
* @param node ASTNode for a function definition.
|
|
*/
|
|
functionDefinitionVisitor (node: any): void {
|
|
if (node.stateMutability === 'view' && (node.visibility === 'external' || node.visibility === 'public')) {
|
|
const name = node.name;
|
|
const params = node.parameters.map((item: any) => {
|
|
return { name: item.name, type: item.typeName.name };
|
|
});
|
|
|
|
let errorMessage = '';
|
|
|
|
const typeName = node.returnParameters[0].typeName;
|
|
switch (typeName.type) {
|
|
case 'ElementaryTypeName': {
|
|
const returnType = typeName.name;
|
|
|
|
this._schema.addQuery(name, params, returnType);
|
|
this._resolvers.addQuery(name, params, returnType);
|
|
this._entity.addQuery(name, params, returnType);
|
|
this._database.addQuery(name, params, returnType);
|
|
this._client.addQuery(name, params, returnType);
|
|
|
|
assert(this._contract);
|
|
this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, returnType);
|
|
|
|
break;
|
|
}
|
|
case 'UserDefinedTypeName':
|
|
errorMessage = `No support in codegen for user defined return type from method "${node.name}"`;
|
|
break;
|
|
|
|
case 'ArrayTypeName':
|
|
errorMessage = `No support in codegen for return type "${typeName.baseTypeName.name}[]" from method "${node.name}"`;
|
|
break;
|
|
|
|
default:
|
|
errorMessage = `No support in codegen for return type "${typeName.type}" from method "${node.name}"`;
|
|
}
|
|
|
|
if (errorMessage !== '') {
|
|
if (this._continueOnError) {
|
|
console.log(errorMessage);
|
|
return;
|
|
}
|
|
|
|
throw new Error(errorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Visitor function for state variable declarations.
|
|
* @param node ASTNode for a state variable declaration.
|
|
*/
|
|
stateVariableDeclarationVisitor (node: any): void {
|
|
// TODO Handle multiples variables in a single line.
|
|
// TODO Handle array types.
|
|
// TODO Handle user defined type .
|
|
const variable = node.variables[0];
|
|
const name: string = variable.name;
|
|
const stateVariableType: string = variable.typeName.type;
|
|
const params: Param[] = [];
|
|
|
|
if (variable.isImmutable) {
|
|
// Skip in case variable is immutable.
|
|
return;
|
|
}
|
|
|
|
let typeName = variable.typeName;
|
|
let numParams = 0;
|
|
|
|
// If the variable type is mapping, extract key as a param:
|
|
// Eg. mapping(address => mapping(address => uint256)) private _allowances;
|
|
while (typeName.type === 'Mapping') {
|
|
params.push({ name: `key${numParams.toString()}`, type: typeName.keyType.name });
|
|
typeName = typeName.valueType;
|
|
numParams++;
|
|
}
|
|
|
|
if (['UserDefinedTypeName', 'ArrayTypeName'].includes(typeName.type)) {
|
|
// Skip in case of UserDefinedTypeName | ArrayTypeName.
|
|
return;
|
|
}
|
|
|
|
const returnType = typeName.name;
|
|
|
|
this._schema.addQuery(name, params, returnType);
|
|
this._resolvers.addQuery(name, params, returnType);
|
|
this._entity.addQuery(name, params, returnType);
|
|
this._database.addQuery(name, params, returnType);
|
|
this._client.addQuery(name, params, returnType);
|
|
|
|
assert(this._contract);
|
|
this._indexer.addQuery(this._contract.name, MODE_STORAGE, name, params, returnType, stateVariableType);
|
|
}
|
|
|
|
/**
|
|
* Function to parse event definitions.
|
|
* @param abi Contract ABI.
|
|
*/
|
|
parseEvents (abi: any): void {
|
|
const contractInterface = new utils.Interface(abi);
|
|
|
|
Object.values(contractInterface.events).forEach(event => {
|
|
this._schema.addEventType(event.name, event.inputs);
|
|
});
|
|
}
|
|
|
|
visitSubgraph (subgraphPath?: string): void {
|
|
if (!subgraphPath) {
|
|
return;
|
|
}
|
|
|
|
// Parse subgraph schema to get subgraphSchemaDocument.
|
|
const subgraphSchemaDocument = parseSubgraphSchema(subgraphPath);
|
|
|
|
this._schema.addSubgraphSchema(subgraphSchemaDocument);
|
|
this._types.addSubgraphTypes(subgraphSchemaDocument);
|
|
this._entity.addSubgraphEntities(subgraphSchemaDocument);
|
|
this._resolvers.addSubgraphResolvers(subgraphSchemaDocument);
|
|
this._indexer.addSubgraphEntities(subgraphSchemaDocument);
|
|
this._database.addSubgraphEntities(subgraphSchemaDocument);
|
|
}
|
|
|
|
/**
|
|
* Writes schema to a stream.
|
|
* @param outStream A writable output stream to write the schema to.
|
|
* @returns The schema string.
|
|
*/
|
|
exportSchema (outStream: Writable): string {
|
|
return this._schema.exportSchema(outStream);
|
|
}
|
|
|
|
/**
|
|
* Writes the resolvers file generated from a template to a stream.
|
|
* @param outStream A writable output stream to write the resolvers file to.
|
|
*/
|
|
exportResolvers (outStream: Writable): void {
|
|
this._resolvers.exportResolvers(outStream);
|
|
}
|
|
|
|
/**
|
|
* Writes the indexer file generated from a template to a stream.
|
|
* @param outStream A writable output stream to write the indexer file to.
|
|
* @param contracts Input contracts to be passed to the template.
|
|
*/
|
|
exportIndexer (outStream: Writable, contracts: any[]): void {
|
|
this._indexer.exportIndexer(outStream, contracts);
|
|
}
|
|
|
|
/**
|
|
* Writes the generated entity files in the given directory.
|
|
* @param entityDir Directory to write the entities to.
|
|
*/
|
|
exportEntities (entityDir: string, subgraphPath: string): void {
|
|
this._entity.exportEntities(entityDir, subgraphPath);
|
|
}
|
|
|
|
/**
|
|
* Writes the database file generated from a template to a stream.
|
|
* @param outStream A writable output stream to write the database file to.
|
|
*/
|
|
exportDatabase (outStream: Writable): void {
|
|
this._database.exportDatabase(outStream);
|
|
}
|
|
|
|
/**
|
|
* Writes the client file generated from a template to a stream and export quries.
|
|
* @param outStream A writable output stream to write the client file to.
|
|
* @param schemaContent Content of the schema for generating the queries, mutations and subscriptions.
|
|
* @param gqlDir Directory to store the generated gql queries, mutations and subscriptions.
|
|
*/
|
|
exportClient (outStream: Writable, schemaContent: string, gqlDir: string): void {
|
|
this._client.exportClient(outStream, schemaContent, gqlDir);
|
|
}
|
|
|
|
/**
|
|
* Writes the types file generated from a template to a stream.
|
|
* @param outStream A writable output stream to write the database file to.
|
|
*/
|
|
exportTypes (outStream: Writable): void {
|
|
this._types.exportTypes(outStream);
|
|
}
|
|
}
|