Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

solium

Package Overview
Dependencies
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

solium - npm Package Compare versions

Comparing version 0.5.5 to 1.0.0

config/rulesets/solium-all.js

42

config/solium.json

@@ -32,2 +32,30 @@ {

"conditionals-whitespace": {
"enabled": true,
"recommended": true,
"type": "error",
"description": "Ensure that there is exactly one space between conditional operators and parenthetic blocks"
},
"comma-whitespace": {
"enabled": true,
"recommended": true,
"type": "error",
"description": "Ensure that there is no whitespace or comments between comma delimited elements and commas"
},
"semicolon-whitespace": {
"enabled": true,
"recommended": true,
"type": "error",
"description": "Ensure that there is no whitespace or comments before semicolons"
},
"function-whitespace": {
"enabled": true,
"recommended": true,
"type": "error",
"description": "Ensure function calls and declaration have (or don't have) whitespace in appropriate locations"
},
"lbrace": {

@@ -89,2 +117,9 @@ "enabled": true,

"quotes": {
"enabled": true,
"recommended": true,
"type": "error",
"description": "Ensure that all strings use only 1 style - either double quotes or single quotes."
},
"blank-lines": {

@@ -104,2 +139,9 @@ "enabled": true,

"arg-overflow": {
"enabled": true,
"recommended": false,
"type": "warning",
"description": "In the case of 4+ elements in the same line require they are instead put on a single line each"
},
"whitespace": {

@@ -106,0 +148,0 @@ "enabled": true,

23

lib/autofix/merge-fixer-packets.js

@@ -9,2 +9,7 @@ /**

/**
* Merge all fixer packets into 1 packet and return it.
* @params {(Array|Object)} fixesArrayOrObject Fixer packet(s) to be merged into a single fixer packet.
* @params {String} sourceCode Source code.
*/
module.exports = function (fixesArrayOrObject, sourceCode) {

@@ -18,6 +23,2 @@ // If argument is already a single fixes packet, return.

if (!fixes.length) {
throw new Error ('Array doesn\'t contain any Fixer Objects.');
}
if (fixes.length === 1) {

@@ -27,2 +28,3 @@ return fixesArrayOrObject [0];

// Sort fixer packets in top-down approach
fixes.sort (function (a, b) {

@@ -32,2 +34,4 @@ return (a.range [0] - b.range [0]) || (a.range [1] - b.range [1]);

// Take start of first fix and end of last fix to merge them all into 1 big fixer packet,
// combining all the code in between.
var start = fixes [0].range [0], end = fixes [fixes.length - 1].range [1];

@@ -37,4 +41,11 @@ var text = '', cursor = Number.MIN_SAFE_INTEGER;

fixes.forEach (function (fix) {
if (cursor >= fix.range [0]) {
throw new Error ('Fixer objects in a single Fixer Array cannot overlap.');
/**
* Condition below is '>' instead of '>=' on purpose.
* Say we have 2 packets- {range: [12, 20]} & {range: [20, 28], text: 'world'}.
* These should NOT conflict. After 1st packet (which actually makes changes to only 12 to 19 index),
* cursor is set to 20. At start of 2nd packet, the slice (20, 20) returns empty string and hence,
* there is no damage.
*/
if (cursor > fix.range [0]) {
throw new Error ('2 or more fix objects in the Fixer Array must not overlap.');
}

@@ -41,0 +52,0 @@

@@ -10,3 +10,46 @@ /**

function validateArgs (args) {
if (args.hasOwnProperty ('node')) {
if (!astUtils.isASTNode (args.node)) {
throw new Error ('A valid AST node was not provided.');
}
}
if (args.hasOwnProperty ('text')) {
var text = args.text;
// There are no restriction on string length
if (typeof text !== 'string') {
throw new Error ('A valid string was not provided.');
}
}
if (args.hasOwnProperty ('range')) {
var range = args.range;
if (!(Array.isArray (range) && range.length === 2 && Number.isInteger (range [0]) &&
Number.isInteger (range [1]) && range [0] >= 0 && range [1] >= 0)) {
throw new Error (
'A valid range object was not provided. Should be an Array of 2 unsigned Integers.');
}
}
if (args.hasOwnProperty ('index')) {
var index = args.index;
if (!(Number.isInteger (index) && index >= 0)) {
throw new Error ('A valid index was not provided. Must be an unsigned Integer.');
}
}
if (args.hasOwnProperty ('char')) {
var char = args.char;
if (!(typeof char === 'string' && char.length === 1)) {
throw new Error ('A valid character was not provided. Must be a string with length of 1.');
}
}
}
function insertTextAt (index, text) {

@@ -19,17 +62,7 @@ return {

function validateArgs (node, text) {
if (!astUtils.isASTNode (node)) {
throw new Error ('A valid AST node was not provided.');
}
if (!(text && typeof text === 'string')) {
throw new Error ('A valid string was not provided.');
}
}
var ruleFixerAPI = {
insertTextAfter (node, text) {
validateArgs (node, text);
validateArgs ({node: node, text: text});
return this.insertTextAfterRange ([node.start, node.end], text);

@@ -39,3 +72,3 @@ },

insertTextBefore (node, text) {
validateArgs (node, text);
validateArgs ({node: node, text: text});
return this.insertTextBeforeRange ([node.start, node.end], text);

@@ -49,3 +82,3 @@ },

replaceText (node, text) {
validateArgs (node, text);
validateArgs ({node: node, text: text});
return this.replaceTextRange ([node.start, node.end], text);

@@ -55,2 +88,3 @@ },

insertTextAfterRange (range, text) {
validateArgs ({range: range, text: text});
return insertTextAt (range [1], text);

@@ -60,2 +94,3 @@ },

insertTextBeforeRange (range, text) {
validateArgs ({range: range, text: text});
return insertTextAt (range [0], text);

@@ -65,6 +100,13 @@ },

removeRange (range) {
validateArgs ({range: range});
return this.replaceTextRange (range, '');
},
replaceChar (index, newChar) {
validateArgs ({index: index, char: newChar});
return this.replaceTextRange ([index, index + 1], newChar);
},
replaceTextRange (range, text) {
validateArgs ({range: range, text: text});
return {

@@ -71,0 +113,0 @@ range: range,

@@ -23,2 +23,8 @@ /**

/**
* Apply fixes to source code depending on whichever errors can be fixed.
* @param {String} sourceCode Code to fix
* @param {Array} errorMessages Error objects that describe the error and possibly how to fix it.
* @returns {Object} fixed Contains fixed code and information about fixes applied & remaining un-fixed errors.
*/
applyFixes: function (sourceCode, errorMessages) {

@@ -32,3 +38,5 @@ var fixedSourceCode = '', fixes = [], fixesApplied = [], remainingMessages = [];

// If this fix overlaps with the previous one or has negaive range, return.
if (cursor >= start || start > end) {
// Note that when cursor === start, its NOT an overlap since when source code in range
// [i, j] is being edited, the code covered is actually from i to j-1.
if (cursor > start || start > end) {
return false;

@@ -43,7 +51,13 @@ }

// Segregate errors that can be fixed from those that can't for sure.
errorMessages.forEach (function (msg) {
if (msg.fix) {
// If msg.fix is an Array of fix packets, merge them into a single fix packet.
msg.fix = mergeFixes (msg.fix, sourceCode);
try {
msg.fix = mergeFixes (msg.fix, sourceCode);
} catch (e) {
throw new Error ('An error occured while applying fix of rule "'
+ msg.ruleName + '" for error "' + msg.message + '": ' + e.message);
}
return fixes.push (msg);

@@ -55,2 +69,5 @@ }

// Fixes will be applied in top-down approach. The fix that arrives first (line-wise, followed by column-wise)
// gets applied first. But if current fix is applied successfully & the next one overlaps the current one,
// then the next one is simply skipped. Hence, it is NOT guranteed that all fixes will be applied.
fixes.sort (compareMessagesByFixRange).forEach (function (msg) {

@@ -57,0 +74,0 @@ if (attemptFix (msg.fix)) {

@@ -23,10 +23,6 @@ /**

SOLIUMIGNORE_FILENAME_ABSOLUTE = path.join (CWD, SOLIUMIGNORE_FILENAME),
DEFAULT_SOLIUMIGNORE_PATH = __dirname + '/cli-utils/.default-solium-ignore';
DEFAULT_SOLIUMIGNORE_PATH = __dirname + '/cli-utils/.default-solium-ignore',
DEFAULT_SOLIUMRC_PATH = __dirname + '/cli-utils/.default-soliumrc.json';
var errorCodes = {
ERRORS_FOUND: 1,
NO_SOLIUMRC: 3,
WRITE_FAILED: 4,
INVALID_PARAMS: 5,
};
var errorCodes = { ERRORS_FOUND: 1, NO_SOLIUMRC: 3, WRITE_FAILED: 4, INVALID_PARAMS: 5, FILE_NOT_FOUND: 6 };

@@ -54,5 +50,4 @@ /**

} catch (e) {
console.error (
'An error occurred while writing to ' + SOLIUMRC_FILENAME_ABSOLUTE + ':\n' + e
);
errorReporter.reportFatal (
'An error occurred while writing to ' + SOLIUMRC_FILENAME_ABSOLUTE + ':\n' + e.message);
process.exit (errorCodes.WRITE_FAILED);

@@ -63,20 +58,2 @@ }

/**
* Insert any rules that are present in SoliumRules.json but not in user's .soliumrc.json's rules
* @param {Object} userConfig User Configuration object
* @returns {void}
*/
function synchronizeWithSoliumRules (userConfig) {
Object.keys (soliumRules).filter (function (rulename) {
return soliumRules [rulename].enabled;
}).forEach (function (rulename) {
//only insert those rules that don't already exist. If a rule exists and is disabled, leave it untouched
if (!userConfig.rules.hasOwnProperty (rulename)) {
userConfig.rules [rulename] = true;
}
});
writeConfigFile (userConfig);
}
/**
* Copy data from cli-utils/.default-solium-ignore to (newly created) .soliumignore in user's root directory

@@ -92,5 +69,4 @@ * @returns {void}

} catch (e) {
console.error (
'An error occurred while writing to ' + SOLIUMIGNORE_FILENAME_ABSOLUTE + ':\n' + e
);
errorReporter.reportFatal (
'An error occurred while writing to ' + SOLIUMIGNORE_FILENAME_ABSOLUTE + ':\n' + e.message);
process.exit (errorCodes.WRITE_FAILED);

@@ -105,14 +81,3 @@ }

function createDefaultConfigJSON () {
var config = {
'custom-rules-filename': null,
rules: {}
};
Object.keys (soliumRules).filter (function (rulename) {
return soliumRules [rulename].enabled;
}).forEach (function (rulename) {
config.rules [rulename] = true;
});
writeConfigFile (config);
writeConfigFile (require (DEFAULT_SOLIUMRC_PATH));
}

@@ -126,2 +91,3 @@

* @param {String} fileName (optional) File name to use when reporting errors
* @returns {Integer} numOfErrors Number of Lint ERRORS that occured.
*/

@@ -142,13 +108,14 @@ function lintString (sourceCode, userConfig, errorReporter, fileName) {

} catch (e) {
console.error (
'An error occurred while running the linter on ' + fileName + ':\n' + e.stack
);
return;
errorReporter.reportFatal (
'An error occurred while linting over ' + fileName + ': ' + e.message);
process.exit (errorCodes.ERRORS_FOUND);
}
//if any lint errors exist, report them
// If any lint/internal errors/warnings exist, report them
lintErrors.length &&
errorReporter.report (fileName || '[stdin]', sourceCode, lintErrors, fixesApplied);
errorReporter.report (fileName, sourceCode, lintErrors, fixesApplied);
return lintErrors.length;
return lintErrors.reduce (function (numOfErrors, err) {
return err.type === 'error' ? numOfErrors+1 : numOfErrors;
}, 0);
}

@@ -161,5 +128,6 @@

* @param {Object} errorReporter The error reporter to use
* @returns {Integer} numOfErrors Number of Lint ERRORS that occured (the result returned by lintString())
*/
function lintFile (fileName, userConfig, errorReporter) {
var sourceCode = '';
var sourceCode;

@@ -169,5 +137,4 @@ try {

} catch (e) {
console.error (
'[ERROR] Unable to read ' + fileName + ': ' + e
);
errorReporter.reportFatal ('Unable to read ' + fileName + ': ' + e.message);
process.exit (errorCodes.FILE_NOT_FOUND);
}

@@ -179,6 +146,7 @@

/**
* Function that calls Solium object's linter based on user settings
* Function that calls Solium object's linter based on user settings.
* If not given, we lint the entire directory's (and sub-directories') solidity files.
* @param {Object} userConfig User's configurations that contain information about which linting rules to apply
* @param {String} filename (optional) The single file to be linted.
* If not given, we lint the entire directory's (and sub-directories') solidity files
* @returns {Integer} totalNumOfErrors Total no. of errors found throughout the codebase (directory) linted.
*/

@@ -196,12 +164,14 @@ function lint (userConfig, input, ignore, errorReporter) {

if (filesToLint) {
errorCount = sum (filesToLint.map (function (file) {
errorCount = sum (filesToLint.map (function (file, index) {
userConfig.options.returnInternalIssues = (index === 0);
return lintFile (file, userConfig, errorReporter);
}));
} else if (input.stdin) {
// This only works on *nix. Need to fix to enable stdin input in windows.
var sourceCode = fs.readFileSync ('/dev/stdin', 'utf-8');
errorCount = lintString (sourceCode, userConfig, errorReporter);
userConfig.options.returnInternalIssues = true;
errorCount = lintString (sourceCode, userConfig, errorReporter, '[stdin]');
} else {
console.error(
'ERROR! Must specify input for linter using --file, --dir or --stdin'
);
errorReporter.reportFatal ('Must specify input for linter using --file, --dir or --stdin');
process.exit (errorCodes.INVALID_PARAMS);

@@ -211,3 +181,2 @@ }

errorReporter.finalize && errorReporter.finalize();
return errorCount;

@@ -230,3 +199,2 @@ }

.option ('--watch, --hot', 'Enable Hot Loading (Hot Swapping)')
.option ('-s, --sync', 'Make sure that all Solium rules enabled by default are specified in your ' + SOLIUMRC_FILENAME)
.option ('-R, --reporter <name>', 'Specify the format in which to report the issues found')

@@ -237,15 +205,16 @@ .option ('--fix', 'Fix the errors where possible.');

/**
* Function that takes a name and returns an error reporter
* Takes a name and returns an error reporter
* @param {String} name Name of the reporter
* @returns {Object} reporter The reporter whose name was supplied.
*/
function getErrorReporter (name) {
if (name === 'pretty' || typeof name === 'undefined') {
return require ('./reporters/pretty');
} else if (name === 'gcc') {
return require ('./reporters/gcc');
name = name || 'pretty';
try {
return require ('./reporters/' + name);
} catch (e) {
throw new Error (
'Invalid reporter "' + name + '". Valid reporters are "gcc" and "pretty"'
);
}
throw new Error (
'Invalid reporter "' + name + '". Valid reporters are "gcc" or "pretty"'
);
}

@@ -271,3 +240,3 @@

} catch (e) {
console.error ('ERROR! ' + e.message);
console.error ('[FATAL ERROR] ' + e.message);
process.exit (errorCodes.INVALID_PARAMS)

@@ -279,13 +248,13 @@ }

} catch (e) {
console.error (
'ERROR! Couldn\'t find ' + SOLIUMRC_FILENAME + ' in the current directory.\nUse solium --init to create one.'
);
// Check if soliumrc file exists. If yes, then the file is in an invalid format.
if (fs.existsSync (SOLIUMRC_FILENAME_ABSOLUTE)) {
errorReporter.reportFatal ('An invalid ' + SOLIUMRC_FILENAME + ' was provided. ' + e.message);
} else {
errorReporter.reportFatal ('Couldn\'t find '
+ SOLIUMRC_FILENAME + ' in the current directory. Use "solium --init" to create one.');
}
process.exit (errorCodes.NO_SOLIUMRC);
}
if (cli.sync) {
synchronizeWithSoliumRules (userConfig);
return writeConfigFile (userConfig);
}
//if custom rules' file is set, make sure we have its absolute path

@@ -308,4 +277,4 @@ if (

} catch (e) {
console.error (
'There was an error trying to read \'' + SOLIUMIGNORE_FILENAME_ABSOLUTE + '\':\n' + e
errorReporter.reportInternal (
'There was an error trying to read \'' + SOLIUMIGNORE_FILENAME_ABSOLUTE + '\': ' + e.message
);

@@ -316,11 +285,7 @@ }

if (cli.stdin) {
return console.error (
'ERROR! Cannot watch files when reading from stdin'
);
return errorReporter.reportFatal ('Cannot watch files when reading from stdin');
}
if (cli.fix) {
return console.error (
'ERROR! Autofixing is currently not supported in watch mode.'
);
return errorReporter.reportFatal ('Autofixing is not supported in watch mode.');
}

@@ -342,8 +307,4 @@ }

} else {
if (errorCount > 0) {
process.exit(errorCodes.ERRORS_FOUND);
}
} else if (errorCount > 0) {
process.exit (errorCodes.ERRORS_FOUND);
}

@@ -350,0 +311,0 @@ }

@@ -10,4 +10,27 @@ /**

reportFatal: function (message) {
console.log ('[FATAL ERROR] ' + message);
},
reportInternal: function (message) {
console.log ('[WARNING] ' + message);
},
report: function (filename, sourceCode, lintErrors, fixesApplied) {
var internalIssuesExist = false;
// Examine internal issues first
lintErrors.forEach (function (issue, index) {
if (!issue.internal) {
return;
}
console.log (issue.message);
delete lintErrors [index];
internalIssuesExist = true;
});
internalIssuesExist && console.log ('\n');
lintErrors.forEach (function (error) {

@@ -14,0 +37,0 @@

@@ -20,2 +20,6 @@ /**

function colorInternalIssue (type) {
return type === 'warning' ? 'blue' : 'red';
}
var counts = {};

@@ -25,9 +29,35 @@

reportFatal: function (message) {
console.log (('[FATAL ERROR] ' + message) [colorInternalIssue ('error')]);
},
// Convenience method when only a message needs to be passed as part of an internal issue
reportInternal: function (message) {
console.log (('[WARNING] ' + message) [colorInternalIssue ('warning')]);
},
report: function (filename, sourceCode, lintErrors, fixesApplied) {
var lines = sourceCode.split ('\n');
var errorLine, line, tabCount;
var internalIssuesExist = false;
// First output all internal issues
lintErrors.forEach (function (issue, index) {
if (!issue.internal) {
return;
}
console.log (issue.message [colorInternalIssue (issue.type)]);
// Remove internal issue, so only rule errors reach the next loop
delete lintErrors [index];
internalIssuesExist = true;
});
internalIssuesExist && console.log ('\n');
console.log(('\nIn ' + filename + ':').bold);
lintErrors.forEach (function (error) {
if (error.line != errorLine) {
console.log(('\nIn ' + filename + ', line ' + error.line + ':').bold);
console.log(('\nLine ' + error.line + ':').bold);

@@ -59,3 +89,3 @@ line = lines[error.line - 1];

if (Array.isArray (fixesApplied)) {
counts.fixes = fixesApplied.length;
counts.fixes = (counts.fixes || 0) + fixesApplied.length;
}

@@ -62,0 +92,0 @@

@@ -8,7 +8,7 @@ /**

var jsUtils = require ('./utils/js-utils');
var util = require ('util'),
isErrObjectValid = require ('../config/schemas/error-reported-by-rule').validationFunc;
var INHERITABLE_METHODS = [
'getSourceCode',
'on'
'getSourceCode'
];

@@ -26,2 +26,5 @@

// Set contect attribute 'options' iff options were provided.
ruleDesc.options && Object.assign (contextObject, { options: ruleDesc.options });
//set read-only properties of the context object

@@ -55,9 +58,8 @@ Object.defineProperties (contextObject, {

if (!jsUtils.isStrictlyObject (error)) {
throw new Error (JSON.stringify (error) + ' is an invalid Error object');
if (!isErrObjectValid (error)) {
throw new Error ('Rule "' + ruleName +
'": invalid error object was passed. AJV message:\n' + util.inspect (isErrObjectValid.errors));
}
Object.assign (error, { ruleName: ruleName,
ruleMeta: ruleMeta, type: contextObject.meta.type });
Object.assign (error, { ruleName: ruleName, ruleMeta: ruleMeta, type: contextObject.meta.type });
Solium.report (error);

@@ -64,0 +66,0 @@

@@ -10,10 +10,24 @@ /**

path = require ('path'),
util = require ('util'),
jsUtils = require ('./utils/js-utils'),
ruleLoader = require ('./utils/rule-loader'),
soliumRules = require ('../config/solium').rules, //list of all rules available inside solium
rules = {};
var RULES_DIR = path.join (__dirname, 'rules'),
var loadRulesFromUpstream = ruleLoader.loadRulesFromUpstream;
var RULES_DIR = path.join (__dirname, ruleLoader.constants.SOLIUM_CORE_RULES_DIRNAME),
CWD = process.cwd (),
JS_EXT = '.js';
// Utilities for getRuleSeverity()
var configSchema = require ('../config/schemas/config'),
SchemaValidator = new require ('ajv') ({ allErrors: true }),
severityValueSchemas = configSchema.properties.rules.patternProperties ['^.+$'].oneOf;
var isValidSeverityString = SchemaValidator.compile (severityValueSchemas [0]),
isValidSeverityInt = SchemaValidator.compile (severityValueSchemas [1]),
isValidSeverityArray = SchemaValidator.compile (severityValueSchemas [2]),
isAValidPlugin = require ('../config/schemas/plugin').validationFunc;
module.exports = {

@@ -31,2 +45,3 @@

* load the user-specified rules from the rules/ directory and custom rules from specified file
* This function loads rule information from a DEPRECATED config format.
* @param {Object} userRules object whose keys specify the rule name and boolean values specify whether to include it

@@ -36,4 +51,4 @@ * @param {String} customRulesFilePath The file from where definitions of user-defined rules are loaded

*/
load: function (userRules, customRulesFilePath, noReset) {
var ruleFiles, idCounter = 1, customRules = {};
loadUsingDeprecatedConfigFormat: function (userRules, customRulesFilePath, noReset) {
var ruleFiles, idCounter = 1;

@@ -47,17 +62,5 @@ if (!jsUtils.isStrictlyObject (userRules)) {

} catch (e) {
throw new Error ('Unable to read ' + RULES_DIR + ':\n' + e.stack);
throw new Error ('Unable to read ' + RULES_DIR + ': ' + e.message);
}
if (customRulesFilePath && typeof customRulesFilePath === 'string') {
if (!path.isAbsolute (customRulesFilePath)) {
customRulesFilePath = path.join (CWD, customRulesFilePath);
}
try {
customRules = require (customRulesFilePath);
} catch (e) {
throw new Error ('Unable to read ' + customRulesFilePath + ':\n' + e);
}
}
!noReset && this.reset ();

@@ -73,3 +76,3 @@

} catch (e) {
throw new Error ('Unable to read ' + absoluteRuleFilePath + ':\n' + e);
throw new Error ('Unable to read ' + absoluteRuleFilePath + ': ' + e.message);
}

@@ -99,41 +102,105 @@ }

Object.keys (customRules).forEach (function (ruleName) {
//skip if user has not enabled custom rule ruleName
if (!userRules [ruleName]) {
return;
//if there is still a rule set to true and not yet expanded, its an invalid rule (neither built-in nor custom)
Object.keys (userRules).forEach (function (ruleName) {
if (typeof userRules [ruleName] !== 'object') {
throw new Error ('Rule ' + ruleName + ' was not found');
}
});
if (rules [ruleName]) {
/**
* Gets executed when a pre-defined and a custom rule have a name clash.
* The custom rule over-writes the pre-defined one.
*/
return userRules;
},
/**
* Load Solium rules as described in the configuration object provided.
* @param {Object} config The configuration object (read from soliumrc) that describes what rules the user wishes to apply.
* @param {Boolean} noReset Determines whether to re-initilize internal variables or not. If this param has a false-equivalent value, data is reset.
* @returns {Object} userRules Definitions of all user-requested rules. Throws error if a rule in userRules is not amongst available rules
*/
load: function (config, noReset) {
var ruleDescriptions = {}, ruleConfigs = {}, getRuleSeverity = this.getRuleSeverity;
!noReset && this.reset ();
// If plugins are passed, ensure all of them are installed in the same scope as Solium.
// If not, provide appropriate error messages, instructions & doc links.
if (config.plugins && config.plugins.length) {
config.plugins.forEach (function (pName) {
// User must only provide the plugin name, not the solium plugin prefix string
var plugin;
pName = ruleLoader.constants.SOLIUM_PLUGIN_PREFIX + pName;
try {
plugin = require (pName);
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
// Plugin is not installed in Solium's scope
throw new Error (
'Oops! The Plugin "' + pName + '" was not found.' +
'\n\nPlease make sure that it is installed globally by running "npm install -g ' + pName + '"'
);
}
// Some other error
throw new Error (
'Oops! An error occured while trying to load the plugin "' +
pName + '".' + e.message +
'\n\nPlease see http://solium.readthedocs.io/en/latest/user-guide.html#plugins for plugin usage.'
);
}
// If plugin was loaded successfully, validate it using schema.
if (!isAValidPlugin (plugin)) {
throw new Error (
'"' + pName + '" is not a valid plugin.' +
'\nPlease see http://solium.readthedocs.io/en/latest/developer-guide.html#developing-a-plugin' +
' for plugin development. AJV Message:\n' + util.inspect (isAValidPlugin.errors)
);
}
});
}
if (config.extends) {
try {
Object.assign (ruleConfigs, ruleLoader.resolveUpstream (config.extends));
} catch (e) {
throw new Error (
'An error occured while trying to resolve dependancy "' + config.extends + '": ' + e.message);
}
}
rules [ruleName] = {
verify: customRules [ruleName]
};
// If both extends & rules attributes exist, the rules imported from "rules" attr will override any rules
// imported from "extends" in case of a name clash.
if (config.rules && Object.keys (config.rules).length) {
Object.assign (ruleConfigs, config.rules);
}
//in case of rule name clash, assign the custom rule the same id as the pre-defined rule with the same name
userRules [ruleName] = {
enabled: true,
custom: true,
type: 'custom-error',
id: (
userRules [ruleName] && userRules [ruleName].id ?
userRules [ruleName].id : idCounter++
)
// Remove all rules that are disabled.
Object.keys (ruleConfigs).forEach (function (name) {
getRuleSeverity (ruleConfigs [name]) < 1 && delete ruleConfigs [name];
});
// Load all enabled rules.
try {
Object.assign (rules, ruleLoader.load (Object.keys (ruleConfigs)));
} catch (e) {
throw new Error ('An error occured while trying to load rules: ' + e.message);
}
// Use rule definitions & configs to generate ruleDescriptions
Object.keys (rules).forEach (function (name) {
var desc = {
description: rules [name].meta.docs.description,
recommended: rules [name].meta.docs.recommended,
type: (getRuleSeverity (ruleConfigs [name]) === 1) ? 'warning' : 'error'
};
});
// Only set "options" attribute if the rule config is an array of length is at least 2.
if (Array.isArray (ruleConfigs [name]) && ruleConfigs [name].length > 1) {
desc.options = ruleConfigs [name].slice (1);
}
//if there is still a rule set to true and not yet expanded, its an invalid rule (neither built-in nor custom)
Object.keys (userRules).forEach (function (ruleName) {
if (typeof userRules [ruleName] !== 'object') {
throw new Error ('Rule ' + ruleName + ' was not found');
}
ruleDescriptions [name] = desc;
});
return userRules;
return ruleDescriptions;
},

@@ -151,4 +218,27 @@

}
},
/**
* Get severity value for a rule from its given configuration description.
* @param {Integer|String|Array} ruleConfig configuration for the rule (picked up from soliumrc)
* @returns {Integer} severity Either 0 (rule turned off), 1 (warning) or 2 (error).
*/
getRuleSeverity: function getRuleSeverity (ruleConfig) {
if (isValidSeverityInt (ruleConfig)) {
return ruleConfig;
}
if (isValidSeverityString (ruleConfig)) {
return ({
'off': 0, 'warning': 1, 'error': 2
}) [ruleConfig];
}
if (isValidSeverityArray (ruleConfig)) {
return getRuleSeverity (ruleConfig [0]);
}
throw new Error ('Invalid configuration value for rule.');
}
};

@@ -10,7 +10,21 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Ensure that array declarations don\'t have space between the type and brackets'
},
schema: [],
fixable: 'whitespace'
},
create: function (context) {
var sourceCode = context.getSourceCode ();
context.on ('Type', function (emitted) {
function inspectType (emitted) {
var node = emitted.node;

@@ -22,28 +36,53 @@

var code = sourceCode.getText (node),
isSpaceBetweenLiteralAndBracket = !/\w/.test ( code.slice (code.indexOf ('[')-1, code.indexOf ('[')) );
var code = sourceCode.getText (node), whitespaceDetector = /\s*(?=\[)/;
isSpaceBetweenLiteralAndBracket && context.report ({
// First the regex must detect whitespace between literal and opening bracket
var scanned = whitespaceDetector.exec (code),
whitespaceString = scanned [0], index = scanned.index;
whitespaceString !== '' && context.report ({
node: node,
location: { column: index + 1 },
message: (
'There should be no whitespace between literal ' +
(typeof node.literal === 'string' ? node.literal + ' ' : '') +
'and \'[]\''
)
'There should be no whitespace between "'
+ code.slice (0, index) + '" and the opening square bracket.'
),
fix: function (fixer) {
return [fixer.removeRange (
[node.start + index, node.start + index + whitespaceString.length])];
}
});
if (
node.array_parts.length === 1 &&
node.array_parts [0] === null &&
code.slice (code.indexOf ('[')) !== '[]'
) {
context.report ({
// Next, the regex must detect whitespace between opening & closing brackets
// Since this regex doesn't exclude the brackets themselves, we have to move positions to acquire
// the right info.
if (node.array_parts.length === 1 && node.array_parts [0] === null) {
var bracketString, indexWs;
whitespaceDetector = /\[(\s*)\]/;
scanned = whitespaceDetector.exec (code);
bracketString = scanned [0], whitespaceString = scanned [1], index = scanned.index, indexWs = index + 1;
(whitespaceString !== '') && context.report ({
node: node,
message: 'There should be no whitespace between square brackets. Use [] instead.'
location: { column: indexWs + 1 },
fix: function (fixer) {
return [fixer.replaceTextRange (
[node.start + index, node.start + index + bracketString.length], '[]')];
},
message: 'There should be no whitespace between opening & closing square brackets. Use [] instead.'
});
}
});
}
return {
Type: inspectType
};
}
};
};

@@ -10,4 +10,16 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure that there is exactly a 2-line gap between Contract and Funtion declarations'
},
schema: []
},
create: function (context) {
var sourceCode = context.getSourceCode (),

@@ -18,3 +30,3 @@ noCodeRegExp = /^[ \t]*$/,

context.on ('Program', function (emitted) {
function inspectProgram (emitted) {
var node = emitted.node, body = node.body;

@@ -54,3 +66,3 @@

}
});
}

@@ -95,8 +107,15 @@

topLevelDeclarations.forEach (function (event) {
context.on (event, inspectChild)
var listeners = {
Program: inspectProgram
};
topLevelDeclarations.forEach (function (node) {
listeners [node] = inspectChild;
});
return listeners;
}
};

@@ -10,6 +10,18 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure that contract, library, modifier and struct names follow CamelCase notation'
},
schema: []
},
create: function (context) {
var camelCaseRegEx = /^([A-Z][A-Za-z0-9]+)+$/;
var eventsToWatch = {
var nodesToWatch = {
'ContractStatement': 'Contract',

@@ -21,4 +33,4 @@ 'LibraryStatement': 'Library',

function inspectEventsToWatch (event) {
context.on (event, function (emitted) {
function createInspector (nodeDesc) {
return (function inspect (emitted) {
var node = emitted.node;

@@ -33,3 +45,3 @@

node: node,
message: eventsToWatch [event] + ' name \'' + node.name + '\' doesn\'t follow the CamelCase notation.'
message: nodeDesc + ' name \'' + node.name + '\' doesn\'t follow the CamelCase notation.'
});

@@ -40,6 +52,11 @@ }

Object.keys (eventsToWatch).forEach (inspectEventsToWatch);
return Object.keys (nodesToWatch).reduce (function (listeners, nodeName) {
listeners [nodeName] = createInspector (nodesToWatch [nodeName]);
return listeners;
}, {});
}
};
};

@@ -14,6 +14,19 @@ /**

module.exports = {
verify: function (context) {
context.on ('CallExpression', function (emittedObject) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Suggest replacing deprecated \'suicide\' for \'selfdestruct\''
},
schema: [],
fixable: 'code'
},
create: function (context) {
function inspectCallExpression (emittedObject) {
if (!emittedObject.exit) {

@@ -29,10 +42,16 @@ return;

node: emittedObject.node,
message: "'suicide' is deprecated. Use 'selfdestruct' instead",
fix: function (fixer) {
return [fixer.replaceTextRange ([callee.start, callee.end], 'selfdestruct')];
},
message: '\'suicide\' is deprecated. Use \'selfdestruct\' instead.'
});
}
}
});
return {
CallExpression: inspectCallExpression
};
}
}
}
};

@@ -26,4 +26,18 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Ensure that string are quoted with double-quotes only',
replacedBy: ['quotes']
},
schema: [],
deprecated: true
},
create: function (context) {
var doubleQuotesLiteralRegExp = /^\".*\"$/,

@@ -50,6 +64,8 @@ sourceCode = context.getSourceCode ();

context.on ('Literal', inspectLiteral);
return {
Literal: inspectLiteral
};
}
};
};

@@ -34,4 +34,16 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Ensure that all import statements are on top of the file'
},
schema: []
},
create: function (context) {
function inspectImportStatement (emitted) {

@@ -65,3 +77,5 @@ var node = emitted.node,

context.on ('ImportStatement', inspectImportStatement);
return {
ImportStatement: inspectImportStatement
};

@@ -68,0 +82,0 @@ }

@@ -14,17 +14,45 @@ /**

verify: function (context) {
meta: {
var sourceCode = context.getSourceCode ();
docs: {
recommended: true,
type: 'warning',
description: 'Ensure consistent indentation of 4 spaces per level'
},
var BASE_INDENTATION_STYLE = ' ',
BASE_INDENTATION_STYLE_DESC = '4 SPACES',
BASE_INDENTATION_STYLE_REGEXP_GLOBAL = new RegExp (BASE_INDENTATION_STYLE, 'g');
schema: [{
oneOf: [
{ type: 'string', enum: ['tab'] },
{ type: 'integer', minimum: 0 }
]
}]
var topLevelDeclarations = [
'ContractStatement',
'LibraryStatement'
];
},
create: function (context) {
// default configurations
var BASE_INDENTATION_STYLE = ' '.repeat (4),
BASE_INDENTATION_STYLE_DESC = '4 SPACES';
if (context.options) {
if (context.options [0] === 'tab') {
BASE_INDENTATION_STYLE = '\t';
BASE_INDENTATION_STYLE_DESC = '1 TAB';
} else {
var spCount = context.options [0];
BASE_INDENTATION_STYLE = ' '.repeat (spCount);
BASE_INDENTATION_STYLE_DESC = spCount + ' SPACE' + (spCount === 1 ? '' : 'S'); // grammar-nazi-proof error messages
}
}
var BASE_INDENTATION_STYLE_REGEXP_GLOBAL = new RegExp (BASE_INDENTATION_STYLE, 'g');
var sourceCode = context.getSourceCode ();
var topLevelDeclarations = ['ContractStatement', 'LibraryStatement'];
//Ensure NO indentation exists before top-level declarations (contract, library)
context.on ('Program', function (emitted) {
function inspectProgram (emitted) {
var node = emitted.node;

@@ -80,3 +108,3 @@

node.body.forEach (inspectProgramChild);
});
}

@@ -86,50 +114,46 @@

//Ensure level 1 indentation before all immediate children of top-level declarations
topLevelDeclarations.forEach (function (event) {
function inspectTopLevelDeclaration (emitted) {
var body = emitted.node.body || [],
levelOneIndentRegExp = new RegExp ('^\\n' + BASE_INDENTATION_STYLE + '$'),
endingLineRegExp = new RegExp ('^' + BASE_INDENTATION_STYLE + '(\\S| *)$'); //either a non-whitespace character or 1 extra whitespace followed by * (closing of multi-line comment)
context.on (event, function (emitted) {
var body = emitted.node.body || [],
levelOneIndentRegExp = new RegExp ('^\\n' + BASE_INDENTATION_STYLE + '$'),
endingLineRegExp = new RegExp ('^' + BASE_INDENTATION_STYLE + '(\\S| *)$'); //either a non-whitespace character or 1 extra whitespace followed by * (closing of multi-line comment)
if (emitted.exit) {
return;
}
if (emitted.exit) {
return;
function inspectChild (child) {
var prevChars = sourceCode.getPrevChars (child, BASE_INDENTATION_STYLE.length+1),
endingLineNum = sourceCode.getEndingLine (child);
//if the start of node doesn't follow correct indentation
if (!levelOneIndentRegExp.test (prevChars)) {
context.report ({
node: child,
message: 'Incorrect indentation: Make sure you use ' + BASE_INDENTATION_STYLE_DESC + ' per level and don\'t precede the code by any comments.'
});
}
function inspectChild (child) {
var prevChars = sourceCode.getPrevChars (child, BASE_INDENTATION_STYLE.length+1),
endingLineNum = sourceCode.getEndingLine (child);
//if the start of node doesn't follow correct indentation
if (!levelOneIndentRegExp.test (prevChars)) {
context.report ({
node: child,
message: 'Incorrect indentation: Make sure you use ' + BASE_INDENTATION_STYLE_DESC + ' per level and don\'t precede the code by any comments.'
});
}
//if the node doesn't end on the same line as it starts and the ending line doesn't follow correct indentation
if (
sourceCode.getLine (child) !== endingLineNum &&
!endingLineRegExp.test (sourceCode.getTextOnLine (endingLineNum).slice (0, 5))
) {
context.report ({
node: child,
location: {
line: endingLineNum,
column: 0
},
message: 'Incorrect indentation: Make sure you use ' + BASE_INDENTATION_STYLE_DESC + ' per level and don\'t precede the code by any comments.'
});
}
//if the node doesn't end on the same line as it starts and the ending line doesn't follow correct indentation
if (
sourceCode.getLine (child) !== endingLineNum &&
!endingLineRegExp.test (sourceCode.getTextOnLine (endingLineNum).slice (0, 5))
) {
context.report ({
node: child,
location: {
line: endingLineNum,
column: 0
},
message: 'Incorrect indentation: Make sure you use ' + BASE_INDENTATION_STYLE_DESC + ' per level and don\'t precede the code by any comments.'
});
}
}
body.forEach (inspectChild);
});
body.forEach (inspectChild);
}
});
//Ensure 1 extra indentation inside Block than before it
context.on ('BlockStatement', function (emitted) {
function inspectBlockStatement (emitted) {
var node = emitted.node;

@@ -200,7 +224,7 @@

node.body.forEach (inspectBlockItem.bind (null, blockIndent));
});
}
context.on ('StructDeclaration', function (emitted) {
function inspectStructDeclaration (emitted) {
var node = emitted.node,

@@ -210,3 +234,2 @@ body = node.body || [],

var structDeclarationLineText, currentIndent, currentIndentLevel, structIndent;
var MAX_ATTR_IN_SINGLE_LINE = 3;

@@ -230,8 +253,2 @@ function inspectStructAttr (structIndent, attr) {

if (sourceCode.getLine (node) === endingLineNum) {
if (body.length > MAX_ATTR_IN_SINGLE_LINE) {
context.report ({
node: node,
message: '\'' + node.name + '\': In case of more than 3 properties, struct declaration needs to be spread over multiple lines with 1 property per line.'
});
}
return;

@@ -257,11 +274,10 @@ }

});
}
context.on ('ArrayExpression', function (emitted) {
function inspectArrayExpression (emitted) {
var node = emitted.node, elements = node.elements;
var endingLineNum = sourceCode.getEndingLine (node),
arrayExpressionLineText, currentIndent, currentIndentLevel, arrayIndent;
var MAX_ELEMS_IN_SINGLE_LINE = 3;

@@ -284,8 +300,2 @@ function inspectElement (arrayIndent, elem) {

if (sourceCode.getLine (node) === endingLineNum) {
if (elements.length > MAX_ELEMS_IN_SINGLE_LINE) {
context.report ({
node: node,
message: 'In case of more than 3 elements, array expression needs to be spread over multiple lines with 1 element per line.'
});
}
return;

@@ -310,9 +320,8 @@ }

elements.forEach (inspectElement.bind (null, arrayIndent));
});
}
//function params (if on multiple lines)
context.on ('FunctionDeclaration', function (emitted) {
function inspectFunctionDeclaration (emitted) {
var node = emitted.node, params = node.params || [];
var MAX_PARAMS_ON_SINGLE_LINE = 3;

@@ -339,8 +348,2 @@ var startLine = sourceCode.getLine (node),

if (startLine === lastArgLine) {
if (params.length > MAX_PARAMS_ON_SINGLE_LINE) {
context.report ({
node: node,
message: 'Function \'' + node.name + '\': in case of more than 3 parameters, drop each into its own line.'
});
}
return;

@@ -366,6 +369,6 @@ }

params.forEach (inspectParam.bind (null, paramIndent));
});
}
context.on ('CallExpression', function (emitted) {
function inspectCallExpression (emitted) {

@@ -375,3 +378,2 @@ var node = emitted.node;

callExpressionLineText, currentIndent, currentIndentLevel, argIndent;
var MAX_ARGS_ON_SINGLE_LINE = 3;

@@ -394,8 +396,2 @@ function inspectArgument (argIndent, argument) {

if (sourceCode.getLine (node) === endingLineNum) {
if (node.arguments.length > MAX_ARGS_ON_SINGLE_LINE) {
context.report ({
node: node,
message: 'Function \'' + node.callee.name + '\': in case of more than 3 arguments, drop each into its own line.'
});
}
return;

@@ -422,6 +418,22 @@ }

}
var response = {
CallExpression: inspectCallExpression,
FunctionDeclaration: inspectFunctionDeclaration,
ArrayExpression: inspectArrayExpression,
StructDeclaration: inspectStructDeclaration,
BlockStatement: inspectBlockStatement,
Program: inspectProgram
};
topLevelDeclarations.forEach (function (nodeName) {
response [nodeName] = inspectTopLevelDeclaration;
});
return response;
}
};

@@ -11,7 +11,19 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Ensure that every if, for, while and do statement is followed by an opening curly brace \'{\' on the same line'
},
schema: []
},
create: function (context) {
var sourceCode = context.getSourceCode ();
context.on ('IfStatement', function (emitted) {
function inspectIfStatement (emitted) {
var node = emitted.node;

@@ -167,6 +179,6 @@

});
});
}
context.on ('ForStatement', function (emitted) {
function inspectForStatement (emitted) {
var node = emitted.node,

@@ -212,6 +224,6 @@ lastNodeOnStartingLine = node.update || node.test || node.init;

}
});
}
context.on ('WhileStatement', function (emitted) {
function inspectWhileStatement (emitted) {
var node = emitted.node,

@@ -251,6 +263,6 @@ startingLine = sourceCode.getLine (node);

}
});
}
context.on ('DoWhileStatement', function (emitted) {
function inspectDoWhileStatement (emitted) {
var node = emitted.node,

@@ -290,3 +302,3 @@ startingLine = sourceCode.getLine (node);

}
});
}

@@ -298,3 +310,3 @@

*/
context.on ('WithStatement', function (emitted) {
function inspectWithStatement (emitted) {
var node = emitted.node;

@@ -326,6 +338,6 @@

}
});
}
context.on ('StructDeclaration', function (emitted) {
function inspectStructDeclaration (emitted) {
var node = emitted.node,

@@ -342,6 +354,6 @@ code = sourceCode.getText (node).replace ('struct ' + node.name, '');

});
});
}
context.on ('ContractStatement', function (emitted) {
function inspectContractStatement (emitted) {
var node = emitted.node,

@@ -358,6 +370,6 @@ code = sourceCode.getTextOnLine (sourceCode.getLine (node));

});
});
}
context.on ('LibraryStatement', function (emitted) {
function inspectLibraryStatement (emitted) {
var node = emitted.node,

@@ -374,6 +386,6 @@ code = sourceCode.getTextOnLine (sourceCode.getLine (node));

});
});
}
context.on ('FunctionDeclaration', function (emitted) {
function inspectFunctionDeclaration (emitted) {
var node = emitted.node;

@@ -470,5 +482,16 @@

});
}
});
return {
FunctionDeclaration: inspectFunctionDeclaration,
LibraryStatement: inspectLibraryStatement,
ContractStatement: inspectContractStatement,
StructDeclaration: inspectStructDeclaration,
WithStatement: inspectWithStatement,
DoWhileStatement: inspectDoWhileStatement,
WhileStatement: inspectWhileStatement,
ForStatement: inspectForStatement,
IfStatement: inspectIfStatement
};

@@ -475,0 +498,0 @@ }

@@ -10,4 +10,16 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure that all variable, function and parameter names follow the mixedCase naming convention'
},
schema: []
},
create: function (context) {
var mixedCaseRegEx = /^_?[a-z][a-zA-Z0-9]*_?$/;

@@ -26,26 +38,25 @@ var similarNodes = [

similarNodes.forEach (function (event) {
context.on (event, function (emitted) {
var node = emitted.node;
function inspectFuncOrModif (emitted) {
var node = emitted.node;
/**
* If node's parent is contract / library and node is either a modifier (which means Inheritance), do not apply mixedcase
*/
if (
emitted.exit ||
(
(node.parent.type === 'ContractStatement' || node.parent.type === 'LibraryStatement') &&
(node.type === 'FunctionDeclaration' && node.parent.name === node.name)
)
) {
return;
}
/**
* If node's parent is contract / library and node is either a modifier (which means Inheritance),
* do not apply mixedcase
*/
if (
emitted.exit ||
(
(node.parent.type === 'ContractStatement' || node.parent.type === 'LibraryStatement') &&
(node.type === 'FunctionDeclaration' && node.parent.name === node.name)
)
) {
return;
}
if (!mixedCaseRegEx.test (node.name)) {
report (node, node.name);
}
});
});
if (!mixedCaseRegEx.test (node.name)) {
report (node, node.name);
}
}
context.on ('VariableDeclarator', function (emitted) {
function inspectVariableDeclarator (emitted) {
var node = emitted.node;

@@ -63,5 +74,5 @@

}
});
}
context.on ('DeclarativeExpression', function (emitted) {
function inspectDeclarativeExpression (emitted) {
var node = emitted.node;

@@ -76,5 +87,5 @@

}
});
}
context.on ('InformalParameter', function (emitted) {
function inspectInformalParameter (emitted) {
var node = emitted.node;

@@ -86,9 +97,27 @@

if (!mixedCaseRegEx.test (node.id)) {
/**
* node.is could either be an object containing "name" or null.
* It is null when there is no name (eg- `function foo() returns(bool) {}`)
* Here, bool's node will have "literal" object but "id" as null.
*/
if (node.id && !mixedCaseRegEx.test (node.id)) {
report (node, node.id);
}
}
var response = {
InformalParameter: inspectInformalParameter,
DeclarativeExpression: inspectDeclarativeExpression,
VariableDeclarator: inspectVariableDeclarator
};
similarNodes.forEach (function (nodeName) {
response [nodeName] = inspectFuncOrModif;
});
return response;
}
};
};

@@ -10,4 +10,16 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure that no empty blocks {} exist'
},
schema: []
},
create: function (context) {
function report (node, loc) {

@@ -23,19 +35,16 @@ context.report ({

similarNodeTypes.forEach (function (event) {
context.on (event, function (emitted) {
var node = emitted.node,
sourceCode = context.getSourceCode (),
text = sourceCode.getText (node);
function inspectCLI (emitted) {
var node = emitted.node,
sourceCode = context.getSourceCode (), text = sourceCode.getText (node);
if (emitted.exit) {
return;
}
if (emitted.exit) {
return;
}
if (!node.body.length) {
report (node, { column: text.indexOf ('{') });
}
});
});
if (!node.body.length) {
report (node, { column: text.indexOf ('{') });
}
}
context.on ('BlockStatement', function (emitted) {
function inspectBlockStatement (emitted) {
var node = emitted.node;

@@ -54,6 +63,17 @@

}
}
var response = {
BlockStatement: inspectBlockStatement
};
similarNodeTypes.forEach (function (nodeName) {
response [nodeName] = inspectCLI;
});
return response;
}
};
};

@@ -10,8 +10,20 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Flag all the variables that were declared but never used'
},
schema: []
},
create: function (context) {
var allVariableDeclarations = {};
//collect all variable declarations from VariableDeclarators and DeclarativeExpressions
context.on ('VariableDeclarator', function (emitted) {
function inspectVariableDeclarator (emitted) {
var node = emitted.node;

@@ -22,5 +34,5 @@

}
});
}
context.on ('DeclarativeExpression', function (emitted) {
function inspectDeclarativeExpression (emitted) {
var node = emitted.node;

@@ -32,8 +44,8 @@

}
});
}
//While exiting Progam Node, all the vars that haven't been used still exist inside VariableDeclarations. Report them
context.on ('Program', function (emitted) {
function inspectProgram (emitted) {
if (emitted.exit) {
Object.keys (allVariableDeclarations).forEach (function (name) {

@@ -45,8 +57,8 @@ context.report ({

});
}
});
}
//As soon as the first use of a variable is encountered, delete that variable's node from allVariableDeclarations
context.on ('Identifier', function (emitted) {
function inspectIdentifier (emitted) {
if (!emitted.exit) {

@@ -63,6 +75,14 @@ var node = emitted.node,

}
});
}
return {
Identifier: inspectIdentifier,
Program: inspectProgram,
DeclarativeExpression: inspectDeclarativeExpression,
VariableDeclarator: inspectVariableDeclarator
};
}
};
};

@@ -10,5 +10,18 @@ /**

verify: function (context) {
meta: {
context.on ('WithStatement', function (emitted) {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure no use of with statements in the code'
},
deprecated: true,
schema: []
},
create: function (context) {
function inspectWithStatement (emitted) {
if (emitted.exit) {

@@ -22,6 +35,10 @@ return;

});
});
}
return {
WithStatement: inspectWithStatement
};
}
};

@@ -10,7 +10,67 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'error',
description: 'Ensure that operators are surrounded by a single space on either side'
},
schema: []
},
create: function (context) {
var sourceCode = context.getSourceCode ();
context.on ('BinaryExpression', function (emitted) {
function inspectAssignmentExpression (emitted) {
/**
* node.operator is refined here by adding backslash before all the 'special' characters.
* 'special' chars are thos chars that are part of solidity assignment operators and, if used without backslash in JS RegExp,
* behave as wildcard characters. So to make sure they're treated as simple strings, we add '\' before them.
* As of today, these chars include: * / + | ^
*/
var node = emitted.node,
op = node.operator.replace (/([\+\*\/\|\^])/g, '\\$1'), opLength = node.operator.length;
if (emitted.exit) {
return;
}
// If expression is 'abc *= def;', then charsAfterLeftNode will contain ' *= d'.
var charsAfterLeftNode = sourceCode.getNextChars (node.left, 3 + opLength),
validationRegexp = new RegExp ('^ ' + op + ' [^\\s]$');
(!validationRegexp.test (charsAfterLeftNode)) && context.report ({
node: node.left,
message: 'Assignment operator must have exactly single space on both sides of it.'
});
}
//statement like `var x = 10` doesn't come under AssignmentExpression, so needs to be checked separately
function inspectVariableDeclaration (emitted) {
var node = emitted.node, code = sourceCode.getText (node);
if (emitted.exit) {
return;
}
//if a particular character is '=', check its left and right for single space
for (var i = 2; i < code.length; i++) {
if (code [i] === '=') {
(!/^[^\/\s] $/.test (code.slice (i-2, i))) && context.report ({
node: node,
message: 'There should be only a single space between assignment operator \'=\' and its left side.'
});
(!/^ [^\/\s]$/.test (code.slice (i+1, i+3))) && context.report ({
node: node,
message: 'There should be only a single space between assignment operator \'=\' and its right side.'
});
}
}
}
function inspectBinaryExpression (emitted) {
var leftNode,

@@ -124,6 +184,12 @@ node = emitted.node;

});
}
return {
BinaryExpression: inspectBinaryExpression,
VariableDeclaration: inspectVariableDeclaration,
AssignmentExpression: inspectAssignmentExpression
};
}
};
};

@@ -10,8 +10,30 @@ /**

verify: function (context) {
meta: {
context.on ('Program', function (emitted) {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure a) A PRAGMA directive exists and b) its on top of the file'
},
schema: [],
fixable: 'code'
},
create: function (context) {
var missinNodeOnTopErrorReported = false;
/**
* This executes only when we're leaving the Program node. At that time,
* we check whether the "missing pragma on top" error has already been reported or not.
* If not, we proceed to report it here. This happens when there are no pragma statements in
* the entire file. If there is one (but not on top of file), it gets reported by inspectPragmaStatement().
*/
function inspectProgram (emitted) {
var node = emitted.node, body = node.body;
if (emitted.exit) {
if (!emitted.exit || missinNodeOnTopErrorReported) {
return;

@@ -24,10 +46,8 @@ }

});
});
}
context.on ('PragmaStatement', function (emitted) {
function inspectPragmaStatement (emitted) {
var node = emitted.node,
sourceCode = context.getSourceCode (),
pragmaParent = sourceCode.getParent (node),
error;
sourceCode = context.getSourceCode (), pragmaParent = sourceCode.getParent (node), error;

@@ -38,2 +58,4 @@ if (emitted.exit) {

var pragmaCode = sourceCode.getText (node);
/**

@@ -46,4 +68,3 @@ * Raise error if Pragma statement is not a direct child of the program

node: node,
message: 'Pragma Directive \"' + sourceCode.getText (node) +
'\" cannot be inside ' + pragmaParent.type
message: 'Pragma Directive "' + pragmaCode + '" cannot be inside ' + pragmaParent.type
};

@@ -58,12 +79,21 @@

node: node,
message: 'Pragma Directive \"' + sourceCode.getText (node) +
'\" should only be at the top of the file.'
message: 'Pragma Directive "' + pragmaCode + '" should only be at the top of the file.',
fix: function (fixer) {
return [fixer.remove (node),
fixer.insertTextBefore (pragmaParent.body [0], pragmaCode + '\n')];
}
};
missinNodeOnTopErrorReported = true;
return context.report (error);
}
});
}
return {
Program: inspectProgram,
PragmaStatement: inspectPragmaStatement
};
}
};

@@ -10,4 +10,16 @@ /**

verify: function (context) {
meta: {
docs: {
recommended: true,
type: 'warning',
description: 'Ensure that all constants (and only constants) contain only upper case letters and underscore'
},
schema: []
},
create: function (context) {
var upperCaseRegEx = /^[A-Z][A-Z_0-9]*[A-Z0-9]$/;

@@ -22,3 +34,3 @@

context.on ('StateVariableDeclaration', function (emitted) {
function inspectStateVariableDeclaration (emitted) {
var node = emitted.node;

@@ -33,6 +45,10 @@

reportNode (node);
});
}
return {
StateVariableDeclaration: inspectStateVariableDeclaration
};
}
};
};

@@ -10,9 +10,24 @@ /**

verify: function (context) {
meta: {
var disallowedNames = ['l', 'O', 'I'];
docs: {
recommended: true,
type: 'error',
description: 'Ensure that names "l", "O" & "I" are not used for variables'
},
schema: [{
type: 'array',
items: { type: 'string', minLength: 1 },
minItems: 1
}]
},
create: function (context) {
var disallowedNames = context.options ? context.options [0] : ['l', 'O', 'I'];
function inspectVariableDeclarator (emitted) {
var node = emitted.node,
variableName = node.id.name;
var node = emitted.node, variableName = node.id.name;

@@ -27,3 +42,3 @@ if (emitted.exit) {

node: node,
message: 'Using \'' + variableName + '\' for a variable name should be avoided.'
message: 'Using "' + variableName + '" for a variable name should be avoided.'
});

@@ -35,4 +50,3 @@ }

function inspectDeclarativeExpression (emitted) {
var node = emitted.node,
variableName = node.name;
var node = emitted.node, variableName = node.name;

@@ -53,7 +67,14 @@ if (emitted.exit) {

context.on ('VariableDeclarator', inspectVariableDeclarator);
context.on ('DeclarativeExpression', inspectDeclarativeExpression);
// Aliased because both the inspect functions access the same property "name" of the node passed to them.
// So no need for 2 separate functions for now (will create separate when different attrs are accessed in future)
var inspectStateVariableDeclaration = inspectDeclarativeExpression;
return {
VariableDeclarator: inspectVariableDeclarator,
DeclarativeExpression: inspectDeclarativeExpression,
StateVariableDeclaration: inspectStateVariableDeclaration
};
}
};
};

@@ -10,79 +10,19 @@ /**

verify: function (context) {
meta: {
var sourceCode = context.getSourceCode ();
docs: {
recommended: true,
type: 'warning',
description: 'Specify where whitespace is suitable and where it isn\'t'
},
//same could potentially be applied to FunctionDeclaration
context.on ('CallExpression', function (emitted) {
var node = emitted.node,
callArgs = node.arguments;
schema: []
function inspectCallArgForCommaWhitespace (arg) {
var charAfterArg = sourceCode.getNextChar (arg);
},
(charAfterArg !== ',') && context.report ({
node: arg,
location: {
column: sourceCode.getEndingColumn (arg) + 1
},
message: 'There should be no whitespace or comments between argument and the comma following it.'
});
}
create: function (context) {
if (emitted.exit) {
return;
}
var sourceCode = context.getSourceCode ();
var nodeCode = sourceCode.getText (node);
//for a 0-argument call, ensure that name is followed by '()'
if (!callArgs.length) {
for (var i = nodeCode.length; i > 0; i--) {
if (nodeCode [i] === ')' && nodeCode [i-1] === '(') {
return;
}
if (/[\s\(\)]/.test (nodeCode [i])) {
break;
}
}
return context.report ({
node: node,
message: '\"' + nodeCode + '\": ' +
'A call without arguments should have brackets without any whitespace between them, like \'functionName ()\'.'
});
}
//CHECKING FOR COMMA WHITESPACE
callArgs.slice (0, -1).forEach (inspectCallArgForCommaWhitespace);
var lastCallArg = callArgs.slice (-1) [0];
//if call spans over multiple lines (due to too many arguments), below rules don't apply
if (sourceCode.getLine (node) !== sourceCode.getEndingLine (lastCallArg)) {
return;
}
var charBeforeFirstArg = sourceCode.getPrevChar (callArgs [0]),
charAfterLastCallArg = sourceCode.getNextChar (lastCallArg);
(callArgs [0].type !== 'NameValueAssignment' && charBeforeFirstArg !== '(') && context.report ({
node: callArgs [0],
location: {
column: sourceCode.getColumn (callArgs [0]) - 1
},
message: '\'' + node.callee.name + '\': The first argument must not be preceded by any whitespace or comments (only \'(\').'
});
(lastCallArg.type !== 'NameValueAssignment' && charAfterLastCallArg !== ')') && context.report ({
node: callArgs [0],
location: {
column: sourceCode.getEndingColumn (lastCallArg) + 1
},
message: '\'' + node.callee.name + '\': The last argument must not be succeded by any whitespace or comments (only \')\').'
});
});
context.on ('NameValueAssignment', function (emitted) {
function inspectNameValueAssignment (emitted) {
if (emitted.exit) {

@@ -124,6 +64,6 @@ return;

});
});
}
context.on ('MemberExpression', function (emitted) {
function inspectMemberExpression (emitted) {
var node = emitted.node,

@@ -162,6 +102,6 @@ property = node.property;

});
});
}
context.on ('BlockStatement', function (emitted) {
function inspectBlockStatement (emitted) {
var node = emitted.node, blockBody = node.body,

@@ -208,20 +148,8 @@ lastBlockItem = blockBody.slice (-1) [0]; //if block is empty, this becomes undefined

});
});
}
context.on ('ObjectExpression', function (emitted) {
function inspectObjectExpression (emitted) {
var node = emitted.node, properties = node.properties;
function inspectObjectPropForWhitespace (prop) {
var charAfterProp = sourceCode.getNextChar (prop);
(charAfterProp !== ',') && context.report ({
node: prop,
location: {
column: sourceCode.getEndingColumn (prop) + 1
},
message: 'There should be no whitespace or comments between object property and the comma following it.'
});
}
if (emitted.exit) {

@@ -243,5 +171,2 @@ return;

//CHECK FOR COMMA WHITESPACE
properties.slice (0, -1).forEach (inspectObjectPropForWhitespace);
var lastProperty = lastProperty = properties.slice (-1) [0];

@@ -272,330 +197,13 @@

});
});
}
return {
ObjectExpression: inspectObjectExpression,
BlockStatement: inspectBlockStatement,
MemberExpression: inspectMemberExpression,
NameValueAssignment: inspectNameValueAssignment
};
context.on ('AssignmentExpression', function (emitted) {
/**
* node.operator is refined here by adding backslash before all the 'special' characters.
* 'special' chars are thos chars that are part of solidity assignment operators and, if used without backslash in JS RegExp,
* behave as wildcard characters. So to make sure they're treated as simple strings, we add '\' before them.
* As of today, these chars include: * / + | ^
*/
var node = emitted.node,
op = node.operator.replace (/([\+\*\/\|\^])/g, '\\$1'), opLength = node.operator.length;
if (emitted.exit) {
return;
}
// If expression is 'abc *= def;', then charsAfterLeftNode will contain ' *= d'.
var charsAfterLeftNode = sourceCode.getNextChars (node.left, 3 + opLength),
validationRegexp = new RegExp ('^ ' + op + ' [^\\s]$');
(!validationRegexp.test (charsAfterLeftNode)) && context.report ({
node: node.left,
message: 'Assignment operator must have exactly single space on both sides of it.'
});
});
//statement like `var x = 10` doesn't come under AssignmentExpression, so needs to be checked separately
context.on ('VariableDeclaration', function (emitted) {
var node = emitted.node, code = sourceCode.getText (node);
if (emitted.exit) {
return;
}
//if a particular character is '=', check its left and right for single space
for (var i = 2; i < code.length; i++) {
if (code [i] === '=') {
(!/^[^\/\s] $/.test (code.slice (i-2, i))) && context.report ({
node: node,
message: 'There should be only a single space between assignment operator \'=\' and its left side.'
});
(!/^ [^\/\s]$/.test (code.slice (i+1, i+3))) && context.report ({
node: node,
message: 'There should be only a single space between assignment operator \'=\' and its right side.'
});
}
}
//ensure there's no whitespace or comments before semicolon
(code [code.length - 1] === ';' && /(\s|\/)/.test (code [code.length - 2])) && context.report ({
node: node,
location: {
column: code.length - 2
},
message: 'There should be no whitespace or comments before the semicolon.'
});
});
//If we're dealing with abstract function declaration, we need to ensure no whitespce or comments before semicolon
context.on ('FunctionDeclaration', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
var code = sourceCode.getText (node);
(node.is_abstract && code [code.length - 1] === ';' && /(\s|\/)/.test (code [code.length - 2])) &&
context.report ({
node: node,
location: {
column: code.length - 2
},
message: 'There should be no whitespace or comments before the semicolon.'
});
//If parameters are specified, ensure appropriate spacing surrounding commas
var params = node.params;
if (params && params.length > 1) {
params.slice (0, -1).forEach (function (arg) {
sourceCode.getNextChar (arg) !== ',' && context.report ({
node: arg,
location: {
column: sourceCode.getEndingColumn (arg) + 1
},
message: node.name + ' ' + arg.id + '(): All arguments (except the last one) must be immediately followed by a comma.'
});
});
}
});
// The InformalParameter nodes (params) in modifier declaration should follow the same spacing rules as function declarations
context.on ('ModifierDeclaration', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
//If parameters are specified, ensure appropriate spacing surrounding commas
var params = node.params;
if (params && params.length > 1) {
params.slice (0, -1).forEach (function (arg) {
sourceCode.getNextChar (arg) !== ',' && context.report ({
node: arg,
location: {
column: sourceCode.getEndingColumn (arg) + 1
},
message: node.name + ' ' + arg.id + '(): All arguments (except the last one) must be immediately followed by a comma.'
});
});
}
});
context.on ('IfStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
/**
* Ensure a single space between 'if' token and the opening parenthesis 'if (...)'
*/
var ifTokenLength = 'if'.length,
nodeCode = sourceCode.getText (node).slice (ifTokenLength, ifTokenLength + 2);
(nodeCode !== ' (') && context.report ({
node: node,
location: {
column: sourceCode.getColumn (node) + ifTokenLength
},
message: 'There should be exactly a single space between the \'if\' token and the parenthetic block representing the conditional.'
});
});
context.on ('WhileStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
/**
* Ensure a single space between 'while' token and the opening parenthesis 'while (...)'
*/
var whileTokenLength = 'while'.length,
nodeCode = sourceCode.getText (node).slice (whileTokenLength, whileTokenLength + 2);
(nodeCode !== ' (') && context.report ({
node: node,
location: {
column: sourceCode.getColumn (node) + whileTokenLength
},
message: 'There should be exactly a single space between the \'while\' token and the parenthetic block representing the conditional.'
});
});
context.on ('ForStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
/**
* Ensure a single space between 'for' token and the opening parenthesis 'for (...)'
*/
var forTokenLength = 'for'.length,
nodeCode = sourceCode.getText (node).slice (forTokenLength, forTokenLength + 2);
(nodeCode !== ' (') && context.report ({
node: node,
location: {
column: sourceCode.getColumn (node) + forTokenLength
},
message: 'There should be exactly a single space between the \'for\' token and the parenthetic block representing the conditional.'
});
});
/************************************************************************************************
* From here on, we're explicitly looking for semicolon whitespace errors
************************************************************************************************/
context.on ('ExpressionStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
var code = sourceCode.getText (node);
//ensure there's no whitespace or comments before semicolon
(code [code.length - 1] === ';' && /(\s|\/)/.test (code [code.length - 2])) && context.report ({
node: node,
location: {
column: code.length - 2
},
message: 'There should be no whitespace or comments before the semicolon.'
});
});
context.on ('UsingStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
var code = sourceCode.getText (node);
//ensure there's no whitespace or comments before semicolon
(code [code.length - 1] === ';' && /(\s|\/)/.test (code [code.length - 2])) && context.report ({
node: node,
location: {
column: code.length - 2
},
message: 'There should be no whitespace or comments before the semicolon.'
});
});
context.on ('ImportStatement', function (emitted) {
var node = emitted.node;
if (emitted.exit) {
return;
}
var code = sourceCode.getText (node);
//ensure there's no whitespace or comments before semicolon
(code [code.length - 1] === ';' && /(\s|\/)/.test (code [code.length - 2])) && context.report ({
node: node,
location: {
column: code.length - 2
},
message: 'There should be no whitespace or comments before the semicolon.'
});
});
/************************************************************************************************
* From here on, we're explicitly looking for comma whitespace errors
************************************************************************************************/
context.on ('ArrayExpression', function (emitted) {
var node = emitted.node;
function inspectArrayElementForCommaWhitespace (elem) {
var charAfterElem = sourceCode.getNextChar (elem);
(charAfterElem !== ',') && context.report ({
node: elem,
location: {
column: sourceCode.getEndingColumn (elem) + 1
},
message: 'There should be no whitespace or comments between Array element and the comma following it.'
});
}
if (emitted.exit) {
return;
}
//need to test comma from starting to second last elem, since there's no comma after last element
node.elements.slice (0, -1).forEach (inspectArrayElementForCommaWhitespace);
});
context.on ('SequenceExpression', function (emitted) {
var node = emitted.node, expressions = node.expressions || [];
function inspectExprForWhitespace (expr) {
var charAfterExpr = sourceCode.getNextChar (expr);
(charAfterExpr !== ',') && context.report ({
node: expr,
location: {
column: sourceCode.getEndingColumn (expr) + 1
},
message: 'There should be no whitespace or comments between Tuple\'s element and the comma following it.'
});
}
if (emitted.exit) {
return;
}
expressions.slice (0, -1).forEach (inspectExprForWhitespace);
});
context.on ('VariableDeclarationTuple', function (emitted) {
var node = emitted.node, declarations = node.declarations;
function inspectVariableDeclaratorForWhitespace (vd) {
var charAfterExpr = sourceCode.getNextChar (vd);
(charAfterExpr !== ',') && context.report ({
node: vd,
location: {
column: sourceCode.getEndingColumn (vd) + 1
},
message: '\'' + vd.id.name + '\': identifier should be immediately followed by a comma without any whitespace in between.'
});
}
if (emitted.exit) {
return;
}
declarations.slice (0, -1).forEach (inspectVariableDeclaratorForWhitespace);
});
}
};

@@ -10,2 +10,3 @@ /**

solExplore = require ('sol-explore'),
util = require ('util'),

@@ -22,4 +23,9 @@ EventEmitter = require ('events').EventEmitter,

astUtils = require ('./utils/ast-utils'),
jsUtils = require ('./utils/js-utils');
jsUtils = require ('./utils/js-utils'),
configInspector = require ('./utils/config-inspector'),
ruleInspector = require ('./utils/rule-inspector'),
isErrObjectValid = require ('../config/schemas/error-supplied-to-solium').validationFunc,
isValidFixerPacket = require ('../config/schemas/fixer-packet').validationFunc;
module.exports = (function () {

@@ -35,3 +41,3 @@

Solium.reset = function reset () {
this.removeAllListeners ();
Solium.removeAllListeners ();
messages = [];

@@ -49,5 +55,3 @@ sourceCodeText = '';

Solium.lint = function lint (sourceCode, config, noReset) {
var nodeEventGenerator = new EventGenerator (Solium),
AST = {},
errorObjects;
var nodeEventGenerator = new EventGenerator (Solium), AST = {}, errorObjects;

@@ -59,33 +63,84 @@ if (typeof sourceCode === 'object' && sourceCode.constructor.name === 'Buffer') {

if (!(sourceCode && typeof sourceCode === 'string')) {
throw new Error (
'A valid source code string was not passed.'
);
throw new Error ('A valid source code string was not provided.');
}
if (!(config && typeof config === 'object' && config.rules && typeof config.rules === 'object')) {
if (!configInspector.isValid (config)) {
throw new Error (
'A valid configuration object was not passed. Please check the documentation.'
'A valid configuration object was not passed.' +
' Please see http://solium.readthedocs.io/en/latest/user-guide.html#configuring-the-linter' +
' for a valid config format.'
);
}
!noReset && this.reset ();
!noReset && Solium.reset ();
sourceCodeText = sourceCode;
astUtils.init (sourceCodeText);
currentConfig = JSON.parse (JSON.stringify (config)); //deep copy config object
//load meta information of enabled rules
currentConfig.rules = rules.load (currentConfig.rules, currentConfig ['custom-rules-filename']);
currentConfig = JSON.parse (JSON.stringify (config)); // deep copy config object
currentConfig.options = currentConfig.options || {}; // ensure "options" attr always exists in config
//load meta information of rules
if (configInspector.isFormatDeprecated (currentConfig)) {
var crf = currentConfig ['custom-rules-filename'];
Solium.reportInternal ({
type: 'warning',
message: '[DEPRECATED] You are using a deprecated soliumrc configuration format. ' +
'Please see http://solium.readthedocs.io/en/latest/user-guide.html#migrating-to-v1-0-0' +
' to migrate from Solium v0 to v1.'
});
crf && Solium.reportInternal ({
type: 'warning',
message: '[DEPRECATED] Attribute "custom-rules-filename" is now deprecated. ' +
'Rules from ' + crf + ' were not loaded. Plugins are supported v1 onward. Please see ' +
'http://solium.readthedocs.io/en/latest/user-guide.html#custom-rule-injection-is-now-deprecated'
});
currentConfig.rules = rules.loadUsingDeprecatedConfigFormat (currentConfig.rules, crf);
} else {
currentConfig.rules = rules.load (currentConfig);
}
Object.keys (currentConfig.rules).forEach (function (name) {
var rule = rules.get (name);
var rule = rules.get (name), currentRuleConfig = currentConfig.rules [name];
try {
rule.verify (
new RuleContext (name, currentConfig.rules [name], rule.meta || {}, Solium)
);
} catch (e) {
// Check for validity of exposed rule object
if (!ruleInspector.isAValidRuleObject (rule)) {
throw new Error ('A valid definition for rule "'
+ name + '" was not provided. AJV message:\n' + util.inspect (ruleInspector.isAValidRuleObject.errors));
}
// Check for validity of options passed to the rule via soliumrc (if options were passed)
if (currentRuleConfig.options && !ruleInspector.areValidOptionsPassed (currentRuleConfig.options, rule.meta.schema)) {
throw new Error ('Invalid options were passed to rule "' + name + '".');
}
// If rule contains deprecated tag & is set to true, display deprecation notice.
if (rule.meta.deprecated) {
var message = '[DEPRECATED] Rule "' + name + '" is deprecated.';
if (rule.meta.docs.replacedBy) {
message += ' Please use ' + rule.meta.docs.replacedBy.map (function (rn) {
return '"' + rn + '"';
}).join (', ') + ' instead.';
}
Solium.reportInternal ({ type: 'warning', message: message });
}
// Call rule implementation's create() to retrieve the node names to listen for & their handlers
// and subscribe them to the event emitter.
var ruleNodeListeners = rule.create (new RuleContext (name, currentRuleConfig, rule.meta, Solium));
if (!ruleInspector.isAValidRuleResponseObject (ruleNodeListeners)) {
throw new Error (
'A valid definition for rule \'' + name + '\' not found. Description:\n' + e.stack
'A rule implementation\'s response must be an object whose keys are AST Nodes to listen for and values their corresponding handler functions. AJV message:\n'
+ util.inspect (ruleInspector.isAValidRuleResponseObject.errors)
);
}
Object.keys (ruleNodeListeners).forEach (function (node) {
Solium.on (node, ruleNodeListeners [node]);
});
});

@@ -96,5 +151,3 @@

} catch (e) {
throw new Error (
'An error occured while parsing the source code:\n' + e
);
throw new Error ('An error occured while parsing the source code: ' + e.message);
}

@@ -119,2 +172,9 @@

// Remove all internal issues if user didn't ask for them.
if (!currentConfig.options.returnInternalIssues) {
messages = messages.filter (function (msg) {
return !msg.internal;
});
}
//sort errors by line (column if line is same)

@@ -142,3 +202,7 @@ messages.sort (function (a, b) {

Solium.lintAndFix = function lintAndFix (sourceCode, config, noReset) {
var errorObjects = this.lint (sourceCode, config);
if (typeof sourceCode === 'object' && sourceCode.constructor.name === 'Buffer') {
sourceCode = sourceCode.toString ();
}
var errorObjects = Solium.lint (sourceCode, config, noReset);
var fixed = SourceCodeFixer.applyFixes (sourceCode, errorObjects);

@@ -159,37 +223,41 @@

Solium.report = function report (error) {
if (!jsUtils.isStrictlyObject (error)) {
throw new Error ('Invalid error object');
if (!isErrObjectValid (error)) {
throw new Error (util.inspect (error) +
' is not a valid error object. AJV message:\n' + util.inspect (isErrObjectValid.errors));
}
if (!astUtils.isASTNode (error.node)) {
throw new Error ('Rule ' + error.ruleId + ' does not provide a valid AST node');
}
error.location = error.location || {};
if (!(error.message && typeof error.message === 'string')) {
throw new Error (
'Rule ' + error.ruleId + ' flags a node but doesn\'t provide an error description'
);
}
if (!jsUtils.isStrictlyObject (error.location)) {
error.location = {};
}
var message = {
ruleName: error.ruleName,
type: error.type, //either 'error' or 'warning'
type: error.type, // either 'error' or 'warning'
node: error.node,
message: error.message,
line: error.location.line || astUtils.getLine (error.node),
column: (error.location.column === 0) ? 0 : error.location.column || astUtils.getColumn (error.node)
column: (error.location.column === 0) ? 0 : (error.location.column || astUtils.getColumn (error.node))
};
if (error.ruleMeta.fixable && error.fix) {
if (typeof error.fix !== 'function') {
throw new Error (error.ruleName + ': Attribute \'fix\' must be a function.');
// If rule supplies a fix, it can be added to the message reported after validation.
if (error.fix) {
if (!error.ruleMeta.fixable) {
Solium.reportInternal ({
type: 'warning', message: '[WARNING] The fixes supplied by rule "' +
error.ruleName + '" will be ignored since its "meta" doesn\'t contain the "fixable" property.'
});
} else {
if (typeof error.fix !== 'function') {
throw new Error ('Rule "' + error.ruleName +
'": Attribute "fix" (reported as part of the error "' + error.message + '") must be a function.');
}
message.fix = error.fix (new RuleFixer (error.ruleMeta.fixable));
// Validate return value of the rule's error's fix() function
if (!isValidFixerPacket (message.fix)) {
throw new Error ('Rule "' + error.ruleName +
'": the fix() method for rule error "' + error.message + '" returns an invalid value.');
}
}
message.fix = error.fix (new RuleFixer (error.ruleMeta.fixable));
}

@@ -201,2 +269,15 @@

/**
* Convenience wrapper for Solium modules to report internal issues. It adds the "internal: true" attr to error.
* @param {Object} issue Internal issue
*/
Solium.reportInternal = function reportInternal (issue) {
if (!jsUtils.isStrictlyObject (issue)) {
throw new Error ('Invalid error object');
}
// Assign line & column = -1 so messages.sort() brings the internal issues on top
messages.push (Object.assign (issue, { internal: true, line: -1, column: -1 }));
};
/**
* Provides the user with program source code wrapped inside a utility object that also provides functions to operate on the code

@@ -203,0 +284,0 @@ * @returns {Object} sourceCodeObject The SourceCode Object that provides source text & functionality

@@ -8,4 +8,17 @@ /**

var sourceCodeText = '';
var Ajv = require ('ajv'),
util = require ('util'),
astNodeSchema = require ('../../config/schemas/ast-node');
var nodeSchemaValidator = new Ajv ({ allErrors: true }), sourceCodeText = '';
// For internal use. Throws if is passed an invalid AST node, else does nothing.
function throwIfInvalidNode (node, functionName) {
if (!exports.isASTNode (node)) {
throw new Error (functionName + '(): ' + util.inspect (node) + ' is not a valid AST node.');
}
}
/**

@@ -24,13 +37,4 @@ * Initialization method - provide all the necessary information astUtils functions could require in order to work

*/
exports.isASTNode = function (possibleNode) {
exports.isASTNode = nodeSchemaValidator.compile (astNodeSchema);
return (
possibleNode !== null && //node shouldn't be null
typeof possibleNode === 'object' && //must be data type object
possibleNode.hasOwnProperty ('type') && //a 'type' key must exist in the node
typeof possibleNode.type === 'string' //node.type's value must be a string
);
};
/**

@@ -42,5 +46,3 @@ * Get the parent node of the specified node

exports.getParent = function (node) {
if (!exports.isASTNode (node)) {
throw new Error (JSON.stringify (node) + ' is not a valid AST node');
}
throwIfInvalidNode (node, 'getParent');
return node.parent;

@@ -55,5 +57,3 @@ };

exports.getLine = function (node) {
if (!exports.isASTNode (node)) {
throw new Error (JSON.stringify (node) + ' is not a valid AST node');
}
throwIfInvalidNode (node, 'getLine');

@@ -75,5 +75,3 @@ var newLineCharsBefore = sourceCodeText

exports.getColumn = function (node) {
if (!exports.isASTNode (node)) {
throw new Error (JSON.stringify (node) + ' is not a valid AST node');
}
throwIfInvalidNode (node, 'getColumn');

@@ -96,5 +94,3 @@ //start looking from sourceCodeText [node.start] and stop upon encountering the first linebreak character

exports.getEndingLine = function (node) {
if (!exports.isASTNode (node)) {
throw new Error (JSON.stringify (node) + ' is not a valid AST node');
}
throwIfInvalidNode (node, 'getEndingLine');

@@ -117,5 +113,3 @@ var newLineCharsBefore = sourceCodeText

exports.getEndingColumn = function (node) {
if (!exports.isASTNode (node)) {
throw new Error (JSON.stringify (node) + ' is not a valid AST node');
}
throwIfInvalidNode (node, 'getEndingColumn');

@@ -122,0 +116,0 @@ //start looking from 1 character before node.start and stop upon encountering the first linebreak character

@@ -17,5 +17,5 @@ /**

typeof possibleObject === 'object' &&
possibleObject.constructor !== Array
possibleObject.constructor.name === 'Object'
);
},
};
{
"name": "solium",
"version": "0.5.5",
"description": "A flexible, stand-alone linter for Ethereum Solidity",
"version": "1.0.0",
"description": "A customisable linter to identify & fix patterns in Ethereum Solidity",
"main": "./lib/solium.js",

@@ -14,5 +14,13 @@ "bin": {

"keywords": [
"Lint",
"Solidity",
"Abstract-Syntax-Tree"
"lint",
"static-analysis",
"solidity",
"abstract-syntax-tree",
"ethereum",
"smart-contracts",
"solium",
"blockchain",
"code-quality",
"dapp",
"developer-tools"
],

@@ -26,9 +34,11 @@ "repository": {

"dependencies": {
"ajv": "^5.2.2",
"chokidar": "^1.6.0",
"colors": "^1.1.2",
"commander": "^2.9.0",
"js-string-escape": "^1.0.1",
"lodash": "^4.14.2",
"sol-digger": "0.0.2",
"sol-explore": "^1.6.1",
"solparse": "^1.2.2"
"solparse": "^1.2.7"
},

@@ -40,4 +50,8 @@ "devDependencies": {

"should": "^11.0.0",
"supertest": "^2.0.0"
"solium-config-test": "0.0.1",
"solium-config-test-invalid-schema": "0.0.0",
"solium-config-test-invalid-syntax": "0.0.0",
"solium-plugin-test": "^0.1.5",
"solium-plugin-test-invalid-schema": "0.0.0"
}
}

@@ -0,3 +1,14 @@

# Solium v1 is now in Beta!
v1 comes packed with automatic code formatting, sharable configs, plugin system, bug fixes, pretty printing and a lot more!
To install v1, run `npm install -g solium@v1` and access the complete documentation [here](http://solium.readthedocs.io/).
If you'd like to use the `latest` (stable) version instead, please see below.
___
![solium](https://cloud.githubusercontent.com/assets/12758282/18283522/4b206522-7483-11e6-9bcd-2a70ebc8cfdb.png)
[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/Solium-linter/Lobby)
[![Build Status](https://travis-ci.org/duaraghav8/Solium.svg?branch=master)](https://travis-ci.org/duaraghav8/Solium)
[![Latest News](https://img.shields.io/badge/Blog-Medium-yellowgreen.svg)](https://medium.com/solium)
Solium is a linter for Solidity which uses Abstract Syntax Trees and allows the user to enable/disable existing rules and add their own ones!

@@ -40,8 +51,6 @@

2. When new rules are added in subsequent versions and you update Solium, you need not re-initialize with ```--init```. Simply run ```solium --sync``` in your root directory and it automatically adds the newly added rules to your ```.soliumrc.json```. The sync option **doesn't change anything else in your configuration files**.
2. Use `solium --reporter=gcc` or `solium --reporter=pretty` to configure the output
3. Use ```solium --dir <DIRECTORY_NAME>``` to run the linter over a particular directory
4. Use `solium --reporter=gcc` or `solium --reporter=pretty` to configure the output
# Plugging in your custom rules

@@ -129,5 +138,2 @@ -> Open up the ```.soliumrc.json``` configuration file and set the value of ```custom-rules-filename``` to the path of the file that defines your rules. You can either provide an absolute path or a path relative to the directory in which .soliumrc.json resides. For example: ```"custom-rules-filename": "./my-rules.js"```

# Contributing
Please see the [Developer Guide](https://github.com/duaraghav8/Solium/blob/master/docs/DEVELOPER.md) to understand how to contribute rules to this repository.
## Setup

@@ -158,3 +164,5 @@

If you're using vim with syntastic, and prefer to use a locally installed version of Solium (rather than a global version), you can install [syntastic-local-solium](https://github.com/sohkai/syntastic-local-solium.vim) to automatically load the local version in packages that have installed their own.
# License
## MIT
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc