From 0c33a3cee4eddfdd60e7cac6c30534a73b3f3989 Mon Sep 17 00:00:00 2001 From: Mariano Aguero Date: Fri, 14 Dec 2018 09:58:49 -0300 Subject: [PATCH 1/8] Add rulesets --- conf/rulesets/solhint-all.js | 12 ++ conf/rulesets/solhint-default.js | 12 ++ conf/rulesets/solhint-recommended.js | 12 ++ lib/common/ajv.js | 15 ++ lib/common/errors.js | 11 ++ lib/common/utils.js | 45 ++++-- lib/config/config-file.js | 85 ++++++++++++ lib/config/config-schema.js | 16 +++ lib/config/config-validator.js | 119 ++++++++++++++++ lib/constants.js | 6 - lib/load-rules.js | 67 +++++++++ lib/rules/align/array-declaration-spaces.js | 18 ++- lib/rules/align/bracket-align.js | 19 ++- lib/rules/align/expression-indent.js | 18 ++- lib/rules/align/indent.js | 28 +++- lib/rules/align/no-mix-tabs-and-spaces.js | 20 ++- lib/rules/align/no-spaces-before-semicolon.js | 18 ++- lib/rules/align/space-after-comma.js | 18 ++- lib/rules/align/statement-indent.js | 18 ++- lib/rules/base-checker.js | 3 +- lib/rules/best-practises/code-complexity.js | 27 +++- .../best-practises/function-max-lines.js | 28 +++- lib/rules/best-practises/max-line-length.js | 27 +++- lib/rules/best-practises/max-states-count.js | 28 +++- lib/rules/best-practises/no-empty-blocks.js | 18 ++- lib/rules/best-practises/no-unused-vars.js | 18 ++- lib/rules/best-practises/payable-fallback.js | 18 ++- lib/rules/deprecations/base-deprecation.js | 4 +- lib/rules/deprecations/constructor-syntax.js | 18 ++- lib/rules/index.js | 15 +- lib/rules/miscellaneous/prettier.js | 19 ++- lib/rules/miscellaneous/quotes.js | 33 ++++- lib/rules/naming/const-name-snakecase.js | 18 ++- lib/rules/naming/contract-name-camelcase.js | 18 ++- lib/rules/naming/event-name-camelcase.js | 18 ++- lib/rules/naming/func-name-mixedcase.js | 18 ++- lib/rules/naming/func-param-name-mixedcase.js | 18 ++- lib/rules/naming/modifier-name-mixedcase.js | 18 ++- lib/rules/naming/use-forbidden-name.js | 18 ++- lib/rules/naming/var-name-mixedcase.js | 18 ++- lib/rules/order/func-order.js | 18 ++- lib/rules/order/imports-on-top.js | 18 ++- .../order/separate-by-one-line-in-contract.js | 18 ++- .../order/two-lines-top-level-separator.js | 18 ++- lib/rules/order/visibility-modifier-order.js | 18 ++- lib/rules/security/avoid-call-value.js | 18 ++- lib/rules/security/avoid-low-level-calls.js | 18 ++- lib/rules/security/avoid-sha3.js | 18 ++- lib/rules/security/avoid-suicide.js | 18 ++- lib/rules/security/avoid-throw.js | 18 ++- lib/rules/security/avoid-tx-origin.js | 18 ++- lib/rules/security/check-send-result.js | 18 ++- lib/rules/security/compiler-fixed.js | 18 ++- lib/rules/security/compiler-gt-0_4.js | 18 ++- lib/rules/security/func-visibility.js | 18 ++- lib/rules/security/mark-callable-contracts.js | 19 ++- lib/rules/security/multiple-sends.js | 18 ++- lib/rules/security/no-complex-fallback.js | 18 ++- lib/rules/security/no-inline-assembly.js | 18 ++- .../security/no-simple-event-func-name.js | 18 ++- lib/rules/security/not-rely-on-block-hash.js | 18 ++- lib/rules/security/not-rely-on-time.js | 18 ++- lib/rules/security/reentrancy.js | 18 ++- lib/rules/security/state-visibility.js | 18 ++- package-lock.json | 128 +++++++++++++++--- package.json | 4 +- solhint.js | 21 ++- test/common/config-validator.js | 52 +++++++ test/common/load-rules.js | 41 ++++++ 69 files changed, 1527 insertions(+), 109 deletions(-) create mode 100644 conf/rulesets/solhint-all.js create mode 100644 conf/rulesets/solhint-default.js create mode 100644 conf/rulesets/solhint-recommended.js create mode 100644 lib/common/ajv.js create mode 100644 lib/common/errors.js create mode 100644 lib/config/config-file.js create mode 100644 lib/config/config-schema.js create mode 100644 lib/config/config-validator.js delete mode 100644 lib/constants.js create mode 100644 lib/load-rules.js create mode 100644 test/common/config-validator.js create mode 100644 test/common/load-rules.js diff --git a/conf/rulesets/solhint-all.js b/conf/rulesets/solhint-all.js new file mode 100644 index 00000000..b13589f7 --- /dev/null +++ b/conf/rulesets/solhint-all.js @@ -0,0 +1,12 @@ +const { loadRules } = require('../../lib/load-rules') + +const rulesConstants = loadRules() +const enabledRules = {} + +rulesConstants.forEach(rule => { + if (!rule.meta.deprecated) { + enabledRules[rule.ruleId] = rule.meta.defaultSetup + } +}) + +module.exports = { rules: enabledRules } diff --git a/conf/rulesets/solhint-default.js b/conf/rulesets/solhint-default.js new file mode 100644 index 00000000..ff7e2c47 --- /dev/null +++ b/conf/rulesets/solhint-default.js @@ -0,0 +1,12 @@ +const { loadRules } = require('../../lib/load-rules') + +const rulesConstants = loadRules() +const enabledRules = {} + +rulesConstants.forEach(rule => { + if (!rule.meta.deprecated && rule.meta.isDefault) { + enabledRules[rule.ruleId] = rule.meta.defaultSetup + } +}) + +module.exports = { rules: enabledRules } diff --git a/conf/rulesets/solhint-recommended.js b/conf/rulesets/solhint-recommended.js new file mode 100644 index 00000000..19a0307c --- /dev/null +++ b/conf/rulesets/solhint-recommended.js @@ -0,0 +1,12 @@ +const { loadRules } = require('../../lib/load-rules') + +const rulesConstants = loadRules() +const enabledRules = {} + +rulesConstants.forEach(rule => { + if (!rule.meta.deprecated && rule.meta.recommended) { + enabledRules[rule.ruleId] = rule.meta.defaultSetup + } +}) + +module.exports = { rules: enabledRules } diff --git a/lib/common/ajv.js b/lib/common/ajv.js new file mode 100644 index 00000000..cb98ce5a --- /dev/null +++ b/lib/common/ajv.js @@ -0,0 +1,15 @@ +const Ajv = require('ajv') +const metaSchema = require('ajv/lib/refs/json-schema-draft-04.json') + +const ajv = new Ajv({ + meta: false, + validateSchema: false, + missingRefs: 'ignore', + verbose: true, + schemaId: 'auto' +}) + +ajv.addMetaSchema(metaSchema) +ajv._opts.defaultMeta = metaSchema.id + +module.exports = ajv diff --git a/lib/common/errors.js b/lib/common/errors.js new file mode 100644 index 00000000..b8f16600 --- /dev/null +++ b/lib/common/errors.js @@ -0,0 +1,11 @@ +class ConfigMissingError extends Error { + constructor(data) { + const { configName } = data + const message = `Failed to load config "${configName}" to extend from.` + super(message) + } +} + +module.exports = { + ConfigMissingError +} diff --git a/lib/common/utils.js b/lib/common/utils.js index 8d59ea8b..02ae5ede 100644 --- a/lib/common/utils.js +++ b/lib/common/utils.js @@ -1,18 +1,33 @@ -module.exports = { - getLocFromIndex(text, index) { - let line = 1 - let column = 0 - let i = 0 - while (i < index) { - if (text[i] === '\n') { - line++ - column = 0 - } else { - column++ - } - i++ - } +const fs = require('fs') +const path = require('path') - return { line, column } +const getLocFromIndex = (text, index) => { + let line = 1 + let column = 0 + let i = 0 + while (i < index) { + if (text[i] === '\n') { + line++ + column = 0 + } else { + column++ + } + i++ } + + return { line, column } +} + +const walkSync = (dir, filelist = []) => { + fs.readdirSync(dir).forEach(file => { + filelist = fs.statSync(path.join(dir, file)).isDirectory() + ? walkSync(path.join(dir, file), filelist) + : filelist.concat(path.join(dir, file)) + }) + return filelist +} + +module.exports = { + getLocFromIndex, + walkSync } diff --git a/lib/config/config-file.js b/lib/config/config-file.js new file mode 100644 index 00000000..d529e104 --- /dev/null +++ b/lib/config/config-file.js @@ -0,0 +1,85 @@ +const path = require('path') +const fs = require('fs') +const _ = require('lodash') +const cosmiconfig = require('cosmiconfig') +const { ConfigMissingError } = require('../common/errors') +const packageJson = require('../../package.json') + +const getSolhintCoreConfigPath = name => { + if (name === 'solhint:recommended') { + return path.resolve(__dirname, '../../conf/rulesets/solhint-recommended.js') + } + + if (name === 'solhint:all') { + return path.resolve(__dirname, '../../conf/rulesets/solhint-all.js') + } + + if (name === 'solhint:default') { + return path.resolve(__dirname, '../../conf/rulesets/solhint-default.js') + } + + throw new ConfigMissingError(name) +} + +const createEmptyConfig = () => ({ + excludedFiles: {}, + extends: {}, + globals: {}, + env: {}, + rules: {}, + parserOptions: {} +}) + +const loadConfig = () => { + // Use cosmiconfig to get the config from different sources + const appDirectory = fs.realpathSync(process.cwd()) + const moduleName = packageJson.name + const cosmiconfigOptions = { + searchPlaces: [ + 'package.json', + `.${moduleName}.json`, + `.${moduleName}rc`, + `.${moduleName}rc.json`, + `.${moduleName}rc.yaml`, + `.${moduleName}rc.yml`, + `.${moduleName}rc.js`, + `${moduleName}.config.js` + ] + } + + const explorer = cosmiconfig(moduleName, cosmiconfigOptions) + const searchedFor = explorer.searchSync(appDirectory) + return searchedFor.config || createEmptyConfig() +} + +const applyExtends = config => { + let configExtends = config.extends + + // normalize into an array for easier handling + if (!Array.isArray(config.extends)) { + configExtends = [config.extends] + } + + return configExtends.reduceRight((previousValue, parentPath) => { + try { + let extensionPath + + if (parentPath.startsWith('solhint:')) { + extensionPath = getSolhintCoreConfigPath(parentPath) + } else { + // Load packages with rules + extensionPath = `solhint-config-${parentPath}` + } + + const extensionConfig = require(extensionPath) + return _.merge(config, extensionConfig) + } catch (e) { + throw new ConfigMissingError(parentPath) + } + }, config) +} + +module.exports = { + applyExtends, + loadConfig +} diff --git a/lib/config/config-schema.js b/lib/config/config-schema.js new file mode 100644 index 00000000..bd3cdd89 --- /dev/null +++ b/lib/config/config-schema.js @@ -0,0 +1,16 @@ +const baseConfigProperties = { + rules: { type: 'object' }, + excludedFiles: { type: 'array' }, + extends: { type: 'array' }, + globals: { type: 'object' }, + env: { type: 'object' }, + parserOptions: { type: 'object' } +} + +const configSchema = { + type: 'object', + properties: baseConfigProperties, + additionalProperties: false +} + +module.exports = configSchema diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js new file mode 100644 index 00000000..9d1b30d3 --- /dev/null +++ b/lib/config/config-validator.js @@ -0,0 +1,119 @@ +const _ = require('lodash') +const ajv = require('../common/ajv') +const configSchema = require('./config-schema') +const { loadRule } = require('../load-rules') + +let validateSchema + +const validSeverityMap = ['error', 'warn'] + +const invalidSeverityMap = ['off'] + +const defaultSchemaValueForRules = Object.freeze({ + oneOf: [{ type: 'string', enum: [...validSeverityMap, ...invalidSeverityMap] }, { const: false }] +}) + +const validateRules = rulesConfig => { + if (!rulesConfig) { + return + } + + const errorsSchema = [] + const errorsRules = [] + const rulesConfigKeys = Object.keys(rulesConfig) + + for (const ruleId of rulesConfigKeys) { + const ruleInstance = loadRule(ruleId) + const ruleValue = rulesConfig[ruleId] + + if (ruleInstance === undefined) { + errorsRules.push(ruleId) + continue + } + + // Inject default schema + if (ruleInstance.meta.schema.length) { + let i + for (i = 0; i < ruleInstance.meta.schema.length; i++) { + const schema = ruleInstance.meta.schema[i] + if (schema.type === 'array') { + ruleInstance.meta.schema[i] = _.cloneDeep(defaultSchemaValueForRules) + ruleInstance.meta.schema[i].oneOf.push(schema) + ruleInstance.meta.schema[i].oneOf[2].items.unshift(defaultSchemaValueForRules) + } + } + } else { + ruleInstance.meta.schema.push(defaultSchemaValueForRules) + } + + // Validate rule schema + validateSchema = ajv.compile(ruleInstance.meta.schema[0]) + + if (!validateSchema(ruleValue)) { + errorsSchema.push({ ruleId, defaultSetup: ruleInstance.meta.defaultSetup }) + } + } + + if (errorsRules.length) { + throw new Error(errorsRules.map(error => `\tRule ${error} doesn't exist.\n`).join('')) + } + + if (errorsSchema.length) { + throw new Error( + errorsSchema + .map( + (ruleId, defaultSetup) => + `\tRule ${ruleId} have an invalid schema.\n\tThe default setup is: ${JSON.stringify( + defaultSetup + )}` + ) + .join('') + ) + } +} + +const formatErrors = errors => + errors + .map(error => { + if (error.keyword === 'additionalProperties') { + const formattedPropertyPath = error.dataPath.length + ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` + : error.params.additionalProperty + + return `Unexpected top-level property "${formattedPropertyPath}"` + } + if (error.keyword === 'type') { + const formattedField = error.dataPath.slice(1) + const formattedExpectedType = Array.isArray(error.schema) + ? error.schema.join('/') + : error.schema + const formattedValue = JSON.stringify(error.data) + + return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)` + } + + const field = error.dataPath[0] === '.' ? error.dataPath.slice(1) : error.dataPath + + return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}` + }) + .map(message => `\t- ${message}.\n`) + .join('') + +const validateConfigSchema = config => { + validateSchema = validateSchema || ajv.compile(configSchema) + + if (!validateSchema(config)) { + throw new Error(`Solhint configuration is invalid:\n${formatErrors(validateSchema.errors)}`) + } +} + +const validate = config => { + validateConfigSchema(config) + validateRules(config.rules) +} + +module.exports = { + validate, + validSeverityMap, + defaultSchemaValueForRules +} diff --git a/lib/constants.js b/lib/constants.js deleted file mode 100644 index 749d4bd0..00000000 --- a/lib/constants.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = Object.freeze({ - RULES: { - PRETTIER: 'prettier/prettier', - QUOTES: 'quotes' - } -}) diff --git a/lib/load-rules.js b/lib/load-rules.js new file mode 100644 index 00000000..48b3baee --- /dev/null +++ b/lib/load-rules.js @@ -0,0 +1,67 @@ +const path = require('path') +const { walkSync } = require('./common/utils') + +const blacklistedFiles = ['index.js', 'base-deprecation.js', 'load-rules.js'] + +/** + * Load all rule modules from specified directory + */ +const loadRules = () => { + const rulesDir = path.join(__dirname, 'rules') + const rules = [] + const files = walkSync(rulesDir) + + const filesFiltered = files.filter(file => { + const filename = path.parse(file).base + return path.extname(filename) === '.js' && !blacklistedFiles.includes(filename) + }) + + for (const file of filesFiltered) { + const FileRule = require(file) + + const isClass = typeof FileRule === 'function' + if (!isClass) { + return + } + + const instance = new FileRule() + if (instance && instance.ruleId) { + rules.push({ ruleId: instance.ruleId, meta: instance.meta }) + } + } + + return rules +} + +const loadRule = rule => { + const rulesDir = path.join(__dirname, 'rules') + let fileInstance + const files = walkSync(rulesDir) + + const filesFiltered = files.filter(file => { + const filename = path.parse(file).base + return path.extname(filename) === '.js' && !blacklistedFiles.includes(filename) + }) + + for (const file of filesFiltered) { + const filename = path.parse(file).name + if (filename !== rule) { + continue + } + const FileRule = require(file) + + const isClass = typeof FileRule === 'function' + if (!isClass) { + continue + } + + fileInstance = new FileRule() + } + + return fileInstance +} + +module.exports = { + loadRules, + loadRule +} diff --git a/lib/rules/align/array-declaration-spaces.js b/lib/rules/align/array-declaration-spaces.js index c77b61b7..c07ec26d 100644 --- a/lib/rules/align/array-declaration-spaces.js +++ b/lib/rules/align/array-declaration-spaces.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const { hasNoSpacesBefore } = require('./../../common/tokens') +const ruleId = 'array-declaration-spaces' +const meta = { + type: 'align', + + docs: { + description: 'Array declaration must not contains spaces.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ArrayDeclarationSpacesChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'array-declaration-spaces') + super(reporter, ruleId, meta) } enterTypeName(ctx) { diff --git a/lib/rules/align/bracket-align.js b/lib/rules/align/bracket-align.js index 34b6a12b..ec1cbd15 100644 --- a/lib/rules/align/bracket-align.js +++ b/lib/rules/align/bracket-align.js @@ -3,9 +3,26 @@ const BaseChecker = require('./../base-checker') const { hasSpaceBefore, onSameLine, prevToken, startOf } = require('./../../common/tokens') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'bracket-align' +const meta = { + type: 'align', + + docs: { + description: + 'Open bracket must be on same line. It must be indented by other constructions by space.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class BracketAlign extends BaseChecker { constructor(reporter) { - super(reporter, 'bracket-align') + super(reporter, ruleId, meta) } enterBlock(ctx) { diff --git a/lib/rules/align/expression-indent.js b/lib/rules/align/expression-indent.js index c2e12143..c3b609bf 100644 --- a/lib/rules/align/expression-indent.js +++ b/lib/rules/align/expression-indent.js @@ -5,9 +5,25 @@ const { Rule } = require('./../../common/statements-indent-validator') +const ruleId = 'expression-indent' +const meta = { + type: 'align', + + docs: { + description: 'Expression indentation is incorrect.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ExpressionIndentChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'expression-indent') + super(reporter, ruleId, meta) } enterExpression(ctx) { diff --git a/lib/rules/align/indent.js b/lib/rules/align/indent.js index 82f3a39d..9c301838 100644 --- a/lib/rules/align/indent.js +++ b/lib/rules/align/indent.js @@ -2,10 +2,34 @@ const _ = require('lodash') const { typeOf } = require('./../../common/tree-traversing') const { columnOf, lineOf, stopLine } = require('./../../common/tokens') +const ruleId = 'indent' +const meta = { + type: 'align', + + docs: { + description: 'Indentation is incorrect.', + category: 'Style Guide Rules' + }, + + isDefault: true, + recommended: true, + defaultSetup: ['error', 4], + + schema: [ + { + type: 'array', + items: [{ type: 'integer' }], + uniqueItems: true, + minItems: 2 + } + ] +} + class IndentChecker { constructor(reporter, config) { this.reporter = reporter - this.ruleId = 'indent' + this.ruleId = ruleId + this.meta = meta this.linesWithError = [] const indent = this.parseConfig(config).indent || 4 @@ -86,7 +110,7 @@ class IndentChecker { } parseConfig(config) { - const rules = config.rules + const rules = (config && config.rules) || {} if (!(rules && rules.indent && rules.indent.length === 2)) { return {} } diff --git a/lib/rules/align/no-mix-tabs-and-spaces.js b/lib/rules/align/no-mix-tabs-and-spaces.js index ecfcd532..6292bbcd 100644 --- a/lib/rules/align/no-mix-tabs-and-spaces.js +++ b/lib/rules/align/no-mix-tabs-and-spaces.js @@ -1,10 +1,26 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'no-mix-tabs-and-spaces' +const meta = { + type: 'align', + + docs: { + description: 'Mixed tabs and spaces.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoMixTabsAndSpacesChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, 'no-mix-tabs-and-spaces') + super(reporter, ruleId, meta) - const configDefined = config.rules && config.rules.indent && config.rules.indent[1] + const configDefined = config && config.rules && config.rules.indent && config.rules.indent[1] this.spacer = (configDefined && config.rules.indent[1] === 'tabs' && 'tabs') || 'spaces' } diff --git a/lib/rules/align/no-spaces-before-semicolon.js b/lib/rules/align/no-spaces-before-semicolon.js index 170ac1b8..d1be1601 100644 --- a/lib/rules/align/no-spaces-before-semicolon.js +++ b/lib/rules/align/no-spaces-before-semicolon.js @@ -7,9 +7,25 @@ const { AlignValidatable } = require('./../../common/tokens') +const ruleId = 'no-spaces-before-semicolon' +const meta = { + type: 'align', + + docs: { + description: 'Semicolon must not have spaces before.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoSpacesBeforeSemicolonChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-spaces-before-semicolon') + super(reporter, ruleId, meta) } exitSourceUnit(ctx) { diff --git a/lib/rules/align/space-after-comma.js b/lib/rules/align/space-after-comma.js index 91b861ef..619b9d51 100644 --- a/lib/rules/align/space-after-comma.js +++ b/lib/rules/align/space-after-comma.js @@ -8,9 +8,25 @@ const { } = require('./../../common/tokens') const { noSpaces, prevTokenFromToken } = require('./../../common/tokens') +const ruleId = 'space-after-comma' +const meta = { + type: 'align', + + docs: { + description: 'Comma must be separated from next element by space.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class SpaceAfterCommaChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'space-after-comma') + super(reporter, ruleId, meta) } exitSourceUnit(ctx) { diff --git a/lib/rules/align/statement-indent.js b/lib/rules/align/statement-indent.js index 6f3e81d6..664ce5b7 100644 --- a/lib/rules/align/statement-indent.js +++ b/lib/rules/align/statement-indent.js @@ -4,9 +4,25 @@ const { StatementsIndentValidator, Term } = require('./../../common/statements-i const { onSameLine, stopOf, startOf } = require('./../../common/tokens') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'statement-indent' +const meta = { + type: 'align', + + docs: { + description: 'Statement indentation is incorrect.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class StatementIndentChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'statement-indent') + super(reporter, ruleId, meta) } enterIfStatement(ctx) { diff --git a/lib/rules/base-checker.js b/lib/rules/base-checker.js index d404dfed..645507dd 100644 --- a/lib/rules/base-checker.js +++ b/lib/rules/base-checker.js @@ -1,7 +1,8 @@ class BaseChecker { - constructor(reporter, ruleId) { + constructor(reporter, ruleId, meta) { this.reporter = reporter this.ruleId = ruleId + this.meta = meta } error(ctx, message) { diff --git a/lib/rules/best-practises/code-complexity.js b/lib/rules/best-practises/code-complexity.js index b8acc8b7..6325b656 100644 --- a/lib/rules/best-practises/code-complexity.js +++ b/lib/rules/best-practises/code-complexity.js @@ -1,10 +1,33 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'code-complexity' +const meta = { + type: 'best-practises', + + docs: { + description: 'Function has cyclomatic complexity "current" but allowed no more than maxcompl.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: ['warn', 7], + + schema: [ + { + type: 'array', + items: [{ type: 'integer' }], + uniqueItems: true, + minItems: 2 + } + ] +} + class CodeComplexityChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, 'code-complexity') + super(reporter, ruleId, meta) - this.maxComplexity = config.getNumber('code-complexity', 7) + this.maxComplexity = (config && config.getNumber('code-complexity', 7)) || 7 } enterFunctionDefinition(ctx) { diff --git a/lib/rules/best-practises/function-max-lines.js b/lib/rules/best-practises/function-max-lines.js index 7dc627ec..9b7c60ab 100644 --- a/lib/rules/best-practises/function-max-lines.js +++ b/lib/rules/best-practises/function-max-lines.js @@ -4,11 +4,35 @@ const { lineOf, stopLine } = require('./../../common/tokens') const DEFAULT_MAX_LINES_COUNT = 50 +const ruleId = 'function-max-lines' +const meta = { + type: 'best-practises', + + docs: { + description: 'Function body contains "count" lines but allowed no more than maxlines.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: ['warn', 45], + + schema: [ + { + type: 'array', + items: [{ type: 'integer' }], + uniqueItems: true, + minItems: 2 + } + ] +} + class FunctionMaxLinesChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, 'function-max-lines') + super(reporter, ruleId, meta) - this.maxLines = config.getNumber('function-max-lines', DEFAULT_MAX_LINES_COUNT) + this.maxLines = + (config && config.getNumber(ruleId, DEFAULT_MAX_LINES_COUNT)) || DEFAULT_MAX_LINES_COUNT } enterFunctionDefinition(ctx) { diff --git a/lib/rules/best-practises/max-line-length.js b/lib/rules/best-practises/max-line-length.js index 41837fde..8cafae0b 100644 --- a/lib/rules/best-practises/max-line-length.js +++ b/lib/rules/best-practises/max-line-length.js @@ -1,10 +1,33 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'max-line-length' +const meta = { + type: 'best-practises', + + docs: { + description: 'Line length must be no more than maxlen.', + category: 'Best Practise Rules' + }, + + isDefault: true, + recommended: false, + defaultSetup: ['error', 120], + + schema: [ + { + type: 'array', + items: [{ type: 'integer' }], + uniqueItems: true, + minItems: 2 + } + ] +} + class MaxLineLengthChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, 'max-line-length') + super(reporter, ruleId, meta) - this.maxLength = config.getNumber('max-line-length', 120) + this.maxLength = (config && config.getNumber(ruleId, 120)) || 120 } enterSourceUnit(ctx) { diff --git a/lib/rules/best-practises/max-states-count.js b/lib/rules/best-practises/max-states-count.js index 2c3bb1ad..97c7c7a5 100644 --- a/lib/rules/best-practises/max-states-count.js +++ b/lib/rules/best-practises/max-states-count.js @@ -2,11 +2,35 @@ const _ = require('lodash') const BaseChecker = require('./../base-checker') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'max-states-count' +const meta = { + type: 'best-practises', + + docs: { + description: + 'Contract has "some count" states declarations but allowed no more than maxstates.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: ['warn', 15], + + schema: [ + { + type: 'array', + items: [{ type: 'integer' }], + uniqueItems: true, + minItems: 2 + } + ] +} + class MaxStatesCountChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, 'max-states-count') + super(reporter, ruleId, meta) - this.maxStatesCount = config.getNumber('max-states-count', 15) + this.maxStatesCount = (config && config.getNumber('max-states-count', 15)) || 15 } enterContractDefinition(ctx) { diff --git a/lib/rules/best-practises/no-empty-blocks.js b/lib/rules/best-practises/no-empty-blocks.js index 4c4cdd78..78c9e5d3 100644 --- a/lib/rules/best-practises/no-empty-blocks.js +++ b/lib/rules/best-practises/no-empty-blocks.js @@ -5,9 +5,25 @@ const ONLY_BRACKETS_LENGTH = 2 const EMPTY_STRUCT_LENGTH = 4 const EMPTY_ENUM_LENGTH = 4 +const ruleId = 'no-empty-blocks' +const meta = { + type: 'best-practises', + + docs: { + description: 'Code contains empty block.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoEmptyBlocksChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-empty-blocks') + super(reporter, ruleId, meta) } exitBlock(ctx) { diff --git a/lib/rules/best-practises/no-unused-vars.js b/lib/rules/best-practises/no-unused-vars.js index 9793b26b..591e0ac1 100644 --- a/lib/rules/best-practises/no-unused-vars.js +++ b/lib/rules/best-practises/no-unused-vars.js @@ -5,9 +5,25 @@ const TreeTraversion = require('./../../common/tree-traversing') const traversing = new TreeTraversion() const { typeOf } = TreeTraversion +const ruleId = 'no-unused-vars' +const meta = { + type: 'best-practises', + + docs: { + description: 'Variable "name" is unused.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoUnusedVarsChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-unused-vars') + super(reporter, ruleId, meta) } enterFunctionDefinition(ctx) { diff --git a/lib/rules/best-practises/payable-fallback.js b/lib/rules/best-practises/payable-fallback.js index c84f434a..1b6276f6 100644 --- a/lib/rules/best-practises/payable-fallback.js +++ b/lib/rules/best-practises/payable-fallback.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'payable-fallback' +const meta = { + type: 'best-practises', + + docs: { + description: 'When fallback is not payable you will not be able to receive ethers.', + category: 'Best Practise Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class PayableFallbackChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'payable-fallback') + super(reporter, ruleId, meta) } exitFunctionDefinition(ctx) { diff --git a/lib/rules/deprecations/base-deprecation.js b/lib/rules/deprecations/base-deprecation.js index bf5b2953..715309db 100644 --- a/lib/rules/deprecations/base-deprecation.js +++ b/lib/rules/deprecations/base-deprecation.js @@ -1,8 +1,8 @@ const BaseChecker = require('./../base-checker') class BaseDeprecation extends BaseChecker { - constructor(reporter, ruleId) { - super(reporter, ruleId) + constructor(reporter, ruleId, meta) { + super(reporter, ruleId, meta) this.active = false this.deprecationVersion() // to ensure we have one. } diff --git a/lib/rules/deprecations/constructor-syntax.js b/lib/rules/deprecations/constructor-syntax.js index 6dd62c80..5c454a19 100644 --- a/lib/rules/deprecations/constructor-syntax.js +++ b/lib/rules/deprecations/constructor-syntax.js @@ -3,9 +3,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const traversing = new TreeTraversing() +const ruleId = 'constructor-syntax' +const meta = { + type: 'best-practises', + + docs: { + description: 'Constructors should use the new constructor keyword.', + category: 'Best practises' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class ConstructorSyntax extends BaseDeprecation { constructor(reporter) { - super(reporter, 'constructor-syntax') + super(reporter, ruleId, meta) } deprecationVersion() { diff --git a/lib/rules/index.js b/lib/rules/index.js index 4b3a7222..eb2f0a97 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -6,6 +6,7 @@ const bestPractises = require('./best-practises/index') const deprecations = require('./deprecations/index') const miscellaneous = require('./miscellaneous/index') const configObject = require('./../config') +const { validSeverityMap } = require('../config/config-validator') module.exports = function checkers(reporter, configVals, inputSrc, fileName) { const config = configObject.from(configVals) @@ -43,7 +44,19 @@ function pluginsRules() { } function ruleEnabled(coreRule, rules) { - if (rules && rules[coreRule.ruleId] !== undefined) { + let ruleValue + if (rules && !Array.isArray(rules[coreRule.ruleId])) { + ruleValue = rules[coreRule.ruleId] + } else if (rules && Array.isArray(rules[coreRule.ruleId])) { + ruleValue = rules[coreRule.ruleId][0] + } + + if ( + rules && + rules[coreRule.ruleId] !== undefined && + ruleValue && + validSeverityMap.includes(ruleValue) + ) { return coreRule } } diff --git a/lib/rules/miscellaneous/prettier.js b/lib/rules/miscellaneous/prettier.js index fc73e1ce..a13a990b 100644 --- a/lib/rules/miscellaneous/prettier.js +++ b/lib/rules/miscellaneous/prettier.js @@ -1,13 +1,28 @@ const { showInvisibles, generateDifferences } = require('prettier-linter-helpers') const { getLocFromIndex } = require('../../common/utils') const BaseChecker = require('../base-checker') -const { RULES } = require('../../constants') const { INSERT, DELETE, REPLACE } = generateDifferences +const ruleId = 'prettier/prettier' +const meta = { + type: 'miscellaneous', + + docs: { + description: 'Prettier rule for solidity projects.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class Prettier extends BaseChecker { constructor(reporter, config, inputSrc, fileName) { - super(reporter, RULES.PRETTIER) + super(reporter, ruleId, meta) this.inputSrc = inputSrc this.fileName = fileName } diff --git a/lib/rules/miscellaneous/quotes.js b/lib/rules/miscellaneous/quotes.js index ce923876..a4787ec0 100644 --- a/lib/rules/miscellaneous/quotes.js +++ b/lib/rules/miscellaneous/quotes.js @@ -1,11 +1,38 @@ const BaseChecker = require('../base-checker') -const { RULES } = require('../../constants') + +const ruleId = 'quotes' +const meta = { + type: 'miscellaneous', + + docs: { + description: `Use double quotes for string literals. Values must be 'single' or 'double'.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: ['error', 'double'], + + schema: [ + { + type: 'array', + items: [ + { + type: 'string', + enum: ['single', 'double'] + } + ], + uniqueItems: true, + minItems: 2 + } + ] +} class QuotesChecker extends BaseChecker { constructor(reporter, config) { - super(reporter, RULES.QUOTES) + super(reporter, ruleId, meta) - const quoteType = config.rules && config.rules.quotes && config.rules.quotes[1] + const quoteType = config && config.rules && config.rules.quotes && config.rules.quotes[1] this.quoteType = (['double', 'single'].includes(quoteType) && quoteType) || 'double' this.incorrectQuote = this.quoteType === 'single' ? '"' : "'" } diff --git a/lib/rules/naming/const-name-snakecase.js b/lib/rules/naming/const-name-snakecase.js index 1955fe7a..6024b104 100644 --- a/lib/rules/naming/const-name-snakecase.js +++ b/lib/rules/naming/const-name-snakecase.js @@ -4,9 +4,25 @@ const naming = require('./../../common/identifier-naming') const traversing = new TreeTraversing() +const ruleId = 'const-name-snakecase' +const meta = { + type: 'naming', + + docs: { + description: 'Constant name must be in capitalized SNAKE_CASE.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ConstNameSnakecaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'const-name-snakecase') + super(reporter, ruleId, meta) } exitStateVariableDeclaration(ctx) { diff --git a/lib/rules/naming/contract-name-camelcase.js b/lib/rules/naming/contract-name-camelcase.js index f8c6a377..ac920efd 100644 --- a/lib/rules/naming/contract-name-camelcase.js +++ b/lib/rules/naming/contract-name-camelcase.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const naming = require('./../../common/identifier-naming') +const ruleId = 'contract-name-camelcase' +const meta = { + type: 'naming', + + docs: { + description: 'Contract name must be in CamelCase.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ContractNameCamelcaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'contract-name-camelcase') + super(reporter, ruleId, meta) } enterContractDefinition(ctx) { diff --git a/lib/rules/naming/event-name-camelcase.js b/lib/rules/naming/event-name-camelcase.js index 09421210..d5e751ab 100644 --- a/lib/rules/naming/event-name-camelcase.js +++ b/lib/rules/naming/event-name-camelcase.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const naming = require('./../../common/identifier-naming') +const ruleId = 'event-name-camelcase' +const meta = { + type: 'naming', + + docs: { + description: 'Event name must be in CamelCase.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class EventNameCamelcaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'event-name-camelcase') + super(reporter, ruleId, meta) } exitEventDefinition(ctx) { diff --git a/lib/rules/naming/func-name-mixedcase.js b/lib/rules/naming/func-name-mixedcase.js index f3e6caf8..1fa9a91c 100644 --- a/lib/rules/naming/func-name-mixedcase.js +++ b/lib/rules/naming/func-name-mixedcase.js @@ -5,9 +5,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const { typeOf } = TreeTraversing const traversing = new TreeTraversing() +const ruleId = 'func-name-mixedcase' +const meta = { + type: 'naming', + + docs: { + description: 'Function name must be in camelCase.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class FuncNameMixedcaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'func-name-mixedcase') + super(reporter, ruleId, meta) } exitFunctionDefinition(ctx) { diff --git a/lib/rules/naming/func-param-name-mixedcase.js b/lib/rules/naming/func-param-name-mixedcase.js index 6e88d09b..b1a5eaeb 100644 --- a/lib/rules/naming/func-param-name-mixedcase.js +++ b/lib/rules/naming/func-param-name-mixedcase.js @@ -2,9 +2,25 @@ const BaseChecker = require('./../base-checker') const naming = require('./../../common/identifier-naming') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'func-param-name-mixedcase' +const meta = { + type: 'naming', + + docs: { + description: 'Function name must be in camelCase.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class FunctionParamNameMixedcaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'func-param-name-mixedcase') + super(reporter, ruleId, meta) } exitEventParameter(ctx) { diff --git a/lib/rules/naming/modifier-name-mixedcase.js b/lib/rules/naming/modifier-name-mixedcase.js index 8f60962e..1aad6c62 100644 --- a/lib/rules/naming/modifier-name-mixedcase.js +++ b/lib/rules/naming/modifier-name-mixedcase.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const naming = require('./../../common/identifier-naming') +const ruleId = 'modifier-name-mixedcase' +const meta = { + type: 'naming', + + docs: { + description: 'Modifier name must be in mixedCase.', + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class ModifierNameMixedcase extends BaseChecker { constructor(reporter) { - super(reporter, 'modifier-name-mixedcase') + super(reporter, ruleId, meta) } exitModifierDefinition(ctx) { diff --git a/lib/rules/naming/use-forbidden-name.js b/lib/rules/naming/use-forbidden-name.js index e8b07d2d..459c2a13 100644 --- a/lib/rules/naming/use-forbidden-name.js +++ b/lib/rules/naming/use-forbidden-name.js @@ -2,9 +2,25 @@ const BaseChecker = require('./../base-checker') const FROBIDDEN_NAMES = ['I', 'l', 'O'] +const ruleId = 'use-forbidden-name' +const meta = { + type: 'naming', + + docs: { + description: `Avoid to use letters 'I', 'l', 'O' as identifiers.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class UseForbiddenNameChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'use-forbidden-name') + super(reporter, ruleId, meta) } exitIdentifier(ctx) { diff --git a/lib/rules/naming/var-name-mixedcase.js b/lib/rules/naming/var-name-mixedcase.js index 6d67ac78..3cd77c58 100644 --- a/lib/rules/naming/var-name-mixedcase.js +++ b/lib/rules/naming/var-name-mixedcase.js @@ -4,9 +4,25 @@ const naming = require('./../../common/identifier-naming') const traversing = new TreeTraversing() +const ruleId = 'var-name-mixedcase' +const meta = { + type: 'naming', + + docs: { + description: `Variable name must be in mixedCase.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class VarNameMixedcaseChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'var-name-mixedcase') + super(reporter, ruleId, meta) } exitIdentifierList(ctx) { diff --git a/lib/rules/order/func-order.js b/lib/rules/order/func-order.js index 13b54572..901e7b24 100644 --- a/lib/rules/order/func-order.js +++ b/lib/rules/order/func-order.js @@ -3,9 +3,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const traversing = new TreeTraversing() +const ruleId = 'func-order' +const meta = { + type: 'order', + + docs: { + description: `Function order is incorrect.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class FuncOrderChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'func-order') + super(reporter, ruleId, meta) } enterContractDefinition(ctx) { diff --git a/lib/rules/order/imports-on-top.js b/lib/rules/order/imports-on-top.js index 4872f42b..5b44babf 100644 --- a/lib/rules/order/imports-on-top.js +++ b/lib/rules/order/imports-on-top.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'imports-on-top' +const meta = { + type: 'order', + + docs: { + description: `Import statements must be on top.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ImportsOnTopChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'imports-on-top') + super(reporter, ruleId, meta) } exitSourceUnit(ctx) { diff --git a/lib/rules/order/separate-by-one-line-in-contract.js b/lib/rules/order/separate-by-one-line-in-contract.js index 3650c678..21c9430a 100644 --- a/lib/rules/order/separate-by-one-line-in-contract.js +++ b/lib/rules/order/separate-by-one-line-in-contract.js @@ -2,9 +2,25 @@ const BaseChecker = require('./../base-checker') const BlankLineCounter = require('./../../common/blank-line-counter') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'separate-by-one-line-in-contract' +const meta = { + type: 'order', + + docs: { + description: `Definitions inside contract / library must be separated by one line.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class SeparateByOneLineInContractChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'separate-by-one-line-in-contract') + super(reporter, ruleId, meta) this.lineCounter = new BlankLineCounter() } diff --git a/lib/rules/order/two-lines-top-level-separator.js b/lib/rules/order/two-lines-top-level-separator.js index c6c48cce..59f29e8c 100644 --- a/lib/rules/order/two-lines-top-level-separator.js +++ b/lib/rules/order/two-lines-top-level-separator.js @@ -2,9 +2,25 @@ const BaseChecker = require('./../base-checker') const BlankLineCounter = require('./../../common/blank-line-counter') const { typeOf } = require('./../../common/tree-traversing') +const ruleId = 'two-lines-top-level-separator' +const meta = { + type: 'order', + + docs: { + description: `Definition must be surrounded with two blank line indent.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: false, + defaultSetup: 'warn', + + schema: [] +} + class TwoLinesTopLevelSeparatorChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'two-lines-top-level-separator') + super(reporter, ruleId, meta) this.lineCounter = new BlankLineCounter() } diff --git a/lib/rules/order/visibility-modifier-order.js b/lib/rules/order/visibility-modifier-order.js index c6278fd8..84ea80a9 100644 --- a/lib/rules/order/visibility-modifier-order.js +++ b/lib/rules/order/visibility-modifier-order.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'visibility-modifier-order' +const meta = { + type: 'order', + + docs: { + description: `Visibility modifier must be first in list of modifiers.`, + category: 'Style Guide Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class VisibilityModifierOrderChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'visibility-modifier-order') + super(reporter, ruleId, meta) } exitModifierList(ctx) { diff --git a/lib/rules/security/avoid-call-value.js b/lib/rules/security/avoid-call-value.js index d095f3e6..52fe7067 100644 --- a/lib/rules/security/avoid-call-value.js +++ b/lib/rules/security/avoid-call-value.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'avoid-call-value' +const meta = { + type: 'security', + + docs: { + description: `Avoid to use ".call.value()()".`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidCallValueChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-call-value') + super(reporter, ruleId, meta) } exitExpression(ctx) { diff --git a/lib/rules/security/avoid-low-level-calls.js b/lib/rules/security/avoid-low-level-calls.js index 2d063f5b..8e0fcc2a 100644 --- a/lib/rules/security/avoid-low-level-calls.js +++ b/lib/rules/security/avoid-low-level-calls.js @@ -1,9 +1,25 @@ const BaseChecker = require('./../base-checker') const { hasMethodCalls } = require('./../../common/tree-traversing') +const ruleId = 'avoid-low-level-calls' +const meta = { + type: 'security', + + docs: { + description: `Avoid to use low level calls.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidLowLevelCallsChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-low-level-calls') + super(reporter, ruleId, meta) } exitExpression(ctx) { diff --git a/lib/rules/security/avoid-sha3.js b/lib/rules/security/avoid-sha3.js index 63cee4de..2afcc770 100644 --- a/lib/rules/security/avoid-sha3.js +++ b/lib/rules/security/avoid-sha3.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'avoid-sha3' +const meta = { + type: 'security', + + docs: { + description: `Use "keccak256" instead of deprecated "sha3".`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidSha3Checker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-sha3') + super(reporter, ruleId, meta) } exitIdentifier(ctx) { diff --git a/lib/rules/security/avoid-suicide.js b/lib/rules/security/avoid-suicide.js index fb367f5b..8d4ad242 100644 --- a/lib/rules/security/avoid-suicide.js +++ b/lib/rules/security/avoid-suicide.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'avoid-suicide' +const meta = { + type: 'security', + + docs: { + description: `Use "selfdestruct" instead of deprecated "suicide".`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidSuicideChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-suicide') + super(reporter, ruleId, meta) } exitIdentifier(ctx) { diff --git a/lib/rules/security/avoid-throw.js b/lib/rules/security/avoid-throw.js index 23892f14..2b8e9560 100644 --- a/lib/rules/security/avoid-throw.js +++ b/lib/rules/security/avoid-throw.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'avoid-throw' +const meta = { + type: 'security', + + docs: { + description: `"throw" is deprecated, avoid to use it.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidThrowChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-throw') + super(reporter, ruleId, meta) } exitThrowStatement(ctx) { diff --git a/lib/rules/security/avoid-tx-origin.js b/lib/rules/security/avoid-tx-origin.js index 2a90a65f..3ce51e42 100644 --- a/lib/rules/security/avoid-tx-origin.js +++ b/lib/rules/security/avoid-tx-origin.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'avoid-tx-origin' +const meta = { + type: 'security', + + docs: { + description: `Avoid to use tx.origin.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class AvoidTxOriginChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'avoid-tx-origin') + super(reporter, ruleId, meta) } exitExpression(ctx) { diff --git a/lib/rules/security/check-send-result.js b/lib/rules/security/check-send-result.js index b26083b9..9380ac99 100644 --- a/lib/rules/security/check-send-result.js +++ b/lib/rules/security/check-send-result.js @@ -3,9 +3,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const traversing = new TreeTraversing() +const ruleId = 'check-send-result' +const meta = { + type: 'security', + + docs: { + description: `Check result of "send" call.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class CheckSendResultChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'check-send-result') + super(reporter, ruleId, meta) } exitExpression(ctx) { diff --git a/lib/rules/security/compiler-fixed.js b/lib/rules/security/compiler-fixed.js index 4fe3a268..c14cf95d 100644 --- a/lib/rules/security/compiler-fixed.js +++ b/lib/rules/security/compiler-fixed.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'compiler-fixed' +const meta = { + type: 'security', + + docs: { + description: `Compiler version must be fixed.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class CompilerFixedChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'compiler-fixed') + super(reporter, ruleId, meta) } exitVersionOperator(ctx) { diff --git a/lib/rules/security/compiler-gt-0_4.js b/lib/rules/security/compiler-gt-0_4.js index 416e76ba..90db27ef 100644 --- a/lib/rules/security/compiler-gt-0_4.js +++ b/lib/rules/security/compiler-gt-0_4.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'compiler-gt-0_4' +const meta = { + type: 'security', + + docs: { + description: `Compiler version must be fixed.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class CompilerGT04Checker extends BaseChecker { constructor(reporter) { - super(reporter, 'compiler-gt-0_4') + super(reporter, ruleId, meta) } exitVersionConstraint(ctx) { diff --git a/lib/rules/security/func-visibility.js b/lib/rules/security/func-visibility.js index b9ec56ff..f43fac7c 100644 --- a/lib/rules/security/func-visibility.js +++ b/lib/rules/security/func-visibility.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'func-visibility' +const meta = { + type: 'security', + + docs: { + description: `Explicitly mark visibility in function.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class FuncVisibilityChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'func-visibility') + super(reporter, ruleId, meta) } exitModifierList(ctx) { diff --git a/lib/rules/security/mark-callable-contracts.js b/lib/rules/security/mark-callable-contracts.js index 4d9e222c..c074e405 100644 --- a/lib/rules/security/mark-callable-contracts.js +++ b/lib/rules/security/mark-callable-contracts.js @@ -4,10 +4,27 @@ const Reporter = require('./../../reporter') const traversing = new TreeTraversing() const SEVERITY = Reporter.SEVERITY +const ruleId = 'mark-callable-contracts' +const meta = { + type: 'security', + + docs: { + description: `Explicitly mark all external contracts as trusted or untrusted.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class MarkCallableContractsChecker { constructor(reporter) { this.reporter = reporter - this.ruleId = 'mark-callable-contracts' + this.ruleId = ruleId + this.meta = meta } exitIdentifier(ctx) { diff --git a/lib/rules/security/multiple-sends.js b/lib/rules/security/multiple-sends.js index e022046a..9f1b0dc2 100644 --- a/lib/rules/security/multiple-sends.js +++ b/lib/rules/security/multiple-sends.js @@ -3,9 +3,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const traversing = new TreeTraversing() +const ruleId = 'multiple-sends' +const meta = { + type: 'security', + + docs: { + description: `Avoid multiple calls of "send" method in single transaction.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class MultipleSendsChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'multiple-sends') + super(reporter, ruleId, meta) this.funcDefSet = new Set() } diff --git a/lib/rules/security/no-complex-fallback.js b/lib/rules/security/no-complex-fallback.js index 7e420cfe..eeaab9be 100644 --- a/lib/rules/security/no-complex-fallback.js +++ b/lib/rules/security/no-complex-fallback.js @@ -3,9 +3,25 @@ const BaseChecker = require('./../base-checker') const traversing = new TreeTraversing() +const ruleId = 'no-complex-fallback' +const meta = { + type: 'security', + + docs: { + description: `Fallback function must be simple.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoComplexFallbackChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-complex-fallback') + super(reporter, ruleId, meta) } exitFunctionDefinition(ctx) { diff --git a/lib/rules/security/no-inline-assembly.js b/lib/rules/security/no-inline-assembly.js index f46df014..20ea65fc 100644 --- a/lib/rules/security/no-inline-assembly.js +++ b/lib/rules/security/no-inline-assembly.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'no-inline-assembly' +const meta = { + type: 'security', + + docs: { + description: `Avoid to use inline assembly. It is acceptable only in rare cases.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoInlineAssemblyChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-inline-assembly') + super(reporter, ruleId, meta) } exitInlineAssemblyStatement(ctx) { diff --git a/lib/rules/security/no-simple-event-func-name.js b/lib/rules/security/no-simple-event-func-name.js index b66bd4d4..c0ab5f3a 100644 --- a/lib/rules/security/no-simple-event-func-name.js +++ b/lib/rules/security/no-simple-event-func-name.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'no-simple-event-func-name' +const meta = { + type: 'security', + + docs: { + description: `Event and function names must be different.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NoSimpleEventFuncNameChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'no-simple-event-func-name') + super(reporter, ruleId, meta) this.funcNameMap = {} this.eventNameMap = {} diff --git a/lib/rules/security/not-rely-on-block-hash.js b/lib/rules/security/not-rely-on-block-hash.js index bf258b53..82b35b7d 100644 --- a/lib/rules/security/not-rely-on-block-hash.js +++ b/lib/rules/security/not-rely-on-block-hash.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'not-rely-on-block-hash' +const meta = { + type: 'security', + + docs: { + description: `Do not rely on "block.blockhash". Miners can influence its value.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NotRelyOnBlockHashChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'not-rely-on-block-hash') + super(reporter, ruleId, meta) } exitExpression(ctx) { diff --git a/lib/rules/security/not-rely-on-time.js b/lib/rules/security/not-rely-on-time.js index 73063ee8..ba7661f3 100644 --- a/lib/rules/security/not-rely-on-time.js +++ b/lib/rules/security/not-rely-on-time.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'not-rely-on-time' +const meta = { + type: 'security', + + docs: { + description: `Avoid to make time-based decisions in your business logic.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class NotRelyOnTimeChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'not-rely-on-time') + super(reporter, ruleId, meta) } exitIdentifier(ctx) { diff --git a/lib/rules/security/reentrancy.js b/lib/rules/security/reentrancy.js index 7a485136..ddaf6aa6 100644 --- a/lib/rules/security/reentrancy.js +++ b/lib/rules/security/reentrancy.js @@ -4,9 +4,25 @@ const TreeTraversing = require('./../../common/tree-traversing') const { typeOf, hasMethodCalls, findPropertyInParents } = TreeTraversing +const ruleId = 'reentrancy' +const meta = { + type: 'security', + + docs: { + description: `Possible reentrancy vulnerabilities. Avoid state changes after transfer.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class ReentrancyChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'reentrancy') + super(reporter, ruleId, meta) } enterContractDefinition(ctx) { diff --git a/lib/rules/security/state-visibility.js b/lib/rules/security/state-visibility.js index 0bba57f6..7822ae2d 100644 --- a/lib/rules/security/state-visibility.js +++ b/lib/rules/security/state-visibility.js @@ -1,8 +1,24 @@ const BaseChecker = require('./../base-checker') +const ruleId = 'state-visibility' +const meta = { + type: 'security', + + docs: { + description: `Explicitly mark visibility of state.`, + category: 'Security Rules' + }, + + isDefault: false, + recommended: true, + defaultSetup: 'warn', + + schema: [] +} + class StateVisibilityChecker extends BaseChecker { constructor(reporter) { - super(reporter, 'state-visibility') + super(reporter, ruleId, meta) } exitStateVariableDeclaration(ctx) { diff --git a/package-lock.json b/package-lock.json index 8294b7a7..f4503040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -181,15 +181,14 @@ } }, "ajv": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.0.tgz", - "integrity": "sha1-6yhAdG6dxIvV4GOjbj/UAMXqtak=", - "dev": true, + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { @@ -324,6 +323,21 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -426,6 +440,28 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cosmiconfig": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "coveralls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", @@ -557,7 +593,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -887,10 +922,9 @@ "dev": true }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-diff": { "version": "1.1.2", @@ -1049,6 +1083,32 @@ "requires": { "ajv": "^5.3.0", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + } } }, "has": { @@ -1108,6 +1168,30 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1159,8 +1243,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-builtin-module": { "version": "1.0.0", @@ -1183,6 +1266,11 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -1311,6 +1399,11 @@ "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -1318,10 +1411,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", diff --git a/package.json b/package.json index 6023f81c..e010a09d 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,14 @@ "author": "Ilya Drabenia ", "license": "MIT", "dependencies": { + "ajv": "^6.6.1", "antlr4": "4.7.1", "commander": "2.18.0", + "cosmiconfig": "^5.0.7", "eslint": "^5.6.0", "fast-diff": "^1.1.2", - "glob": "7.1.3", "ignore": "^4.0.6", + "js-yaml": "^3.12.0", "lodash": "^4.17.11", "prettier-linter-helpers": "^1.0.0" }, diff --git a/solhint.js b/solhint.js index f4dd64b6..256fc766 100755 --- a/solhint.js +++ b/solhint.js @@ -4,7 +4,10 @@ const program = require('commander') const _ = require('lodash') const fs = require('fs') const process = require('process') + const linter = require('./lib/index') +const { applyExtends, loadConfig } = require('./lib/config/config-file') +const { validate } = require('./lib/config/config-validator') function init() { program.version('1.1.10') @@ -128,18 +131,24 @@ const readConfig = _.memoize(() => { let config = {} try { - const configStr = fs.readFileSync('.solhint.json').toString() - config = JSON.parse(configStr) + config = loadConfig() } catch (e) { - if (e instanceof SyntaxError) { - console.log('ERROR: Configuration file [.solhint.json] is not a valid JSON!\n') - process.exit(0) - } + console.log(e.message) + process.exit(0) } const configExcludeFiles = _.flatten(config.excludedFiles) config.excludedFiles = _.concat(configExcludeFiles, readIgnore()) + // If an `extends` property is defined, it represents a configuration file to use as + // a "parent". Load the referenced file and merge the configuration recursively. + if (config.extends) { + config = applyExtends(config) + } + + // validate the configuration before continuing + validate(config) + return config }) diff --git a/test/common/config-validator.js b/test/common/config-validator.js new file mode 100644 index 00000000..ca07ae7d --- /dev/null +++ b/test/common/config-validator.js @@ -0,0 +1,52 @@ +const assert = require('assert') +const _ = require('lodash') +const { + validate, + validSeverityMap, + defaultSchemaValueForRules +} = require('./../../lib/config/config-validator') + +describe('Config validator', () => { + it('should check validSeverityMap', () => { + assert.deepStrictEqual(validSeverityMap, ['error', 'warn']) + }) + + it('should check defaultSchemaValueForRules', () => { + assert.deepStrictEqual(defaultSchemaValueForRules, { + oneOf: [{ type: 'string', enum: ['error', 'warn', 'off'] }, { const: false }] + }) + }) + + it('should validate config', () => { + const config = { + extends: [], + rules: { + 'avoid-throw': 'off', + indent: ['error', 2] + } + } + assert.deepStrictEqual(_.isUndefined(validate(config)), true) + }) + + it('should throw an error with wrong config', () => { + const config = { + test: [], + rules: { + 'avoid-throw': 'off', + indent: ['error', 2] + } + } + assert.throws(() => validate(config), Error) + }) + + it('should throw an error with wrong rule', () => { + const config = { + extends: [], + rules: { + 'avoid-throw': 'off', + indent: 'lol' + } + } + assert.throws(() => validate(config), Error) + }) +}) diff --git a/test/common/load-rules.js b/test/common/load-rules.js new file mode 100644 index 00000000..dc7400d0 --- /dev/null +++ b/test/common/load-rules.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const _ = require('lodash') +const { loadRule, loadRules } = require('./../../lib/load-rules') + +describe('Load Rules', () => { + it('should load all rules', () => { + const rules = loadRules() + + for (const rule of rules) { + assert.equal(typeof rule, 'object') + assert.equal(_.has(rule, 'meta'), true) + assert.equal(_.has(rule, 'ruleId'), true) + assert.equal(_.has(rule.meta, 'type'), true) + assert.equal(_.has(rule.meta, 'docs'), true) + assert.equal(_.has(rule.meta, 'isDefault'), true) + assert.equal(_.has(rule.meta, 'recommended'), true) + assert.equal(_.has(rule.meta, 'defaultSetup'), true) + assert.equal(_.has(rule.meta, 'schema'), true) + } + }) + + it('should load a single rule', () => { + const rule = loadRule('func-param-name-mixedcase') + + assert.equal(typeof rule, 'object') + assert.equal(_.has(rule, 'meta'), true) + assert.equal(_.has(rule, 'ruleId'), true) + assert.equal(_.has(rule.meta, 'type'), true) + assert.equal(_.has(rule.meta, 'docs'), true) + assert.equal(_.has(rule.meta, 'isDefault'), true) + assert.equal(_.has(rule.meta, 'recommended'), true) + assert.equal(_.has(rule.meta, 'defaultSetup'), true) + assert.equal(_.has(rule.meta, 'schema'), true) + }) + + it('should not load a single rule', () => { + const rule = loadRule('foo') + + assert.equal(_.isUndefined(rule), true) + }) +}) From faa73b3870dadc43d8d6deb6cdf4b0d3f45444c3 Mon Sep 17 00:00:00 2001 From: fvictorio Date: Wed, 2 Jan 2019 15:23:43 -0300 Subject: [PATCH 2/8] Fix ConfigMissingError --- lib/common/errors.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/common/errors.js b/lib/common/errors.js index b8f16600..6606aad5 100644 --- a/lib/common/errors.js +++ b/lib/common/errors.js @@ -1,6 +1,5 @@ class ConfigMissingError extends Error { - constructor(data) { - const { configName } = data + constructor(configName) { const message = `Failed to load config "${configName}" to extend from.` super(message) } From 0eb47f3b58842be8e7030aaec4e4ab3b7d7d8787 Mon Sep 17 00:00:00 2001 From: fvictorio Date: Wed, 2 Jan 2019 15:24:03 -0300 Subject: [PATCH 3/8] Update the config object when converting `extends` to an array Otherwise, the subsequent validation will fail. --- lib/config/config-file.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/config/config-file.js b/lib/config/config-file.js index d529e104..25737f8c 100644 --- a/lib/config/config-file.js +++ b/lib/config/config-file.js @@ -53,14 +53,12 @@ const loadConfig = () => { } const applyExtends = config => { - let configExtends = config.extends - // normalize into an array for easier handling if (!Array.isArray(config.extends)) { - configExtends = [config.extends] + config.extends = [config.extends] } - return configExtends.reduceRight((previousValue, parentPath) => { + return config.extends.reduceRight((previousValue, parentPath) => { try { let extensionPath From b72f1bae6a2b6cc075767deff24766cc19c3bdee Mon Sep 17 00:00:00 2001 From: fvictorio Date: Thu, 3 Jan 2019 12:35:49 -0300 Subject: [PATCH 4/8] Merge rulesets in the correct order --- lib/config/config-file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config/config-file.js b/lib/config/config-file.js index 25737f8c..a3700e7d 100644 --- a/lib/config/config-file.js +++ b/lib/config/config-file.js @@ -70,7 +70,7 @@ const applyExtends = config => { } const extensionConfig = require(extensionPath) - return _.merge(config, extensionConfig) + return _.merge({}, extensionConfig, previousValue) } catch (e) { throw new ConfigMissingError(parentPath) } From ddee7624a527fb3081c29955edbc4a731e1dc22c Mon Sep 17 00:00:00 2001 From: fvictorio Date: Thu, 3 Jan 2019 13:00:24 -0300 Subject: [PATCH 5/8] Add tests for applyExtends --- lib/config/config-file.js | 30 +++++++----- test/common/apply-extends.js | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 test/common/apply-extends.js diff --git a/lib/config/config-file.js b/lib/config/config-file.js index a3700e7d..9c5e366a 100644 --- a/lib/config/config-file.js +++ b/lib/config/config-file.js @@ -52,24 +52,29 @@ const loadConfig = () => { return searchedFor.config || createEmptyConfig() } -const applyExtends = config => { - // normalize into an array for easier handling +const configGetter = path => { + let extensionPath = null + + if (path.startsWith('solhint:')) { + extensionPath = getSolhintCoreConfigPath(path) + } else { + // Load packages with rules + extensionPath = `solhint-config-${path}` + } + + const extensionConfig = require(extensionPath) + + return extensionConfig +} + +const applyExtends = (config, getter = configGetter) => { if (!Array.isArray(config.extends)) { config.extends = [config.extends] } return config.extends.reduceRight((previousValue, parentPath) => { try { - let extensionPath - - if (parentPath.startsWith('solhint:')) { - extensionPath = getSolhintCoreConfigPath(parentPath) - } else { - // Load packages with rules - extensionPath = `solhint-config-${parentPath}` - } - - const extensionConfig = require(extensionPath) + const extensionConfig = getter(parentPath) return _.merge({}, extensionConfig, previousValue) } catch (e) { throw new ConfigMissingError(parentPath) @@ -79,5 +84,6 @@ const applyExtends = config => { module.exports = { applyExtends, + configGetter, loadConfig } diff --git a/test/common/apply-extends.js b/test/common/apply-extends.js new file mode 100644 index 00000000..a565c412 --- /dev/null +++ b/test/common/apply-extends.js @@ -0,0 +1,94 @@ +const assert = require('assert') +const { applyExtends } = require('./../../lib/config/config-file') + +describe.only('applyExtends', () => { + it('should use the given config if the extends array is empty', () => { + const initialConfig = { + extends: [], + rules: { + rule0: 'error' + } + } + const result = applyExtends(initialConfig) + + assert.deepStrictEqual(result, initialConfig) + }) + + it('should add the rules in the config', () => { + const initialConfig = { + extends: ['config1'], + rules: { + rule0: 'error' + } + } + const config1 = { + rules: { + rule1: 'warning' + } + } + const result = applyExtends(initialConfig, configName => ({ config1 }[configName])) + + assert.deepStrictEqual(result, { + extends: ['config1'], + rules: { + rule0: 'error', + rule1: 'warning' + } + }) + }) + + it('should give higher priority to later configs', () => { + const initialConfig = { + extends: ['config1', 'config2'], + rules: { + rule0: 'error' + } + } + const config1 = { + rules: { + rule1: 'warning' + } + } + const config2 = { + rules: { + rule1: 'off' + } + } + const result = applyExtends(initialConfig, configName => ({ config1, config2 }[configName])) + + assert.deepStrictEqual(result, { + extends: ['config1', 'config2'], + rules: { + rule0: 'error', + rule1: 'off' + } + }) + }) + + it('should give higher priority to rules in the config file', () => { + const initialConfig = { + extends: ['config1', 'config2'], + rules: { + rule0: 'error' + } + } + const config1 = { + rules: { + rule0: 'warning' + } + } + const config2 = { + rules: { + rule0: 'off' + } + } + const result = applyExtends(initialConfig, configName => ({ config1, config2 }[configName])) + + assert.deepStrictEqual(result, { + extends: ['config1', 'config2'], + rules: { + rule0: 'error' + } + }) + }) +}) From e76c71e2d1cc998e314c358c05889249180f9155 Mon Sep 17 00:00:00 2001 From: fvictorio Date: Thu, 3 Jan 2019 13:00:39 -0300 Subject: [PATCH 6/8] Fix test-only npm script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f6925fa..b128ca2d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "scripts": { "exec-tests": "nyc -x \"**/grammar/**\" mocha --recursive && nyc report --reporter=text-lcov | coveralls", "test": "npm run exec-tests", - "test-only": "mocha", + "test-only": "mocha --recursive", "generate": "scripts/build-grammar.sh", "lint": "eslint ." }, From 0f7fb25c6822b0874a40c2b78856301cee15ffd1 Mon Sep 17 00:00:00 2001 From: fvictorio Date: Thu, 3 Jan 2019 13:13:20 -0300 Subject: [PATCH 7/8] Run linter in travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b9621657..e7ba38a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,7 @@ language: node_js node_js: - "stable" + +script: + - npm run lint + - npm test From 081a910cc3bb4a3ae5288b8cdf99c3d1a61c26be Mon Sep 17 00:00:00 2001 From: fvictorio Date: Thu, 3 Jan 2019 13:13:41 -0300 Subject: [PATCH 8/8] Fix linter errors --- .eslintrc.js | 1 + lib/rules/miscellaneous/prettier.js | 2 +- package.json | 1 + solhint.js | 5 +---- test/rules/security/avoid-low-level-calls.js | 5 +---- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 33ce0ab0..1c88ec28 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,6 +13,7 @@ module.exports = { rules: { 'consistent-return': 'off', 'import/no-dynamic-require': 'off', + 'import/no-extraneous-dependencies': ['error', { optionalDependencies: true }], 'global-require': 'off', 'no-bitwise': 'off', 'no-console': 'off', diff --git a/lib/rules/miscellaneous/prettier.js b/lib/rules/miscellaneous/prettier.js index a13a990b..3cde6f64 100644 --- a/lib/rules/miscellaneous/prettier.js +++ b/lib/rules/miscellaneous/prettier.js @@ -27,7 +27,7 @@ class Prettier extends BaseChecker { this.fileName = fileName } - enterSourceUnit(ctx) { + enterSourceUnit() { try { // Check for optional dependencies with the try catch // Prettier is expensive to load, so only load it if needed. diff --git a/package.json b/package.json index b128ca2d..bb49d296 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "cosmiconfig": "^5.0.7", "eslint": "^5.6.0", "fast-diff": "^1.1.2", + "glob": "^7.1.3", "ignore": "^4.0.6", "js-yaml": "^3.12.0", "lodash": "^4.17.11", diff --git a/solhint.js b/solhint.js index dc579854..a3ff7932 100755 --- a/solhint.js +++ b/solhint.js @@ -17,10 +17,7 @@ function init() { program .usage('[options] [...other_files]') .option('-f, --formatter [name]', 'report formatter name (stylish, table, tap, unix)') - .option( - '-w, --max-warnings [maxWarningsNumber]', - 'number of allowed warnings' - ) + .option('-w, --max-warnings [maxWarningsNumber]', 'number of allowed warnings') .option('-c, --config [file_name]', 'file to use as your .solhint.json') .option('-q, --quiet', 'report errors only - default: false') .option('--ignore-path [file_name]', 'file to use as your .solhintignore') diff --git a/test/rules/security/avoid-low-level-calls.js b/test/rules/security/avoid-low-level-calls.js index 2d96519b..1554f745 100644 --- a/test/rules/security/avoid-low-level-calls.js +++ b/test/rules/security/avoid-low-level-calls.js @@ -1,9 +1,6 @@ const linter = require('./../../../lib/index') const funcWith = require('./../../common/contract-builder').funcWith -const { - assertWarnsCount, - assertErrorMessage -} = require('./../../common/asserts') +const { assertWarnsCount, assertErrorMessage } = require('./../../common/asserts') describe('Linter - avoid-low-level-calls', () => { const LOW_LEVEL_CALLS = [