diff --git a/ale_linters/solidity/solhint.vim b/ale_linters/solidity/solhint.vim index 8ea33e07..505bd5bb 100644 --- a/ale_linters/solidity/solhint.vim +++ b/ale_linters/solidity/solhint.vim @@ -1,29 +1,12 @@ -" Author: Franco Victorio - https://github.com/fvictorio +" Authors: Franco Victorio - https://github.com/fvictorio, Henrique Barcelos +" https://github.com/hbarcelos " Description: Report errors in Solidity code with solhint -function! ale_linters#solidity#solhint#Handle(buffer, lines) abort - " Matches patterns like the following: - " /path/to/file/file.sol: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) - let l:pattern = '\v^[^:]+: line (\d+), col (\d+), (Error|Warning) - (.*) \((.*)\)$' - let l:output = [] - - for l:match in ale#util#GetMatches(a:lines, l:pattern) - let l:isError = l:match[3] is? 'error' - call add(l:output, { - \ 'lnum': l:match[1] + 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[4], - \ 'code': l:match[5], - \ 'type': l:isError ? 'E' : 'W', - \}) - endfor - - return l:output -endfunction - call ale#linter#Define('solidity', { \ 'name': 'solhint', -\ 'executable': 'solhint', -\ 'command': 'solhint --formatter compact %t', -\ 'callback': 'ale_linters#solidity#solhint#Handle', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#solhint#GetExecutable'), +\ 'cwd': function('ale#handlers#solhint#GetCwd'), +\ 'command': function('ale#handlers#solhint#GetCommand'), +\ 'callback': 'ale#handlers#solhint#Handle', \}) diff --git a/autoload/ale/handlers/solhint.vim b/autoload/ale/handlers/solhint.vim new file mode 100644 index 00000000..61ab2a60 --- /dev/null +++ b/autoload/ale/handlers/solhint.vim @@ -0,0 +1,98 @@ +" Author: Henrique Barcelos <@hbarcelos> +" Description: Functions for working with local solhint for checking *.sol files. + +let s:executables = [ +\ 'node_modules/.bin/solhint', +\ 'node_modules/solhint/solhint.js', +\ 'solhint', +\] + +let s:sep = has('win32') ? '\' : '/' + +call ale#Set('solidity_solhint_options', '') +call ale#Set('solidity_solhint_executable', 'solhint') +call ale#Set('solidity_solhint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#handlers#solhint#Handle(buffer, lines) abort + " Matches patterns like the following: + " /path/to/file/file.sol: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) + let l:output = [] + + let l:lint_pattern = '\v^[^:]+: line (\d+), col (\d+), (Error|Warning) - (.*) \((.*)\)$' + + for l:match in ale#util#GetMatches(a:lines, l:lint_pattern) + let l:isError = l:match[3] is? 'error' + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'code': l:match[5], + \ 'type': l:isError ? 'E' : 'W', + \}) + endfor + + let l:syntax_pattern = '\v^[^:]+: line (\d+), col (\d+), (Error|Warning) - (Parse error): (.*)$' + + for l:match in ale#util#GetMatches(a:lines, l:syntax_pattern) + let l:isError = l:match[3] is? 'error' + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[5], + \ 'code': l:match[4], + \ 'type': l:isError ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +function! ale#handlers#solhint#FindConfig(buffer) abort + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:basename in [ + \ '.solhintrc.js', + \ '.solhintrc.json', + \ '.solhintrc', + \] + let l:config = ale#path#Simplify(join([l:path, l:basename], s:sep)) + + if filereadable(l:config) + return l:config + endif + endfor + endfor + + return ale#path#FindNearestFile(a:buffer, 'package.json') +endfunction + +function! ale#handlers#solhint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'solidity_solhint', s:executables) +endfunction + +" Given a buffer, return an appropriate working directory for solhint. +function! ale#handlers#solhint#GetCwd(buffer) abort + " If solhint is installed in a directory which contains the buffer, assume + " it is the solhint project root. Otherwise, use nearest node_modules. + " Note: If node_modules not present yet, can't load local deps anyway. + let l:executable = ale#node#FindNearestExecutable(a:buffer, s:executables) + + if !empty(l:executable) + let l:nmi = strridx(l:executable, 'node_modules') + let l:project_dir = l:executable[0:l:nmi - 2] + else + let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules') + let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : '' + endif + + return !empty(l:project_dir) ? l:project_dir : '' +endfunction + +function! ale#handlers#solhint#GetCommand(buffer) abort + let l:executable = ale#handlers#solhint#GetExecutable(a:buffer) + + let l:options = ale#Var(a:buffer, 'solidity_solhint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --formatter compact %s' +endfunction diff --git a/test/handler/test_solhint_handler.vader b/test/handler/test_solhint_handler.vader index a3ed5d57..f8fffb60 100644 --- a/test/handler/test_solhint_handler.vader +++ b/test/handler/test_solhint_handler.vader @@ -4,7 +4,7 @@ Before: After: call ale#linter#Reset() -Execute(The vint handler should parse error messages correctly): +Execute(The solhint handler should parse linter error messages correctly): AssertEqual \ [ \ { @@ -50,7 +50,7 @@ Execute(The vint handler should parse error messages correctly): \ 'type': 'E', \ }, \ ], - \ ale_linters#solidity#solhint#Handle(bufnr(''), [ + \ ale#handlers#solhint#Handle(bufnr(''), [ \ 'contracts/Bounty.sol: line 1, col 17, Warning - Compiler version must be fixed (compiler-fixed)', \ 'contracts/Bounty.sol: line 4, col 8, Error - Use double quotes for string literals (quotes)', \ 'contracts/Bounty.sol: line 5, col 8, Error - Use double quotes for string literals (quotes)', @@ -58,3 +58,27 @@ Execute(The vint handler should parse error messages correctly): \ 'contracts/Bounty.sol: line 14, col 3, Error - Expected indentation of 4 spaces but found 2 (indent)', \ 'contracts/Bounty.sol: line 47, col 3, Error - Function order is incorrect, public function can not go after internal function. (func-order)', \ ]) + + +Execute(The solhint handler should parse syntax error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 30, + \ 'col': 4, + \ 'text': "missing ';' at 'uint248'", + \ 'code': 'Parse error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 203, + \ 'col': 4, + \ 'text': "no viable alternative at input '_loserStakeMultiplier}'", + \ 'code': 'Parse error', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#solhint#Handle(bufnr(''), [ + \ "contracts/Bounty.sol: line 30, col 4, Error - Parse error: missing ';' at 'uint248'", + \ "contracts/Bounty.sol: line 203, col 4, Error - Parse error: no viable alternative at input '_loserStakeMultiplier}'", + \ ]) diff --git a/test/linter/test_solhint.vader b/test/linter/test_solhint.vader new file mode 100644 index 00000000..fc5afa9b --- /dev/null +++ b/test/linter/test_solhint.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpLinterTest('solidity', 'solhint') + runtime autoload/ale/handlers/solhint.vim + + let b:args = ' --formatter compact %s' + +After: + unlet! b:args + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinterCwd '' + AssertLinter 'solhint', ale#Escape('solhint') . b:args + +Execute(The options should be configurable): + let g:ale_solidity_solhint_options = '--foobar' + + AssertLinter 'solhint', ale#Escape('solhint') . ' --foobar' . b:args + + +Execute(solhint should be run from a containing project with solhint executable): + call ale#test#SetFilename('../test-files/solhint/Contract.sol') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/solhint/node_modules/.bin/solhint') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/solhint') + AssertLinter b:executable, ale#Escape(b:executable) . b:args diff --git a/test/test-files/solhint/Contract.sol b/test/test-files/solhint/Contract.sol new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/node_modules/.bin/solhint b/test/test-files/solhint/node_modules/.bin/solhint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/node_modules/solhint/index.js b/test/test-files/solhint/node_modules/solhint/index.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/package.json b/test/test-files/solhint/package.json new file mode 100644 index 00000000..e69de29b