solidity-docgen
Version:
Solidity API documentation automatic generator.
427 lines • 14.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceContract = exports.Source = void 0;
const lodash_1 = require("lodash");
const path_1 = __importDefault(require("path"));
const memoize_1 = require("./memoize");
const handlebars_1 = require("./handlebars");
class Source {
constructor(contractsDir, solcOutput, contractTemplate) {
this.contractsDir = contractsDir;
this.solcOutput = solcOutput;
this.contractTemplate = contractTemplate;
}
get contracts() {
return lodash_1.flatten(this.files.map(file => file.contracts));
}
get files() {
return Object.keys(this.solcOutput.sources)
.map(fileName => this.file(fileName));
}
file(fileName) {
return new SourceFile(this, this.solcOutput.sources[fileName].ast, path_1.default.relative(this.contractsDir, fileName));
}
fileById(id) {
const file = this.files.find(f => f.astId === id);
if (file === undefined) {
throw new Error(`File with id ${id} not found`);
}
return file;
}
contractById(id) {
const contract = this.contracts.find(c => c.astId === id);
if (contract === undefined) {
throw new Error(`Contract with id ${id} not found`);
}
return contract;
}
}
__decorate([
memoize_1.memoize
], Source.prototype, "file", null);
exports.Source = Source;
class SourceFile {
constructor(source, ast, path) {
this.source = source;
this.ast = ast;
this.path = path;
}
get contracts() {
const astNodes = this.ast.nodes.filter(isContractDefinition);
return astNodes.map(node => new SourceContract(this.source, this, node));
}
get contractsInScope() {
var _a;
const scope = {};
for (const c of this.contracts) {
scope[c.name] = c;
}
const imports = this.ast.nodes.filter(isImportDirective);
for (const i of imports) {
const importedFile = this.source.fileById(i.sourceUnit);
if (i.symbolAliases.length === 0) {
Object.assign(scope, importedFile.contractsInScope);
}
else {
for (const a of i.symbolAliases) {
scope[(_a = a.local) !== null && _a !== void 0 ? _a : a.foreign.name] = importedFile.contractsInScope[a.foreign.name];
}
}
}
;
return scope;
}
get astId() {
return this.ast.id;
}
}
__decorate([
memoize_1.memoize
], SourceFile.prototype, "contracts", null);
__decorate([
memoize_1.memoize
], SourceFile.prototype, "contractsInScope", null);
class SourceContract {
constructor(source, file, astNode) {
this.source = source;
this.file = file;
this.astNode = astNode;
}
toString() {
return this.source.contractTemplate(this);
}
get name() {
return this.astNode.name;
}
get fullName() {
return this.name;
}
get anchor() {
return this.name;
}
get linkable() {
return [this, ...this.ownModifiers, ...this.ownVariables, ...this.ownFunctions, ...this.ownEvents];
}
get inheritance() {
return this.astNode.linearizedBaseContracts.map(id => this.source.contractById(id));
}
get variables() {
return lodash_1.flatten(this.inheritance.map(c => c.ownVariables));
}
get ownVariables() {
return this.astNode.nodes
.filter(isVariableDeclaration)
.filter(n => n.visibility !== 'private')
.map(n => new SourceStateVariable(this, n));
}
get functions() {
return lodash_1.uniqBy(lodash_1.flatten(this.inheritance.map(c => c.ownFunctions)), f => f.name === 'constructor' ? 'constructor' : f.signature);
}
get ownFunctions() {
return this.astNode.nodes
.filter(isFunctionDefinition)
.filter(n => n.visibility !== 'private')
.map(n => new SourceFunction(this, n))
.filter(f => !f.isTrivialConstructor);
}
get privateFunctions() {
return this.astNode.nodes
.filter(isFunctionDefinition)
.filter(n => n.visibility === 'private')
.map(n => new SourceFunction(this, n));
}
get inheritedItems() {
const variables = lodash_1.groupBy(this.variables, f => f.contract.astId);
const functions = lodash_1.groupBy(this.functions, f => f.contract.astId);
const events = lodash_1.groupBy(this.events, f => f.contract.astId);
const modifiers = lodash_1.groupBy(this.modifiers, f => f.contract.astId);
return this.inheritance.map(contract => ({
contract,
variables: variables[contract.astId],
functions: functions[contract.astId],
events: events[contract.astId],
modifiers: modifiers[contract.astId],
}));
}
get events() {
return lodash_1.uniqBy(lodash_1.flatten(this.inheritance.map(c => c.ownEvents)), f => f.signature);
}
get ownEvents() {
return this.astNode.nodes
.filter(isEventDefinition)
.map(n => new SourceEvent(this, n));
}
get modifiers() {
return lodash_1.uniqBy(lodash_1.flatten(this.inheritance.map(c => c.ownModifiers)), f => f.signature);
}
get ownModifiers() {
return this.astNode.nodes
.filter(isModifierDefinition)
.map(n => new SourceModifier(this, n));
}
get natspec() {
if (this.astNode.documentation === null || this.astNode.documentation === undefined) {
return {};
}
return parseNatSpec(this.astNode.documentation, this);
}
get astId() {
return this.astNode.id;
}
}
__decorate([
memoize_1.memoize
], SourceContract.prototype, "ownVariables", null);
__decorate([
memoize_1.memoize
], SourceContract.prototype, "ownFunctions", null);
__decorate([
memoize_1.memoize
], SourceContract.prototype, "privateFunctions", null);
__decorate([
memoize_1.memoize
], SourceContract.prototype, "ownEvents", null);
__decorate([
memoize_1.memoize
], SourceContract.prototype, "ownModifiers", null);
__decorate([
memoize_1.memoize
], SourceContract.prototype, "natspec", null);
exports.SourceContract = SourceContract;
class SourceContractItem {
constructor(contract) {
this.contract = contract;
}
get name() {
return this.astNode.name;
}
get fullName() {
return `${this.contract.name}.${this.name}`;
}
get anchor() {
return `${this.contract.name}-${handlebars_1.slug(this.signature)}`;
}
get args() {
return SourceTypedVariableArray.fromParameterList(this.astNode.parameters);
}
get signature() {
return `${this.name}(${this.args.map(a => a.type).join(',')})`;
}
get natspec() {
if (this.astNode.documentation === null || this.astNode.documentation === undefined) {
return {};
}
return parseNatSpec(this.astNode.documentation, this);
}
}
__decorate([
memoize_1.memoize
], SourceContractItem.prototype, "args", null);
__decorate([
memoize_1.memoize
], SourceContractItem.prototype, "natspec", null);
class SourceStateVariable {
constructor(contract, astNode) {
this.contract = contract;
this.astNode = astNode;
}
get name() {
return this.astNode.name;
}
get fullName() {
return `${this.contract.name}.${this.name}`;
}
get anchor() {
return `${this.contract.name}-${this.name}-${handlebars_1.slug(this.type)}`;
}
get type() {
return this.astNode.typeName.typeDescriptions.typeString;
}
get signature() {
return `${this.type} ${this.name}`;
}
get natspec() {
warnStateVariableNatspec();
return {};
}
}
class SourceFunction extends SourceContractItem {
constructor(contract, astNode) {
super(contract);
this.astNode = astNode;
}
get name() {
const { name, kind } = this.astNode;
const isRegularFunction = kind === 'function';
return isRegularFunction ? name : kind;
}
get outputs() {
return SourceTypedVariableArray.fromParameterList(this.astNode.returnParameters);
}
get visibility() {
return this.astNode.visibility;
}
get isTrivialConstructor() {
return (this.astNode.kind === "constructor" &&
this.visibility === "public" &&
this.args.length === 0 &&
Object.keys(this.natspec).length === 0);
}
}
__decorate([
memoize_1.memoize
], SourceFunction.prototype, "outputs", null);
class SourceEvent extends SourceContractItem {
constructor(contract, astNode) {
super(contract);
this.astNode = astNode;
}
}
class SourceModifier extends SourceContractItem {
constructor(contract, astNode) {
super(contract);
this.astNode = astNode;
}
}
class SourceTypedVariable {
constructor(typeNode, name) {
this.typeNode = typeNode;
this.name = name;
}
get type() {
return this.typeNode.typeDescriptions.typeString;
}
// TODO: deprecate
get typeName() {
return this.type;
}
toString() {
if (this.name) {
return [this.type, this.name].join(' ');
}
else {
return this.type;
}
}
}
class PrettyArray extends Array {
toString() {
return this.map(e => e.toString()).join(', ');
}
}
class SourceTypedVariableArray extends PrettyArray {
static fromParameterList(parameters) {
return SourceTypedVariableArray.from(parameters.parameters.map(p => new SourceTypedVariable(p.typeName, p.name || undefined)));
}
get types() {
return this.map(v => v.type);
}
get names() {
return this.map(v => (v.name === undefined) ? '_' : v.name);
}
}
function parseNatSpec(doc, context) {
var _a, _b, _c, _d, _e, _f;
var _g;
const res = {};
const tagMatches = execall(/^(?:@(\w+|custom:[a-z][a-z-]*) )?((?:(?!^@(?:\w+|custom:[a-z][a-z-]*) )[^])*)/m, doc);
let inheritFrom;
for (const [, tag, content] of tagMatches) {
if (tag === 'dev') {
(_a = res.devdoc) !== null && _a !== void 0 ? _a : (res.devdoc = '');
res.devdoc += content;
}
if (tag === 'notice' || tag === undefined) {
(_b = res.userdoc) !== null && _b !== void 0 ? _b : (res.userdoc = '');
res.userdoc += content;
}
if (tag === 'title') {
res.title = content;
}
if (tag === 'param') {
const paramMatches = content.match(/(\w+) ([^]*)/);
if (paramMatches) {
const [, param, description] = paramMatches;
(_c = res.params) !== null && _c !== void 0 ? _c : (res.params = []);
res.params.push({ param, description });
}
}
if (tag === 'return') {
const paramMatches = content.match(/(\w+) ([^]*)/);
if (paramMatches) {
const [, param, description] = paramMatches;
(_d = res.returns) !== null && _d !== void 0 ? _d : (res.returns = []);
res.returns.push({ param, description });
}
}
if (tag === 'inheritdoc') {
if (!(context instanceof SourceFunction)) {
throw new Error('@inheritdoc only supported in functions');
}
const parentContract = context.contract.file.contractsInScope[content.trim()];
inheritFrom = parentContract.functions.find(f => f.name === context.name);
}
if (tag === null || tag === void 0 ? void 0 : tag.startsWith('custom:')) {
const key = tag.replace(/^custom:/, '');
(_e = res.custom) !== null && _e !== void 0 ? _e : (res.custom = {});
(_f = (_g = res.custom)[key]) !== null && _f !== void 0 ? _f : (_g[key] = '');
res.custom[key] += content;
}
}
if (inheritFrom) {
lodash_1.defaults(res, inheritFrom.natspec);
}
return res;
}
function* execall(re, text) {
re = new RegExp(re, re.flags + (re.sticky ? '' : 'y'));
while (true) {
const match = re.exec(text);
// we break out of the loop if the empty string is matched because no
// progress will be made and it will loop infinitely
if (match && match[0] !== '') {
yield match;
}
else {
break;
}
}
}
function isVariableDeclaration(node) {
return node.nodeType === 'VariableDeclaration';
}
function isFunctionDefinition(node) {
return node.nodeType === 'FunctionDefinition';
}
function isEventDefinition(node) {
return node.nodeType === 'EventDefinition';
}
function isModifierDefinition(node) {
return node.nodeType === 'ModifierDefinition';
}
function isContractDefinition(node) {
return node.nodeType === 'ContractDefinition';
}
function isImportDirective(node) {
return node.nodeType === 'ImportDirective';
}
function oneTimeLogger(msg) {
let warned = false;
return function () {
if (!warned) {
console.warn(msg);
warned = true;
}
};
}
const warnStateVariableNatspec = oneTimeLogger('Warning: NatSpec is currently not available for state variables.');
//# sourceMappingURL=source.js.map