use handlebars for code generation (#2228)

* use handlebars for code generation

* update docs

* fix isStrict

* fix lint

* Apply suggestions from code review

Co-authored-by: Jaco Greeff <jacogr@gmail.com>

* convert consts

* keep empty lines in doc comments

* update rpc

* update tx

* update interfaceRegistry

* update tsDef

* remove no longer used code

Co-authored-by: Jaco Greeff <jacogr@gmail.com>
This commit is contained in:
Xiliang Chen
2020-05-04 15:21:09 +02:00
committed by GitHub
co-authored by Jaco Greeff
parent 39ed76f2de
commit 174510a928
27 changed files with 957 additions and 299 deletions
+40 -39
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ModuleMetadataLatest } from '@polkadot/types/interfaces/metadata';
import Handlebars from 'handlebars';
import staticData from '@polkadot/metadata/Metadata/static';
import Metadata from '@polkadot/metadata/Metadata';
@@ -10,29 +10,10 @@ import { TypeRegistry } from '@polkadot/types/create';
import * as defaultDefs from '@polkadot/types/interfaces/definitions';
import { stringCamelCase } from '@polkadot/util';
import { FOOTER, HEADER, TypeImports, createDocComments, createImportCode, createImports, indent, registerDefinitions, setImports, writeFile } from '../util';
import { createImports, compareName, readTemplate, registerDefinitions, setImports, writeFile } from '../util';
// Generate types for one module
/** @internal */
function generateModule (allDefs: object, { constants, name }: ModuleMetadataLatest, imports: TypeImports, isStrict: boolean): string[] {
if (!constants.length) {
return [];
}
if (!isStrict) {
setImports(allDefs, imports, ['Codec']);
}
return [indent(4)(`${stringCamelCase(name.toString())}: {`)]
.concat(isStrict ? '' : indent(6)('[index: string]: AugmentedConst<object & Codec>;'))
.concat(constants.sort((a, b) => a.name.localeCompare(b.name.toString())).map(({ documentation, name, type }): string => {
setImports(allDefs, imports, [type.toString()]);
return createDocComments(6, documentation) +
indent(6)(`${stringCamelCase(name.toString())}: AugmentedConst<${type}>;`);
}))
.concat([indent(4)('};')]);
}
const template = readTemplate('consts');
const generateForMetaTemplate = Handlebars.compile(template);
/** @internal */
function generateForMeta (meta: Metadata, dest: string, extraTypes: Record<string, Record<string, { types: Record<string, any> }>>, isStrict: boolean): void {
@@ -42,27 +23,47 @@ function generateForMeta (meta: Metadata, dest: string, extraTypes: Record<strin
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const body = meta.asLatest.modules.sort((a, b) => a.name.localeCompare(b.name.toString())).reduce((acc, mod): string[] => {
return acc.concat(generateModule(allDefs, mod, imports, isStrict));
}, [] as string[]);
const header = createImportCode(HEADER('chain'), imports, [
const modules = meta.asLatest.modules
.sort(compareName)
.filter((mod) => mod.constants.length > 0)
.map(({ constants, name }) => {
if (!isStrict) {
setImports(allDefs, imports, ['Codec']);
}
const items = constants
.sort(compareName)
.map(({ documentation, name, type }) => {
setImports(allDefs, imports, [type.toString()]);
return {
docs: documentation,
name: stringCamelCase(name.toString()),
type: type.toString()
};
});
return {
items,
name: stringCamelCase(name.toString())
};
});
const types = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]);
const interfaceStart = [
"declare module '@polkadot/metadata/Decorated/consts/types' {",
indent(2)('export interface Constants {\n')
].join('\n');
const interfaceEnd = `\n${indent(2)('}')}\n}`;
];
return header
.concat(interfaceStart)
.concat(isStrict ? '' : indent(4)('[index: string]: ModuleConstants;\n'))
.concat(body.join('\n'))
.concat(interfaceEnd)
.concat(FOOTER);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types
});
});
}
@@ -2,17 +2,22 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Handlebars from 'handlebars';
import Raw from '@polkadot/types/codec/Raw';
import * as defaultDefinitions from '@polkadot/types/interfaces/definitions';
import * as defaultPrimitives from '@polkadot/types/primitive';
import { FOOTER, HEADER, createImportCode, createImports, getDerivedTypes, indent, setImports, writeFile } from '../util';
import { createImports, getDerivedTypes, readTemplate, setImports, writeFile } from '../util';
const primitiveClasses = {
...defaultPrimitives,
Raw
};
const template = readTemplate('interfaceRegistry');
const generateInterfaceTypesTemplate = Handlebars.compile(template);
/** @internal */
export function generateInterfaceTypes (importDefinitions: { [importPath: string]: object }, dest: string): void {
writeFile(dest, (): string => {
@@ -21,52 +26,43 @@ export function generateInterfaceTypes (importDefinitions: { [importPath: string
const imports = createImports(importDefinitions);
const definitions = imports.definitions;
const primitives = Object
const items: string[] = [];
Object
.keys(primitiveClasses)
.filter((name): boolean => !!name.indexOf('Generic'))
.reduce((accumulator, primitiveName): string => {
.forEach((primitiveName) => {
setImports(definitions, imports, [primitiveName]);
return [
accumulator,
getDerivedTypes(definitions, primitiveName, primitiveName, imports).map(indent(4)).join('\n')
].join('\n');
}, '');
items.push(...getDerivedTypes(definitions, primitiveName, primitiveName, imports));
});
const existingTypes: Record<string, boolean> = {};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const srml = Object.entries(definitions).reduce((accumulator, [_defName, { types }]): string => {
Object.entries(definitions).forEach(([, { types }]) => {
setImports(definitions, imports, Object.keys(types));
const uniqueTypes = Object.keys(types).filter((type) => !existingTypes[type]);
uniqueTypes.forEach((type) => { existingTypes[type] = true; });
uniqueTypes.forEach((type) => {
existingTypes[type] = true;
items.push(...getDerivedTypes(definitions, type, types[type], imports));
});
});
return [
accumulator,
...uniqueTypes.map((type): string =>
getDerivedTypes(definitions, type, types[type], imports).map(indent(4)).join('\n')
)
].join('\n');
}, '');
const header = createImportCode(HEADER('defs'), imports, [
const types = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]);
];
const interfaceStart = "declare module '@polkadot/types/types/registry' {\n export interface InterfaceTypes {";
const interfaceEnd = '\n }\n}';
return header
.concat(interfaceStart)
.concat(primitives)
.concat(srml)
.concat(interfaceEnd)
.concat(FOOTER);
return generateInterfaceTypesTemplate({
headerType: 'defs',
imports,
items,
types
});
});
}
+44 -45
View File
@@ -2,7 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ModuleMetadataLatest, StorageEntryMetadataLatest } from '@polkadot/types/interfaces/metadata';
import Handlebars from 'handlebars';
import { StorageEntryMetadataLatest } from '@polkadot/types/interfaces/metadata';
import { Registry } from '@polkadot/types/types';
import staticData from '@polkadot/metadata/Metadata/static';
@@ -12,7 +14,7 @@ import { unwrapStorageType } from '@polkadot/types/primitive/StorageKey';
import { TypeRegistry } from '@polkadot/types/create';
import { stringCamelCase } from '@polkadot/util';
import { FOOTER, HEADER, TypeImports, createDocComments, createImportCode, createImports, formatType, getSimilarTypes, indent, registerDefinitions, setImports, writeFile } from '../util';
import { TypeImports, createImports, compareName, formatType, getSimilarTypes, readTemplate, registerDefinitions, setImports, writeFile } from '../util';
// From a storage entry metadata, we return [args, returnType]
/** @internal */
@@ -60,28 +62,8 @@ function entrySignature (allDefs: object, registry: Registry, storageEntry: Stor
throw new Error(`entryArgs: Cannot parse args of entry ${storageEntry.name}`);
}
// Generate types for one module
/** @internal */
function generateModule (allDefs: object, registry: Registry, { name, storage }: ModuleMetadataLatest, imports: TypeImports, isStrict: boolean): string[] {
if (storage.isNone) {
return [];
}
return [indent(4)(`${stringCamelCase(name.toString())}: {`)]
.concat(isStrict ? '' : indent(6)('[index: string]: QueryableStorageEntry<ApiType>;'))
.concat(storage.unwrap().items.sort((a, b) => a.name.localeCompare(b.name.toString())).map((storageEntry): string => {
const [args, returnType] = entrySignature(allDefs, registry, storageEntry, imports);
let entryType = 'AugmentedQuery';
if (storageEntry.type.isDoubleMap) {
entryType = `${entryType}DoubleMap`;
}
return createDocComments(6, storageEntry.documentation) +
indent(6)(`${stringCamelCase(storageEntry.name.toString())}: ${entryType}<ApiType, (${args}) => Observable<${returnType}>>${isStrict ? '' : ' & QueryableStorageEntry<ApiType>'};`);
}))
.concat([indent(4)('};')]);
}
const template = readTemplate('query');
const generateForMetaTemplate = Handlebars.compile(template);
/** @internal */
function generateForMeta (registry: Registry, meta: Metadata, dest: string, extraTypes: Record<string, Record<string, { types: Record<string, any> }>>, isStrict: boolean): void {
@@ -91,13 +73,39 @@ function generateForMeta (registry: Registry, meta: Metadata, dest: string, extr
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const body = meta.asLatest.modules.sort((a, b) => a.name.localeCompare(b.name.toString())).reduce((acc: string[], mod): string[] => {
return acc.concat(generateModule(allDefs, registry, mod, imports, isStrict));
}, []);
const modules = meta.asLatest.modules
.sort(compareName)
.filter((mod) => !mod.storage.isNone)
.map(({ name, storage }) => {
const items = storage.unwrap().items
.sort(compareName)
.map((storageEntry) => {
const [args, returnType] = entrySignature(allDefs, registry, storageEntry, imports);
let entryType = 'AugmentedQuery';
if (storageEntry.type.isDoubleMap) {
entryType = `${entryType}DoubleMap`;
}
return {
args,
docs: storageEntry.documentation,
entryType,
name: stringCamelCase(storageEntry.name.toString()),
returnType
};
});
return {
items,
name: stringCamelCase(name.toString())
};
});
imports.typesTypes.Observable = true;
const header = createImportCode(HEADER('chain'), imports, [
const types = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
@@ -106,24 +114,15 @@ function generateForMeta (registry: Registry, meta: Metadata, dest: string, extr
file: '@polkadot/api/types',
types: ['ApiTypes']
}
]);
const interfaceStart = [
"declare module '@polkadot/api/types/storage' {",
indent(2)('export interface AugmentedQueries<ApiType> {\n')
].join('\n');
const interfaceEnd = `\n${indent(2)('}')}\n\n`;
const queryableStorageInterface = indent(2)('export interface QueryableStorage<ApiType extends ApiTypes> extends AugmentedQueries<ApiType> {')
.concat('\n')
.concat(isStrict ? '' : indent(4)('[index: string]: QueryableModuleStorage<ApiType>;\n'))
.concat(indent(2)('}\n'));
];
return header
.concat(interfaceStart)
.concat(body.join('\n'))
.concat(interfaceEnd)
.concat(queryableStorageInterface)
.concat('}')
.concat(FOOTER);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types
});
});
}
+54 -31
View File
@@ -2,13 +2,18 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Handlebars from 'handlebars';
import { TypeRegistry } from '@polkadot/types/create';
import * as definitions from '@polkadot/types/interfaces/definitions';
import { FOOTER, HEADER, createDocComments, createImportCode, createImports, getSimilarTypes, setImports, writeFile, indent } from '../util';
import { createImports, getSimilarTypes, readTemplate, setImports, writeFile } from '../util';
const StorageKeyTye = 'StorageKey | string | Uint8Array | any';
const template = readTemplate('rpc');
const generateRpcTypesTemplate = Handlebars.compile(template);
/** @internal */
export default function generateRpcTypes (dest = 'packages/api/src/augment/rpc.ts'): void {
writeFile(dest, (): string => {
@@ -22,62 +27,80 @@ export default function generateRpcTypes (dest = 'packages/api/src/augment/rpc.t
.keys(definitions)
.filter((key) => Object.keys(definitions[key as 'babe'].rpc || {}).length !== 0)
.sort();
const body = rpcKeys.reduce<string[]>((allSections, section): string[] => {
const allMethods = Object.keys(definitions[section as 'babe'].rpc).sort().map((methodName): string => {
const modules = rpcKeys.map((section) => {
const allMethods = Object.keys(definitions[section as 'babe'].rpc).sort().map((methodName) => {
const def = definitions[section as 'babe'].rpc[methodName];
let args;
let type;
let generic;
// These are too hard to type with generics, do manual overrides
if (section === 'state') {
setImports(allDefs, imports, ['Codec', 'Hash', 'StorageKey', 'Vec']);
if (methodName === 'getStorage') {
return createDocComments(6, [def.description]) + indent(6)(`getStorage: AugmentedRpc<<T = Codec>(key: ${StorageKeyTye}, block?: Hash | Uint8Array | string) => Observable<T>>;`);
generic = 'T = Codec';
args = [`key: ${StorageKeyTye}, block?: Hash | Uint8Array | string`];
type = 'T';
} else if (methodName === 'queryStorage') {
return createDocComments(6, [def.description]) + indent(6)(`queryStorage: AugmentedRpc<<T = Codec[]>(keys: Vec<StorageKey> | (${StorageKeyTye})[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string) => Observable<[Hash, T][]>>;`);
generic = 'T = Codec[]';
args = [`keys: Vec<StorageKey> | (${StorageKeyTye})[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string`];
type = '[Hash, T][]';
} else if (methodName === 'queryStorageAt') {
return createDocComments(6, [def.description]) + indent(6)(`queryStorageAt: AugmentedRpc<<T = Codec[]>(keys: Vec<StorageKey> | (${StorageKeyTye})[], at?: Hash | Uint8Array | string) => Observable<T>>;`);
generic = 'T = Codec[]';
args = [`keys: Vec<StorageKey> | (${StorageKeyTye})[], at?: Hash | Uint8Array | string`];
type = 'T';
} else if (methodName === 'subscribeStorage') {
return createDocComments(6, [def.description]) + indent(6)(`subscribeStorage: AugmentedRpc<<T = Codec[]>(keys?: Vec<StorageKey> | (${StorageKeyTye})[]) => Observable<T>>;`);
generic = 'T = Codec[]';
args = [`keys?: Vec<StorageKey> | (${StorageKeyTye})[]`];
type = 'T';
}
}
setImports(allDefs, imports, [def.type]);
if (args === undefined) {
setImports(allDefs, imports, [def.type]);
args = def.params.map((param: any) => {
const similarTypes = getSimilarTypes(definitions, registry, param.type, imports);
const args = def.params.map((param: any): string => {
const similarTypes = getSimilarTypes(definitions, registry, param.type, imports);
setImports(allDefs, imports, [param.type, ...similarTypes]);
setImports(allDefs, imports, [param.type, ...similarTypes]);
return `${param.name}${param.isOptional ? '?' : ''}: ${similarTypes.join(' | ')}`;
});
return `${param.name}${param.isOptional ? '?' : ''}: ${similarTypes.join(' | ')}`;
});
type = def.type;
generic = '';
}
return createDocComments(6, [def.description]) + indent(6)(`${methodName}: AugmentedRpc<(${args.join(', ')}) => Observable<${def.type}>>;`);
return {
args: args.join(', '),
docs: [def.description],
generic,
name: methodName,
type
};
});
return allSections.concat(
[
` ${section}: {`,
...allMethods,
' };'
].join('\n')
);
}, []).join('\n');
return {
items: allMethods,
name: section
};
});
imports.typesTypes.Observable = true;
const header = createImportCode(HEADER('chain'), imports, [
const types = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]);
const interfaceStart = "declare module '@polkadot/rpc-core/types.jsonrpc' {\n export interface RpcInterface {\n";
const interfaceEnd = '\n }\n}';
];
return header
.concat(interfaceStart)
.concat(body)
.concat(interfaceEnd)
.concat(FOOTER);
return generateRpcTypesTemplate({
headerType: 'chain',
imports,
modules,
types
});
});
}
+28 -15
View File
@@ -2,6 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Handlebars from 'handlebars';
import { TypeDef, TypeDefInfo } from '@polkadot/types/create/types';
import path from 'path';
@@ -9,7 +11,7 @@ import { getTypeDef } from '@polkadot/types/create';
import * as defaultDefinitions from '@polkadot/types/interfaces/definitions';
import { isString, stringCamelCase, stringUpperFirst } from '@polkadot/util';
import { FOOTER, HEADER, TypeImports, createImportCode, createImports, exportInterface, exportType, formatType, setImports, writeFile } from '../util';
import { TypeImports, createImports, exportInterface, exportType, readTemplate, formatType, setImports, writeFile } from '../util';
interface Imports extends TypeImports {
interfaces: [string, string][];
@@ -181,22 +183,37 @@ function generateInterfaces (definitions: object, { types }: { types: Record<str
});
}
const templateIndex = readTemplate('tsDef/index');
const generateTsDefIndexTemplate = Handlebars.compile(templateIndex);
const templateModuleTypes = readTemplate('tsDef/moduleTypes');
const generateTsDefModuleTypesTemplate = Handlebars.compile(templateModuleTypes);
const templateTypes = readTemplate('tsDef/types');
const generateTsDefTypesTemplate = Handlebars.compile(templateTypes);
/** @internal */
function generateTsDefFor (importDefinitions: { [importPath: string]: object }, defName: string, { types }: { types: Record<string, any> }, outputDir: string): void {
const imports = { ...createImports(importDefinitions, { types }), interfaces: [] } as Imports;
const definitions = imports.definitions;
const interfaces = generateInterfaces(definitions, { types }, imports);
const sortedDefs = interfaces.sort((a, b): number => a[0].localeCompare(b[0])).map(([, definition]): string => definition).join('\n\n');
const items = interfaces.sort((a, b): number => a[0].localeCompare(b[0])).map(([, definition]): string => definition);
const header = createImportCode(HEADER('defs'), imports, [
const importTypes = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]);
];
writeFile(path.join(outputDir, defName, 'types.ts'), () => header.concat(sortedDefs).concat(`\n\nexport type PHANTOM_${defName.toUpperCase()} = '${defName}';`).concat(FOOTER), true);
writeFile(path.join(outputDir, defName, 'index.ts'), () => HEADER('defs').concat('export * from \'./types\';').concat(FOOTER), true);
writeFile(path.join(outputDir, defName, 'types.ts'), () => generateTsDefModuleTypesTemplate({
headerType: 'defs',
imports,
items,
name: defName,
types: importTypes
}), true);
writeFile(path.join(outputDir, defName, 'index.ts'), () => generateTsDefIndexTemplate({ headerType: 'defs' }), true);
}
/** @internal */
@@ -210,17 +227,13 @@ export function generateTsDef (importDefinitions: { [importPath: string]: object
generateTsDefFor(importDefinitions, defName, obj, outputDir);
});
return HEADER('defs')
.concat(
Object
.keys(definitions)
.map((moduleName): string => `export * from './${moduleName}/types';`)
.join('\n')
)
.concat(FOOTER);
return generateTsDefTypesTemplate({
headerType: 'defs',
items: Object.keys(definitions)
});
});
writeFile(path.join(outputDir, 'index.ts'), () => HEADER('defs').concat('export * from \'./types\';').concat(FOOTER), true);
writeFile(path.join(outputDir, 'index.ts'), () => generateTsDefIndexTemplate({ headerType: 'defs' }), true);
}
/** @internal */
+49 -57
View File
@@ -2,7 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ModuleMetadataLatest } from '@polkadot/types/interfaces/metadata';
import Handlebars from 'handlebars';
import { Registry } from '@polkadot/types/types';
import staticData from '@polkadot/metadata/Metadata/static';
@@ -12,7 +13,7 @@ import { Text } from '@polkadot/types/primitive';
import { TypeRegistry } from '@polkadot/types/create';
import { stringCamelCase } from '@polkadot/util';
import { FOOTER, HEADER, TypeImports, createDocComments, createImportCode, createImports, formatType, getSimilarTypes, indent, registerDefinitions, setImports, writeFile } from '../util';
import { createImports, compareName, formatType, getSimilarTypes, readTemplate, registerDefinitions, setImports, writeFile } from '../util';
const MAPPED_NAMES: Record<string, string> = {
new: 'updated'
@@ -24,39 +25,8 @@ function mapName (_name: Text): string {
return MAPPED_NAMES[name] || name;
}
// Generate types for one module
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function generateModule (registry: Registry, allDefs: object, { calls, name }: ModuleMetadataLatest, imports: TypeImports, isStrict: boolean): string[] {
const allCalls = calls.unwrapOr(null);
if (!allCalls?.length) {
return [];
}
setImports(allDefs, imports, ['Call', 'Extrinsic', 'SubmittableExtrinsic']);
return [indent(4)(`${stringCamelCase(name.toString())}: {`)]
.concat(isStrict ? '' : indent(6)('[index: string]: SubmittableExtrinsicFunction<ApiType>;'))
.concat(allCalls.sort((a, b) => a.name.localeCompare(b.name.toString())).map(({ args, documentation, name }): string => {
const params = args
.map(({ name, type }): [string, string, string] => {
const typeStr = type.toString();
const similarTypes = getSimilarTypes(allDefs, registry, typeStr, imports).map((type): string => formatType(allDefs, type, imports));
const nameStr = mapName(name);
setImports(allDefs, imports, [...similarTypes.filter((type): boolean => !type.startsWith('(') && !type.startsWith('{')), typeStr]);
return [`${nameStr}: ${similarTypes.join(' | ')}`, nameStr, typeStr];
})
.map(([full]): string => full)
.join(', ');
return createDocComments(6, documentation) +
indent(6)(`${stringCamelCase(name.toString())}: AugmentedSubmittable<(${params}) => SubmittableExtrinsic<ApiType>>;`);
}))
.concat([indent(4)('};')]);
}
const template = readTemplate('tx');
const generateForMetaTemplate = Handlebars.compile(template);
/** @internal */
function generateForMeta (registry: Registry, meta: Metadata, dest: string, extraTypes: Record<string, Record<string, { types: Record<string, any> }>>, isStrict: boolean): void {
@@ -66,10 +36,42 @@ function generateForMeta (registry: Registry, meta: Metadata, dest: string, extr
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const body = meta.asLatest.modules.sort((a, b) => a.name.localeCompare(b.name.toString())).reduce((acc, mod): string[] => {
return acc.concat(generateModule(registry, allDefs, mod, imports, isStrict));
}, [] as string[]);
const header = createImportCode(HEADER('chain'), imports, [
const modules = meta.asLatest.modules
.sort(compareName)
.filter(({ calls }) => calls.unwrapOr([]).length)
.map(({ calls, name }) => {
setImports(allDefs, imports, ['Call', 'Extrinsic', 'SubmittableExtrinsic']);
const items = calls.unwrap()
.sort(compareName)
.map(({ args, documentation, name }) => {
const params = args
.map(({ name, type }) => {
const typeStr = type.toString();
const similarTypes = getSimilarTypes(allDefs, registry, typeStr, imports).map((type): string => formatType(allDefs, type, imports));
const nameStr = mapName(name);
setImports(allDefs, imports, [...similarTypes.filter((type): boolean => !type.startsWith('(') && !type.startsWith('{')), typeStr]);
return `${nameStr}: ${similarTypes.join(' | ')}`;
})
.join(', ');
return {
docs: documentation,
name: stringCamelCase(name.toString()),
params
};
});
return {
items,
name: stringCamelCase(name.toString())
};
});
const types = [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
@@ -78,25 +80,15 @@ function generateForMeta (registry: Registry, meta: Metadata, dest: string, extr
file: '@polkadot/api/types',
types: ['ApiTypes', 'SubmittableExtrinsic']
}
]);
const interfaceStart = [
"declare module '@polkadot/api/types/submittable' {",
indent(2)('export interface AugmentedSubmittables<ApiType> {\n')
].join('\n');
const interfaceEnd = `\n${indent(2)('}')}\n\n`;
const submittableExtrinsicsInterface = indent(2)('export interface SubmittableExtrinsics<ApiType extends ApiTypes> extends AugmentedSubmittables<ApiType> {')
.concat('\n')
.concat(indent(4)('(extrinsic: Call | Extrinsic | Uint8Array | string): SubmittableExtrinsic<ApiType>;\n'))
.concat(isStrict ? '' : indent(4)('[index: string]: SubmittableModuleExtrinsics<ApiType>;\n'))
.concat(indent(2)('}\n'));
];
return header
.concat(interfaceStart)
.concat(body.join('\n'))
.concat(interfaceEnd)
.concat(submittableExtrinsicsInterface)
.concat('}')
.concat(FOOTER);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types
});
});
}
+23
View File
@@ -0,0 +1,23 @@
{{> header }}
{{{ imports }}}
declare module '@polkadot/metadata/Decorated/consts/types' {
export interface Constants {
{{#unless isStrict}}
[index: string]: ModuleConstants;
{{/unless}}
{{#each modules}}
{{{name}}}: {
{{#unless @root.isStrict}}
[index: string]: AugmentedConst<object & Codec>;
{{/unless}}
{{~#each items}}
{{> docs}}
{{{name}}}: AugmentedConst<{{{type}}}>;
{{/each}}
};
{{/each}}
}
}
{{> footer }}
+7
View File
@@ -0,0 +1,7 @@
{{#if docs.length}}
/**
{{#each docs}}
* {{#trim}}{{{this}}}{{/trim}}
{{/each}}
**/
{{/if}}
@@ -0,0 +1,2 @@
// Auto-generated via `yarn polkadot-types-from-{{headerType}}`, do not edit
/* eslint-disable */
@@ -0,0 +1,12 @@
{{> header }}
{{{ imports }}}
declare module '@polkadot/types/types/registry' {
export interface InterfaceTypes {
{{#each items}}
{{{this}}}
{{/each}}
}
}
{{> footer }}
+26
View File
@@ -0,0 +1,26 @@
{{> header }}
{{{ imports }}}
declare module '@polkadot/api/types/storage' {
export interface AugmentedQueries<ApiType> {
{{#each modules}}
{{{name}}}: {
{{#unless @root.isStrict}}
[index: string]: QueryableStorageEntry<ApiType>;
{{/unless}}
{{~#each items}}
{{> docs}}
{{{name}}}: {{{entryType}}}<ApiType, ({{{args}}}) => Observable<{{{returnType}}}>>{{#unless @root.isStrict}} & QueryableStorageEntry<ApiType>{{/unless}};
{{/each}}
};
{{/each}}
}
export interface QueryableStorage<ApiType extends ApiTypes> extends AugmentedQueries<ApiType> {
{{#unless isStrict}}
[index: string]: QueryableModuleStorage<ApiType>;
{{/unless}}
}
}
{{> footer }}
+17
View File
@@ -0,0 +1,17 @@
{{> header }}
{{{ imports }}}
declare module '@polkadot/rpc-core/types.jsonrpc' {
export interface RpcInterface {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedRpc<{{#if generic}}<{{{generic}}}>{{/if}}({{{args}}}) => Observable<{{{type}}}>>;
{{/each}}
};
{{/each}}
}
}
{{> footer }}
@@ -0,0 +1,4 @@
{{> header }}
export * from './types';
{{> footer }}
@@ -0,0 +1,11 @@
{{> header }}
{{{ imports }}}
{{#each items}}
{{{this}}}
{{/each}}
export type PHANTOM_{{#upper}}{{{name}}}{{/upper}} = '{{{name}}}';
{{> footer }}
@@ -0,0 +1,6 @@
{{> header }}
{{#each items}}
export * from './{{{this}}}/types';
{{/each}}
{{> footer }}
+27
View File
@@ -0,0 +1,27 @@
{{> header }}
{{{ imports }}}
declare module '@polkadot/api/types/submittable' {
export interface AugmentedSubmittables<ApiType> {
{{#each modules}}
{{{name}}}: {
{{#unless @root.isStrict}}
[index: string]: SubmittableExtrinsicFunction<ApiType>;
{{/unless}}
{{~#each items}}
{{> docs}}
{{{name}}}: AugmentedSubmittable<({{{params}}}) => SubmittableExtrinsic<ApiType>>;
{{/each}}
};
{{/each}}
}
export interface SubmittableExtrinsics<ApiType extends ApiTypes> extends AugmentedSubmittables<ApiType> {
(extrinsic: Call | Extrinsic | Uint8Array | string): SubmittableExtrinsic<ApiType>;
{{#unless isStrict}}
[index: string]: SubmittableModuleExtrinsics<ApiType>;
{{/unless}}
}
}
{{> footer }}
+5 -17
View File
@@ -2,22 +2,10 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Text } from '@polkadot/types';
import Handlebars from 'handlebars';
import { indent } from './formatting';
import { readTemplate } from './file';
type AnyString = Text | string;
type Arg = [AnyString, AnyString];
export function createDocComments (spaces: number, docs: AnyString[], args: Arg[] = []): string {
const contents = [
...docs.map((doc): string => doc.toString().trim()).filter((doc): boolean => !!doc),
...args.map(([name, type]): string => `@param ${name} ${type}`)
].map((d): string => ` * ${d}`);
return contents.length
? ['/**', ...contents, ' **/\n']
.map((s): string => indent(spaces)(s))
.join('\n')
: '';
}
Handlebars.registerPartial({
docs: Handlebars.compile(readTemplate('docs'))
});
+4
View File
@@ -19,3 +19,7 @@ export function writeFile (dest: string, generator: () => string, noLog?: boolea
!noLog && console.log('');
}
export function readTemplate (path: string): string {
return fs.readFileSync(`${__dirname}/../templates/${path}.hbs`).toString();
}
@@ -1,11 +0,0 @@
// Copyright 2017-2020 @polkadot/typegen authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { indent } from './formatting';
describe('formatting', () => {
it('should indent correctly', () => {
expect(indent(2)('foo')).toBe(' foo');
});
});
+59 -54
View File
@@ -2,59 +2,74 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Handlebars from 'handlebars';
import { TypeDef, TypeDefInfo } from '@polkadot/types/create/types';
import { getTypeDef } from '@polkadot/types/create';
import { paramsNotation } from '@polkadot/types/codec/utils';
import { setImports, TypeImports } from './imports';
export const HEADER = (type: 'chain' | 'defs'): string => `// Auto-generated via \`yarn polkadot-types-from-${type}\`, do not edit\n/* eslint-disable */\n\n`;
export const FOOTER = '\n';
import { readTemplate } from './file';
const TYPES_NON_PRIMITIVE = ['Metadata'];
// creates the import lines
/** @internal */
export function createImportCode (header: string, imports: TypeImports, checks: { file: string; types: string[] }[]): string {
return [
{
file: '@polkadot/types/types',
types: Object.keys(imports.typesTypes)
},
{
file: '@polkadot/types/codec',
types: Object
.keys(imports.codecTypes)
.filter((name): boolean => name !== 'Tuple')
},
{
file: '@polkadot/types/extrinsic',
types: Object.keys(imports.extrinsicTypes)
},
{
file: '@polkadot/types/generic',
types: Object.keys(imports.genericTypes)
},
{
file: '@polkadot/types/primitive',
types: Object
.keys(imports.primitiveTypes)
.filter((name): boolean => !TYPES_NON_PRIMITIVE.includes(name))
},
{
file: '@polkadot/types',
types: Object
.keys(imports.primitiveTypes)
.filter((name): boolean => TYPES_NON_PRIMITIVE.includes(name))
},
...checks
].reduce((result, { file, types }): string => {
return types.length
? `${result}import { ${types.sort().join(', ')} } from '${file}';\n`
: result;
}, header) + '\n';
}
export const HEADER = (type: 'chain' | 'defs'): string => `// Auto-generated via \`yarn polkadot-types-from-${type}\`, do not edit\n/* eslint-disable */\n\n`;
Handlebars.registerPartial({
footer: Handlebars.compile(readTemplate('footer')),
header: Handlebars.compile(readTemplate('header'))
});
Handlebars.registerHelper({
imports () {
const { imports, types } = this as any;
return [
{
file: '@polkadot/types/types',
types: Object.keys(imports.typesTypes)
},
{
file: '@polkadot/types/codec',
types: Object
.keys(imports.codecTypes)
.filter((name): boolean => name !== 'Tuple')
},
{
file: '@polkadot/types/extrinsic',
types: Object.keys(imports.extrinsicTypes)
},
{
file: '@polkadot/types/generic',
types: Object.keys(imports.genericTypes)
},
{
file: '@polkadot/types/primitive',
types: Object
.keys(imports.primitiveTypes)
.filter((name): boolean => !TYPES_NON_PRIMITIVE.includes(name))
},
{
file: '@polkadot/types',
types: Object
.keys(imports.primitiveTypes)
.filter((name): boolean => TYPES_NON_PRIMITIVE.includes(name))
},
...types
].reduce((result, { file, types }): string => {
return types.length
? `${result}import { ${types.sort().join(', ')} } from '${file}';\n`
: result;
}, '');
},
trim (options) {
return options.fn(this).trim();
},
upper (options) {
return options.fn(this).toUpperCase();
}
});
// helper to generate a `export interface <Name> extends <Base> {<Body>}
/** @internal */
@@ -273,13 +288,3 @@ export function formatType (definitions: object, type: string | TypeDef, imports
}
}
}
/**
* Indent a string with `n` spaces before.
*/
/** @internal */
export function indent (n: number, char = ' '): (str: string) => string {
return function (str: string): string {
return `${char.repeat(n)}${str}`;
};
}
+3
View File
@@ -8,3 +8,6 @@ export * from './file';
export * from './formatting';
export * from './imports';
export * from './register';
export const compareName = (a: { name: { toString(): string } }, b: { name: { toString(): string } }): number =>
a.name.toString().localeCompare(b.name.toString());