From 1b1d3a297cc28771eb968e1dac1c2a93e2c530b0 Mon Sep 17 00:00:00 2001 From: Maciej Hirsz Date: Thu, 2 Feb 2017 17:24:15 +0100 Subject: [PATCH] Perform a sync between Rust and JS when generating markdown instead of in spec tests (#4408) * Sync RPC on npm run build:markdown * Helpers * Added GPL headers --- js/scripts/build-rpc-markdown.js | 31 +++++----- js/scripts/helpers/log.js | 32 ++++++++++ js/scripts/helpers/parsed-rpc-traits.js | 81 +++++++++++++++++++++++++ js/src/jsonrpc/index.spec.js | 80 ------------------------ 4 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 js/scripts/helpers/log.js create mode 100644 js/scripts/helpers/parsed-rpc-traits.js diff --git a/js/scripts/build-rpc-markdown.js b/js/scripts/build-rpc-markdown.js index a06f2ed49..2407cf208 100644 --- a/js/scripts/build-rpc-markdown.js +++ b/js/scripts/build-rpc-markdown.js @@ -16,11 +16,12 @@ import fs from 'fs'; import path from 'path'; -import chalk from 'chalk'; import { isPlainObject } from 'lodash'; +import { info, warn, error } from './helpers/log'; import { Dummy } from '../src/jsonrpc/helpers'; import interfaces from '../src/jsonrpc'; +import rustMethods from './helpers/parsed-rpc-traits'; const ROOT_DIR = path.join(__dirname, '../docs'); @@ -28,20 +29,13 @@ if (!fs.existsSync(ROOT_DIR)) { fs.mkdirSync(ROOT_DIR); } -// INFO Logging helper -function info (log) { - console.log(chalk.blue(`INFO:\t${log}`)); -} - -// WARN Logging helper -function warn (log) { - console.warn(chalk.yellow(`WARN:\t${log}`)); -} - -// ERROR Logging helper -function error (log) { - console.error(chalk.red(`ERROR:\t${log}`)); -} +Object.keys(rustMethods).forEach((group) => { + Object.keys(rustMethods[group]).forEach((method) => { + if (interfaces[group] == null || interfaces[group][method] == null) { + error(`${group}_${method} is defined in Rust traits, but not in js/src/jsonrpc/interfaces`); + } + }); +}); function printType (type) { return type.print || `\`${type.name}\``; @@ -291,7 +285,8 @@ Object.keys(interfaces).sort().forEach((group) => { Object.keys(spec).sort(methodComparator).forEach((iname) => { const method = spec[iname]; - const name = `${group.replace(/_.*$/, '')}_${iname}`; + const groupName = group.replace(/_.*$/, ''); + const name = `${groupName}_${iname}`; if (method.nodoc || method.deprecated) { info(`Skipping ${name}: ${method.nodoc || 'Deprecated'}`); @@ -299,6 +294,10 @@ Object.keys(interfaces).sort().forEach((group) => { return; } + if (rustMethods[groupName] == null || rustMethods[groupName][iname] == null) { + error(`${name} is defined in js/src/jsonrpc/interfaces, but not in Rust traits`); + } + const desc = method.desc; const params = buildParameters(method.params); const returns = `- ${formatType(method.returns)}`; diff --git a/js/scripts/helpers/log.js b/js/scripts/helpers/log.js new file mode 100644 index 000000000..dc4955649 --- /dev/null +++ b/js/scripts/helpers/log.js @@ -0,0 +1,32 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import chalk from 'chalk'; + +// INFO Logging helper +export function info (log) { + console.log(chalk.blue(`INFO:\t${log}`)); +} + +// WARN Logging helper +export function warn (log) { + console.warn(chalk.yellow(`WARN:\t${log}`)); +} + +// ERROR Logging helper +export function error (log) { + console.error(chalk.red(`ERROR:\t${log}`)); +} diff --git a/js/scripts/helpers/parsed-rpc-traits.js b/js/scripts/helpers/parsed-rpc-traits.js new file mode 100644 index 000000000..9f2d4ea8a --- /dev/null +++ b/js/scripts/helpers/parsed-rpc-traits.js @@ -0,0 +1,81 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import fs from 'fs'; +import path from 'path'; + +// ```js +// rustMethods['eth']['call'] === true +// ``` +const rustMethods = {}; + +export default rustMethods; + +// Get a list of JSON-RPC from Rust trait source code +function parseMethodsFromRust (source) { + // Matching the custom `rpc` attribute with it's doc comment + const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm; + const commentPattern = /\s*\/\/\/\s*/g; + const separatorPattern = /\s*,\s*/g; + const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/; + const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i; + + const methods = []; + + source.toString().replace(attributePattern, (match, comment, props) => { + comment = comment.replace(commentPattern, '\n').trim(); + + // Skip deprecated methods + if (ignorePattern.test(comment)) { + return match; + } + + props.split(separatorPattern).forEach((prop) => { + const [, key, value] = prop.split(assignPattern) || []; + + if (key === 'name' && value != null) { + methods.push(value); + } + }); + + return match; + }); + + return methods; +} + +// Get a list of all JSON-RPC methods from all defined traits +function getMethodsFromRustTraits () { + const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits'); + + return fs.readdirSync(traitsDir) + .filter((name) => name !== 'mod.rs' && /\.rs$/.test(name)) + .map((name) => fs.readFileSync(path.join(traitsDir, name))) + .map(parseMethodsFromRust) + .reduce((a, b) => a.concat(b)); +} + +getMethodsFromRustTraits().sort().forEach((method) => { + const [group, name] = method.split('_'); + + // Skip methods with malformed names + if (group == null || name == null) { + return; + } + + rustMethods[group] = rustMethods[group] || {}; + rustMethods[group][name] = true; +}); diff --git a/js/src/jsonrpc/index.spec.js b/js/src/jsonrpc/index.spec.js index 66988b664..af4afb9c9 100644 --- a/js/src/jsonrpc/index.spec.js +++ b/js/src/jsonrpc/index.spec.js @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import fs from 'fs'; -import path from 'path'; import interfaces from './'; import * as customTypes from './types'; @@ -27,91 +25,13 @@ function verifyType (obj) { } } -// Get a list of JSON-RPC from Rust trait source code -function parseMethodsFromRust (source) { - // Matching the custom `rpc` attribute with it's doc comment - const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm; - const commentPattern = /\s*\/\/\/\s*/g; - const separatorPattern = /\s*,\s*/g; - const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/; - const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i; - - const methods = []; - - source.toString().replace(attributePattern, (match, comment, props) => { - comment = comment.replace(commentPattern, '\n').trim(); - - // Skip deprecated methods - if (ignorePattern.test(comment)) { - return match; - } - - props.split(separatorPattern).forEach((prop) => { - const [, key, value] = prop.split(assignPattern) || []; - - if (key === 'name' && value != null) { - methods.push(value); - } - }); - - return match; - }); - - return methods; -} - -// Get a list of all JSON-RPC methods from all defined traits -function getMethodsFromRustTraits () { - const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits'); - - return fs.readdirSync(traitsDir) - .filter((name) => name !== 'mod.rs' && /\.rs$/.test(name)) - .map((name) => fs.readFileSync(path.join(traitsDir, name))) - .map(parseMethodsFromRust) - .reduce((a, b) => a.concat(b)); -} - -const rustMethods = {}; - -getMethodsFromRustTraits().sort().forEach((method) => { - const [group, name] = method.split('_'); - - // Skip methods with malformed names - if (group == null || name == null) { - return; - } - - rustMethods[group] = rustMethods[group] || {}; - rustMethods[group][name] = true; -}); - describe('jsonrpc/interfaces', () => { - describe('Rust trait methods', () => { - Object.keys(rustMethods).forEach((group) => { - describe(group, () => { - Object.keys(rustMethods[group]).forEach((name) => { - describe(name, () => { - it('has a defined JS interface', () => { - expect(interfaces[group][name]).to.exist; - }); - }); - }); - }); - }); - }); - Object.keys(interfaces).forEach((group) => { describe(group, () => { Object.keys(interfaces[group]).forEach((name) => { const method = interfaces[group][name]; describe(name, () => { - if (!method.nodoc) { - it('is present in Rust codebase', () => { - expect(rustMethods[group][name]).to.exist; - }); - } - it('has the correct interface', () => { expect(method.desc).to.be.a('string'); expect(method.params).to.be.an('array');