solidity-docgen
solidity-docgen copied to clipboard
Provide helpers to list inherited functions (and other items)
I can't find a simple way of retrieving all the methods available in a contract, including inherited ones.
I'm currently working on a helper method that iterates over all linearizedBaseContracts
, retrieves their methods and merges them. But I need to take into account visibility, overrides... it starts to look like a lot of work and I kind of think such a basic thing should be available out of the box.
What am I missing?
You're right this should be available out of the box, there is quite a bit of complexity. I've implemented this for OpenZeppelin Contracts so you can reuse that but adding this in your templates/properties.js
(or wherever you have your templates):
module.exports.inheritance = function ({ item, build }) {
if (!isNodeType('ContractDefinition', item)) {
throw new Error('used inherited-items on non-contract');
}
return item.linearizedBaseContracts
.map(id => build.deref('ContractDefinition', id))
.filter((c, i) => c.name !== 'Context' || i === 0);
};
module.exports['inherited-functions'] = function ({ item }) {
const { inheritance } = item;
const baseFunctions = new Set(inheritance.flatMap(c => c.functions.flatMap(f => f.baseFunctions ?? [])));
return inheritance.map((contract, i) => ({
contract,
functions: contract.functions.filter(f => !baseFunctions.has(f.id) && (f.name !== 'constructor' || i === 0)),
}));
};
Later use it in the contract.hbs
template like {{#each inherited-functions}} ...
.
Thanks a lot for your answer! I've been tinkering all day (it's my first serious contact with TypeScript) and this is what I came up with:
export function allItems(this: DocItemWithContext, nodeTypeName: string) {
if (this.nodeType == 'ContractDefinition') {
const { deref } = this.__item_context.build;
const parents = this.linearizedBaseContracts.map(deref('ContractDefinition'));
let items: (EnumDefinition | ErrorDefinition | EventDefinition | FunctionDefinition | ModifierDefinition
| StructDefinition | UserDefinedValueTypeDefinition | VariableDeclaration)[] = [];
parents.forEach(p => {
p.nodes.forEach(n => {
// Filter out other types
if (n.nodeType == 'UsingForDirective' || n.nodeType != nodeTypeName) return;
// Filter out private fields
if ((n.nodeType == 'VariableDeclaration' || n.nodeType == 'FunctionDefinition') &&
(n.visibility != 'public' && n.visibility != 'external')) return;
if (n.nodeType == 'FunctionDefinition' && n.virtual) return;
// If this item already exists do not add it again.
// linearizedBaseContracts returned the children first and then the parents, so if the item
// already exists it means that it is an override, and we want to keep those (if they had any docs).
const prev = items.find(i => i.name == n.name);
const prevDocs = prev && (
prev.nodeType == 'ErrorDefinition' ||
prev.nodeType == 'EventDefinition' ||
prev.nodeType == 'FunctionDefinition') ? prev.documentation : null;
if (!prev || !prevDocs)
items.push(n);
});
});
items.sort((a, b) => a.name < b.name ? -1 : 1);
return items;
}
}
I've added this to helpers.ts
and I use it with {{#each (allItems typeName)}}
. I'm sure this stinks, I better study your code!