/ solidity-flycheck.el
solidity-flycheck.el
1 ;;; solidity-flycheck.el --- Flycheck integration for solidity emacs mode 2 3 ;; Copyright (C) 2015-2018 Lefteris Karapetsas 4 5 ;; Author: Lefteris Karapetsas <lefteris@refu.co> 6 ;; Keywords: languages, solidity, flycheck 7 ;; Version: 0.1.11 8 ;; Package-Requires: ((flycheck "32-cvs") (solidity-mode "0.1.9") (dash "2.17.0")) 9 10 ;; This program is free software; you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 15 ;; This program is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 19 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; flycheck integration is taken in a different file to prevent problems 26 ;; when flycheck is not present on the user's system. 27 ;; 28 ;;; Code: 29 30 (require 'flycheck) 31 (require 'solidity-common) 32 (require 'dash) 33 (require 'project) 34 35 (defcustom solidity-flycheck-chaining-error-level 'warning 36 "The maximum error level at which chaining of checkers will happen. 37 38 This means that this is the error level for which solc checker will allow 39 next checkers to run. By default this is the warning level. 40 Possible values are: 41 42 `info' 43 If any errors higher than info level are found in solc, then solium 44 will not run. 45 46 `warning' 47 If any errors higher than warning level are found in solc, then solium 48 will not run. 49 50 `error' 51 If any errors higher than error level are found in solc, then solium 52 will not run. 53 t 54 Solium will always run." 55 :group 'solidity 56 :type '(choice (const :tag "Chain after info-error level" info) 57 (const :tag "Chain after warning-error level" warning) 58 (const :tag "Chain after error-error level" error) 59 (const :tag "Always chain" t)) 60 :package-version '(solidity . "0.1.5") 61 :safe #'symbolp) 62 63 (flycheck-def-option-var flycheck-solidity-solium-soliumrcfile nil solium-check 64 "The path to use for soliumrc.json 65 66 The value of this variable is either a string denoting a path to the soliumrc.json 67 or nil, to use the current directory. When non-nil, 68 we pass the directory to solium via the `--config' option." 69 :type '(choice (const :tag "No custom soliumrc" nil) 70 (string :tag "Custom soliumrc file location")) 71 :safe #'stringp 72 :package-version '(solidity-mode . "0.1.4")) 73 74 (defcustom solidity-flycheck-solc-additional-allow-paths nil 75 "A list of paths that should be passed to solc using the --allow-paths option. 76 77 This lets you import .sol files from other directories without causing linting errors. 78 For example, say that you use Brownie (URL `https://github.com/eth-brownie/brownie'), 79 which stores ethPM packages in \"~/.brownie/packages\". You could add \"~/.brownie/packages\" 80 to this variable to import ethPM packages without linting errors. Subdirectories 81 in each allow path are remapped. 82 83 Note that when `solidity-flycheck-use-project' is t, the project root will be 84 added to --allow-paths in addition to any paths defined here." 85 :group 'solidity 86 :type 'list 87 :safe #'listp 88 :package-version '(solidity-mode . "0.1.11")) 89 90 (defcustom solidity-flycheck-use-project nil 91 "A boolean flag denoting whether solidity-flycheck should detect projects. 92 93 When t, solidity-flycheck will detect a project and include files in the project 94 during compilation and linting. solidity-flycheck will look for a .soliumrc.json 95 in a parent directory. If found, this directory is considered the project root. If 96 no .soliumrc.json is found, `project-roots' is used. 97 98 When using solium-checker, the .soliumrc.json found in the project root will be 99 used as the solium config, rather than a .soliumrc.json in the same directory as 100 the file being linted. 101 102 When using solhint-checker, solhint will be run at the project root. 103 104 When using solidity-checker , the project root will be passed to solc using the 105 --allow-paths flag. This means imports to other files inside the project will 106 lint without erorr." 107 :group 'solidity 108 :type 'boolean 109 :safe #'booleanp 110 :package-version '(solidity-mode . "0.1.11")) 111 112 (defun solidity-flycheck--find-working-directory (_checker) 113 "Look for a working directtory to run solium CHECKER in. 114 115 This will be a parent directory that contains a `.soliumrc.json' file. If 116 no .soliumrc.json is found, `project-roots' is used." 117 (when (and buffer-file-name solidity-flycheck-use-project) 118 (when-let ((root (or 119 (locate-dominating-file buffer-file-name ".soliumrc.json") 120 (let* ((proj (project-current t)) 121 (roots (project-roots proj))) 122 (car roots))))) 123 (expand-file-name root)))) 124 125 ;; define solium flycheck syntax checker 126 ;; expanded the flycheck-define-checker macro in order to eval certain args, as per advice given in gitter 127 ;; https://gitter.im/flycheck/flycheck?at=5a43b3a8232e79134d98872b 128 ;; first try to add solium to the checker's list since if we got solc 129 ;; it must come after it in the list due to it being chained after solc 130 (flycheck-def-executable-var solium-checker "solium") 131 132 (defun solium-has-reporter () 133 (let ((solium-full-path (funcall 134 flycheck-executable-find 135 (or flycheck-solium-checker-executable solidity-solium-path)))) 136 (and solium-full-path 137 (string-match-p "--reporter" (shell-command-to-string (concat solium-full-path " --help")))))) 138 139 (flycheck-define-command-checker 'solium-checker 140 "A Solidity linter using solium" 141 :command `(,solidity-solium-path 142 (eval (when (solium-has-reporter) "--reporter=gcc")) 143 (option "--config=" flycheck-solidity-solium-soliumrcfile concat) 144 "-f" 145 source-inplace) 146 :error-patterns `((error line-start (zero-or-more not-newline) "[Fatal error]" (message)) 147 (error line-start (zero-or-more " ") line ":" column (zero-or-more " ") "error" (message)) 148 (warning line-start (zero-or-more " ") line ":" column (zero-or-more " ") "warning" (message)) 149 ;; reporter=gcc formats 150 (error line-start (file-name) ":" line ":" column ": error: " (message)) 151 (warning line-start (file-name) ":" line ":" column ": warning: " (message))) 152 153 :error-filter 154 ;; Add fake line numbers if they are missing in the lint output 155 #'(lambda (errors) 156 (dolist (err errors) 157 (unless (flycheck-error-line err) 158 (setf (flycheck-error-line err) 1))) 159 errors) 160 :modes 'solidity-mode 161 :predicate #'(lambda nil (eq major-mode 'solidity-mode)) 162 :next-checkers `((,solidity-flycheck-chaining-error-level . solhint-checker)) 163 :standard-input 'nil 164 :working-directory 'solidity-flycheck--find-working-directory) 165 166 ;; add a solidity mode callback to set the executable of solc for flycheck 167 ;; define solidity's flycheck syntax checker 168 ;; expanded the flycheck-define-checker macro in order to eval certain args, as per advice given in gitter 169 ;; https://gitter.im/flycheck/flycheck?at=5a43b3a8232e79134d98872b 170 (flycheck-def-executable-var solidity-checker "solc") 171 172 (defun get-solc-version () 173 "Query solc executable and return its version. 174 175 The result is returned in a list with 3 elements.MAJOR MINOR PATCH. 176 177 If the solc output can't be parsed an error is returned." 178 (let* ((solc-full-path (funcall 179 flycheck-executable-find 180 (or flycheck-solidity-checker-executable solidity-solc-path))) 181 (output (shell-command-to-string (format "%s --version" solc-full-path)))) 182 (if (string-match "Version: \\([[:digit:]]+\\)\.\\([[:digit:]]+\\)\.\\([[:digit:]]+\\)" output) 183 (list (match-string 1 output) 184 (match-string 2 output) 185 (match-string 3 output)) 186 (error "Could not parse the output of %s --version:\n %s" solc-full-path output)))) 187 188 (defun solc-gt-0.6.0 () 189 "Return `t` if solc >= 0.6.0 and `nil` otherwise." 190 (let* ((version (get-solc-version)) 191 (major (string-to-number (nth 0 version))) 192 (minor (string-to-number (nth 1 version))) 193 (patch (string-to-number (nth 2 version)))) 194 (if (and (>= major 0) (>= minor 6)) t nil))) 195 196 (defun solidity-flycheck--solc-allow-paths () 197 (append 198 (mapcar #'expand-file-name solidity-flycheck-solc-additional-allow-paths) 199 '("."))) 200 201 (defun solidity-flycheck--only-subdirectories (dir) 202 (-filter 203 (lambda (p) 204 (and (file-directory-p p) 205 (not (string-prefix-p "." (file-name-nondirectory p))))) 206 (directory-files dir t))) 207 208 (defun solidity-flycheck--solc-remappings () 209 (let* ((allow-paths (solidity-flycheck--solc-allow-paths))) 210 (->> allow-paths 211 (cl-remove-if-not #'file-exists-p) 212 (mapcar #'solidity-flycheck--only-subdirectories) 213 -flatten 214 (mapcar 215 (lambda (dir) 216 (let ((prefix (file-name-nondirectory dir))) 217 (concat prefix "=" dir))))))) 218 219 (defun solidity-flycheck--solc-remappings-opt () 220 (when-let ((remappings (solidity-flycheck--solc-remappings))) 221 remappings)) 222 223 (defun solidity-flycheck--solc-allow-paths-opt () 224 (let ((allow-paths (mapconcat 'identity (solidity-flycheck--solc-allow-paths) ","))) 225 (when (not (string= "" allow-paths)) 226 `("--allow-paths" ,allow-paths)))) 227 228 (defun solidity-flycheck--solc-cmd () 229 `(,solidity-solc-path 230 (eval 231 (when (solc-gt-0.6.0) 232 `("--no-color" 233 ,@(solidity-flycheck--solc-allow-paths-opt) 234 ,@(solidity-flycheck--solc-remappings-opt)))) 235 source-inplace)) 236 237 (flycheck-define-command-checker 'solidity-checker 238 "A Solidity syntax checker using the solc compiler" 239 :command (solidity-flycheck--solc-cmd) 240 :error-patterns '( 241 ;; Solidity >= 0.6.0 error formats 242 (error line-start "Error: " (message) "\n" (zero-or-more whitespace) "--> " (file-name) ":" line ":" column) 243 (warning line-start "Warning: " (message) "\n" (zero-or-more whitespace) "--> " (file-name) ":" line ":" column) 244 245 ;; Solidity < 0.6.0 error formats 246 (error line-start (file-name) ":" line ":" column ":" " Error: " (message)) 247 (error line-start (file-name) ":" line ":" column ":" " Compiler error: " (message)) 248 (error line-start "Error: " (message)) 249 (warning line-start (file-name) ":" line ":" column ":" " Warning: " (message))) 250 :modes 'solidity-mode 251 :predicate #'(lambda nil (eq major-mode 'solidity-mode)) 252 :next-checkers 253 `((,solidity-flycheck-chaining-error-level . solium-checker) 254 (,solidity-flycheck-chaining-error-level . solhint-checker)) 255 :standard-input 'nil 256 :working-directory 'solidity-flycheck--find-working-directory) 257 258 (flycheck-def-executable-var solhint-checker "solhint") 259 (flycheck-define-command-checker 'solhint-checker 260 "A Solidity linter using solhint" 261 :command `(,solidity-solhint-path "-f" "unix" source-inplace) 262 :error-patterns `((error 263 line-start (file-name) ":" line ":" column ":" (zero-or-more " ") 264 (zero-or-more " ") (message (one-or-more not-newline) "[Error/" (one-or-more not-newline) "]" )) 265 (warning 266 line-start (file-name) ":" line ":" column ":" (zero-or-more " ") 267 (zero-or-more " ") (message (one-or-more not-newline) "[Warning/" (one-or-more not-newline) "]" ))) 268 :error-filter 269 ;; Add fake line numbers if they are missing in the lint output 270 #'(lambda (errors) 271 (dolist (err errors) 272 (unless (flycheck-error-line err) 273 (setf (flycheck-error-line err) 1))) 274 errors) 275 :modes 'solidity-mode 276 :predicate #'(lambda nil (eq major-mode 'solidity-mode)) 277 :next-checkers 'nil 278 :standard-input 'nil 279 :working-directory 'solidity-flycheck--find-working-directory) 280 281 (add-to-list 'flycheck-checkers 'solhint-checker) 282 (add-to-list 'flycheck-checkers 'solium-checker) 283 (add-to-list 'flycheck-checkers 'solidity-checker) 284 285 (provide 'solidity-flycheck) 286 ;;; solidity-flycheck.el ends here