Skip to content

Commit

Permalink
Merge 4905636 into 88dca20
Browse files Browse the repository at this point in the history
  • Loading branch information
fvictorio committed Jan 24, 2020
2 parents 88dca20 + 4905636 commit 6d8abbe
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 18 deletions.
31 changes: 31 additions & 0 deletions lib/apply-fixes.js
@@ -0,0 +1,31 @@
function applyFixes(fixes, inputSrc) {
if (fixes.length === 0) {
return {
fixed: false
}
}

let output = ''

let i = 0
let fixIndex = 0

while (i < inputSrc.length) {
if (fixIndex < fixes.length) {
output += inputSrc.slice(i, fixes[fixIndex].range[0])
output += fixes[fixIndex].text
i = fixes[fixIndex].range[1] + 1
fixIndex += 1
} else {
output += inputSrc.slice(i)
break
}
}

return {
fixed: true,
output
}
}

module.exports = applyFixes
27 changes: 17 additions & 10 deletions lib/reporter.js
Expand Up @@ -7,28 +7,35 @@ class Reporter {
this.config = config.rules || {}
}

addReport(line, column, severity, message, ruleId) {
this.reports.push({ line, column, severity, message, ruleId })
addReport(line, column, severity, message, ruleId, fix) {
this.reports.push({ line, column, severity, message, ruleId, fix })
}

addMessage(loc, defaultSeverity, message, ruleId) {
this.addMessageExplicitLine(loc.start.line, loc.start.column, defaultSeverity, message, ruleId)
addMessage(loc, defaultSeverity, message, ruleId, fix) {
this.addMessageExplicitLine(
loc.start.line,
loc.start.column,
defaultSeverity,
message,
ruleId,
fix
)
}

addMessageExplicitLine(line, column, defaultSeverity, message, ruleId) {
addMessageExplicitLine(line, column, defaultSeverity, message, ruleId, fix) {
const configSeverity = this.severityOf(ruleId)

if (this.config[ruleId] !== false && this.commentDirectiveParser.isRuleEnabled(line, ruleId)) {
this.addReport(line, column + 1, configSeverity || defaultSeverity, message, ruleId)
this.addReport(line, column + 1, configSeverity || defaultSeverity, message, ruleId, fix)
}
}

error(ctx, ruleId, message) {
this.addMessage(ctx.loc, Reporter.SEVERITY.ERROR, message, ruleId)
error(ctx, ruleId, message, fix) {
this.addMessage(ctx.loc, Reporter.SEVERITY.ERROR, message, ruleId, fix)
}

warn(ctx, ruleId, message) {
this.addMessage(ctx.loc, Reporter.SEVERITY.WARN, message, ruleId)
warn(ctx, ruleId, message, fix) {
this.addMessage(ctx.loc, Reporter.SEVERITY.WARN, message, ruleId, fix)
}

errorAt(line, column, ruleId, message) {
Expand Down
138 changes: 138 additions & 0 deletions lib/rule-fixer.js
@@ -0,0 +1,138 @@
// taken from eslint (https://github.com/eslint/eslint)

/**
* @fileoverview An object that creates fix commands for rules.
* @author Nicholas C. Zakas
*/

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

// none!

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Creates a fix command that inserts text at the specified index in the source text.
* @param {int} index The 0-based index at which to insert the new text.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
* @private
*/
function insertTextAt(index, text) {
return {
range: [index, index],
text
}
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/**
* Creates code fixing commands for rules.
*/

const ruleFixer = Object.freeze({
/**
* Creates a fix command that inserts text after the given node or token.
* The fix is not applied until applyFixes() is called.
* @param {ASTNode|Token} nodeOrToken The node or token to insert after.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
insertTextAfter(nodeOrToken, text) {
return this.insertTextAfterRange(nodeOrToken.range, text)
},

/**
* Creates a fix command that inserts text after the specified range in the source text.
* The fix is not applied until applyFixes() is called.
* @param {int[]} range The range to replace, first item is start of range, second
* is end of range.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
insertTextAfterRange(range, text) {
return insertTextAt(range[1], text)
},

/**
* Creates a fix command that inserts text before the given node or token.
* The fix is not applied until applyFixes() is called.
* @param {ASTNode|Token} nodeOrToken The node or token to insert before.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
insertTextBefore(nodeOrToken, text) {
return this.insertTextBeforeRange(nodeOrToken.range, text)
},

/**
* Creates a fix command that inserts text before the specified range in the source text.
* The fix is not applied until applyFixes() is called.
* @param {int[]} range The range to replace, first item is start of range, second
* is end of range.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
insertTextBeforeRange(range, text) {
return insertTextAt(range[0], text)
},

/**
* Creates a fix command that replaces text at the node or token.
* The fix is not applied until applyFixes() is called.
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
replaceText(nodeOrToken, text) {
return this.replaceTextRange(nodeOrToken.range, text)
},

/**
* Creates a fix command that replaces text at the specified range in the source text.
* The fix is not applied until applyFixes() is called.
* @param {int[]} range The range to replace, first item is start of range, second
* is end of range.
* @param {string} text The text to insert.
* @returns {Object} The fix command.
*/
replaceTextRange(range, text) {
return {
range,
text
}
},

/**
* Creates a fix command that removes the node or token from the source.
* The fix is not applied until applyFixes() is called.
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
* @returns {Object} The fix command.
*/
remove(nodeOrToken) {
return this.removeRange(nodeOrToken.range)
},

/**
* Creates a fix command that removes the specified range of text from the source.
* The fix is not applied until applyFixes() is called.
* @param {int[]} range The range to remove, first item is start of range, second
* is end of range.
* @returns {Object} The fix command.
*/
removeRange(range) {
return {
range,
text: ''
}
}
})

module.exports = ruleFixer
16 changes: 10 additions & 6 deletions lib/rules/base-checker.js
Expand Up @@ -5,16 +5,20 @@ class BaseChecker {
this.meta = meta
}

error(ctx, message) {
this.reporter.error(ctx, this.ruleId, message)
error(ctx, message, fix) {
this.addReport('error', ctx, message, fix)
}

errorAt(line, column, message) {
this.reporter.errorAt(line, column, this.ruleId, message)
errorAt(line, column, message, fix) {
this.error({ loc: { start: { line, column } } }, message, fix)
}

warn(ctx, message) {
this.reporter.warn(ctx, this.ruleId, message)
warn(ctx, message, fix) {
this.addReport('warn', ctx, message, fix)
}

addReport(type, ctx, message, fix) {
this.reporter[type](ctx, this.ruleId, message, this.meta.fixable ? fix : null)
}
}

Expand Down
5 changes: 4 additions & 1 deletion lib/rules/security/avoid-sha3.js
Expand Up @@ -12,6 +12,7 @@ const meta = {
isDefault: false,
recommended: true,
defaultSetup: 'warn',
fixable: true,

schema: []
}
Expand All @@ -23,7 +24,9 @@ class AvoidSha3Checker extends BaseChecker {

Identifier(node) {
if (node.name === 'sha3') {
this.error(node, 'Use "keccak256" instead of deprecated "sha3"')
this.error(node, 'Use "keccak256" instead of deprecated "sha3"', fixer =>
fixer.replaceTextRange(node.range, 'keccak256')
)
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion lib/rules/security/avoid-throw.js
Expand Up @@ -12,6 +12,7 @@ const meta = {
isDefault: false,
recommended: true,
defaultSetup: 'warn',
fixable: true,

schema: []
}
Expand All @@ -22,7 +23,12 @@ class AvoidThrowChecker extends BaseChecker {
}

ThrowStatement(node) {
this.error(node, '"throw" is deprecated, avoid to use it')
this.error(node, '"throw" is deprecated, avoid to use it', fixer =>
// we don't use just `node.range` because ThrowStatement includes the semicolon and the spaces before it
// we know that node.range[0] is the 't' of throw
// we're also pretty sure that 'throw' has 5 letters
fixer.replaceTextRange([node.range[0], node.range[0] + 5], 'revert()')
)
}
}

Expand Down
21 changes: 21 additions & 0 deletions solhint.js
Expand Up @@ -8,6 +8,8 @@ const process = require('process')
const linter = require('./lib/index')
const { applyExtends, loadConfig } = require('./lib/config/config-file')
const { validate } = require('./lib/config/config-validator')
const applyFixes = require('./lib/apply-fixes')
const ruleFixer = require('./lib/rule-fixer')
const packageJson = require('./package.json')

function init() {
Expand All @@ -21,6 +23,7 @@ function init() {
.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')
.option('--fix', 'automatically fix problems')
.description('Linter for Solidity programming language')
.action(execMainAction)

Expand Down Expand Up @@ -58,6 +61,24 @@ function execMainAction() {
const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
const warningsNumberExceeded = program.maxWarnings >= 0 && warningsCount > program.maxWarnings

if (program.fix) {
for (const report of reports) {
const inputSrc = fs.readFileSync(report.filePath).toString()

const fixes = _(report.reports)
.filter(x => x.fix)
.map(x => x.fix(ruleFixer))
.sort((a, b) => a.range[0] - b.range[0])
.value()

const { fixed, output } = applyFixes(fixes, inputSrc)
if (fixed) {
report.reports = report.reports.filter(x => !x.fix)
fs.writeFileSync(report.filePath, output)
}
}
}

if (program.quiet) {
// filter the list of reports, to set errors only.
reports[0].reports = reports[0].reports.filter(i => i.severity === 2)
Expand Down

0 comments on commit 6d8abbe

Please sign in to comment.