diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3ddd0cd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Dan Orzechowski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 73bc207..6444ff1 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,7 @@ useful variables are members of the `flymake-eslint` group and can be viewed and ## Bugs yes + +## License + +MIT diff --git a/flymake-eslint.el b/flymake-eslint.el index d5ae9ee..7e84f82 100644 --- a/flymake-eslint.el +++ b/flymake-eslint.el @@ -40,7 +40,7 @@ :group 'flymake-eslint) -(defcustom flymake-eslint-executable-args "" +(defcustom flymake-eslint-executable-args nil "Extra arguments to pass to eslint." :type 'string :group 'flymake-eslint) @@ -59,77 +59,88 @@ Name of the temporary file on which to run eslint.") Regular expression definition to match eslint messages.") +(defvar-local flymake-eslint--process nil + "Internal variable. +Handle to the linter process for the current buffer.") + + ;; internal functions (defun flymake-eslint--ensure-binary-exists () "Internal function. -Throw an error if `flymake-eslint-executable-name' can't be found on variable `exec-path'" +Throw an error and tell REPORT-FN to disable itself if `flymake-eslint-executable-name' can't be found on variable `exec-path'" (unless (executable-find flymake-eslint-executable-name) (error (message "can't find '%s' in exec-path - try M-x set-variable flymake-eslint-executable-name maybe?" flymake-eslint-executable-name)))) -(defun flymake-eslint--create-temp-file-from-buffer (source-buffer) +(defun flymake-eslint--report (eslint-stdout-buffer source-buffer) "Internal function. -Create a temporary file containing contents of SOURCE-BUFFER, and return its name." - ;; TODO: there's probably a better way to do all this - (with-current-buffer source-buffer - ;; save the contents of `source-buffer' as a string - (let ((buffer-text (buffer-string)) - (temp-file-name flymake-eslint--filename)) - ;; create the new temp file - (with-temp-file temp-file-name - (insert buffer-text)) - ;; return the name of the temp file - (identity temp-file-name)))) - - -(defun flymake-eslint--report (source-buffer) +Create Flymake diag messages from contents of ESLINT-STDOUT-BUFFER, to be reported against SOURCE-BUFFER. Returns a list of results" + (with-current-buffer eslint-stdout-buffer + ;; start at the top and check each line for an eslint message + (goto-char (point-min)) + (if (looking-at "Error:") + (let ((diag (flymake-make-diagnostic source-buffer (point-min) (point-max) :error (thing-at-point 'line t)))) + ;; ehhhhh point-min and point-max here are of the eslint output buffer + ;; containing the error message, not source-buffer + (list diag)) + (let ((results '())) + (while (not (eobp)) + (when (looking-at flymake-eslint--message-regex) + (let* ((row (string-to-number (match-string 1))) + (column (string-to-number (match-string 2))) + (type (match-string 3)) + (msg (match-string 4)) + (lint-rule (match-string 5)) + (msg-text (format "%s: %s [%s]" type msg lint-rule)) + (type-symbol (if (string-equal "warning" type) :warning :error)) + (src-pos (flymake-diag-region source-buffer row column))) + ;; new Flymake diag message + (push (flymake-make-diagnostic source-buffer (car src-pos) (cdr src-pos) type-symbol msg-text) results))) + (forward-line 1)) + results)))) + + +;; heavily based on the example found at +;; https://www.gnu.org/software/emacs/manual/html_node/flymake/An-annotated-example-backend.html +(defun flymake-eslint--create-process (source-buffer callback) "Internal function. -Create Flymake diag messages from contents of current buffer, as reported against SOURCE-BUFFER." - ;; start at the top and check each line for an eslint message - (goto-char (point-min)) - (if (looking-at "Error:") - (let ((diag (flymake-make-diagnostic source-buffer (point-min) (point-max) :error (thing-at-point 'line t)))) - ;; ehhhhh point-min and point-max here are of the eslint output buffer - ;; containing the error message, not source-buffer - (list diag)) - (let ((results '())) - (while (not (eobp)) - (when (looking-at flymake-eslint--message-regex) - (let* ((row (string-to-number (match-string 1))) - (column (string-to-number (match-string 2))) - (type (match-string 3)) - (msg (match-string 4)) - (lint-rule (match-string 5)) - (msg-text (format "%s: %s [%s]" type msg lint-rule)) - (type-symbol (if (string-equal "warning" type) :warning :error)) - (src-pos (flymake-diag-region source-buffer row column))) - ;; new Flymake diag message - (push (flymake-make-diagnostic source-buffer (car src-pos) (cdr src-pos) type-symbol msg-text) results))) - (forward-line 1)) - results))) - +Create linter process for SOURCE-BUFFER which invokes CALLBACK once linter is finished. CALLBACK is passed one argument, which is a buffer containing stdout from linter." + (when (process-live-p flymake-eslint--process) + (kill-process flymake-eslint--process)) + (setq flymake-eslint--process + (make-process + :name "flymake-eslint" + :connection-type 'pipe + :noquery t + :buffer (generate-new-buffer " *flymake-eslint*") + :command (list flymake-eslint-executable-name "--no-color" "--no-ignore" "--stdin" "--stdin-filename" (buffer-name source-buffer) (or flymake-eslint-executable-args "")) + :sentinel (lambda (proc &rest ignored) + ;; do stuff upon child process termination + (when (and (eq 'exit (process-status proc)) + ;; make sure we're using the latest lint process + (with-current-buffer source-buffer (eq proc flymake-eslint--process))) + ;; read from eslint output then destroy temp buffer when done + (let ((proc-buffer (process-buffer proc))) + (funcall callback proc-buffer) + (kill-buffer proc-buffer))))))) + (defun flymake-eslint--check-and-report (source-buffer flymake-report-fn) "Internal function. Run eslint against SOURCE-BUFFER and use FLYMAKE-REPORT-FN to report results." - (with-temp-buffer - ;; eslint might report incorrect row/column numbers for unsaved buffers, so we - ;; write the current buffer to a temp file and process that instead. Use a file - ;; in the current directory, not the system's temp directory, in case .eslintrc or - ;; other path-sensitive tools like babel are applied to this file - ;; TODO I think that can be solved with --stdin and --stdin-filename ? - (let ((temp-file-name (flymake-eslint--create-temp-file-from-buffer source-buffer))) - (call-process flymake-eslint-executable-name nil t t "--no-ignore" "--no-color" temp-file-name) - (funcall flymake-report-fn (flymake-eslint--report source-buffer)) - (delete-file temp-file-name)))) - + (flymake-eslint--create-process + source-buffer + (lambda (eslint-stdout) + (funcall flymake-report-fn (flymake-eslint--report eslint-stdout source-buffer)))) + (with-current-buffer source-buffer + (process-send-string flymake-eslint--process (buffer-string)) + (process-send-eof flymake-eslint--process))) (defun flymake-eslint--checker (flymake-report-fn &rest ignored) "Internal function. Run eslint on the current buffer, and report results using FLYMAKE-REPORT-FN. All other parameters are currently IGNORED." - (flymake-eslint--ensure-binary-exists) (flymake-eslint--check-and-report (current-buffer) flymake-report-fn)) @@ -140,6 +151,7 @@ Run eslint on the current buffer, and report results using FLYMAKE-REPORT-FN. A (defun flymake-eslint-enable () "Enable Flymake and add flymake-eslint as a buffer-local Flymake backend." (interactive) + (flymake-eslint--ensure-binary-exists) (flymake-mode t) (add-hook 'flymake-diagnostic-functions 'flymake-eslint--checker nil t))