├── README.md └── js-check.el /README.md: -------------------------------------------------------------------------------- 1 | More and more it seems I want JS to be typed. 2 | 3 | I should probably switch to Typescript. 4 | 5 | But I still need a tool to run the compile constantly and identify 6 | issues and mark them in Emacs. 7 | 8 | So that's what this is. 9 | 10 | Yes, it does not use flymake. What's the point? It's pretty easy to do 11 | this. So this is no depends, except the js depends to do the checking. 12 | 13 | 14 | ## What are the depends? 15 | 16 | eslint is the *compiler* I'm currently using - yep, just a linter 17 | really. That's what I actually want. Variable names wrong, commas and 18 | colons and stuff. 19 | 20 | You can install eslint like this: 21 | 22 | ``` 23 | npm install -g eslint 24 | ``` 25 | 26 | that's all you need. 27 | 28 | ## Starting in a source buffer 29 | 30 | Just do: 31 | 32 | ``` 33 | M-x js-check-init 34 | ``` 35 | 36 | and it will start. 37 | 38 | You can of course, put that in your js buffer hook. 39 | 40 | 41 | ## Other stuff 42 | 43 | I couldn't resist throwing in something that calcs the indent in the 44 | file and sets it appropriately. 45 | 46 | Apparently there is a lot of debate about what the correct indentation 47 | is for js. 48 | 49 | -------------------------------------------------------------------------------- /js-check.el: -------------------------------------------------------------------------------- 1 | ;;; js-check -*- lexical-binding: t -*- 2 | 3 | (require 'js) 4 | 5 | 6 | (defvar js-check-error-overlays nil 7 | "List of overlays used to highlight errors.") 8 | 9 | (make-variable-buffer-local 'js-check-error-overlays) 10 | 11 | (defun js-check-point-entered (&rest arg) 12 | "Called by motion hooks on an error. 13 | Argument ARG event arg." 14 | (if arg (message "js-check-point-entered %s" arg)) 15 | (let ((olay (overlays-at (point)))) 16 | (when olay 17 | (message "js-check-error: %s" (overlay-get (car olay) 'js-check-error))))) 18 | 19 | (defun js-check-parse-errors (source-buffer lint-buffer) 20 | "Check parse errors from eslint. 21 | 22 | SOURCE-BUFFER the source code buffer being checked. 23 | 24 | LINT-BUFFER the output of the lint command." 25 | (save-excursion 26 | (with-current-buffer source-buffer 27 | (mapc 'delete-overlay js-check-error-overlays)) 28 | (with-current-buffer lint-buffer 29 | (goto-char (point-min)) 30 | (save-match-data 31 | (while (re-search-forward 32 | "^.*:\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)$" 33 | nil t) 34 | (let* ((line (string-to-number (match-string 1))) 35 | (char-pos (string-to-number (match-string 2))) 36 | (err-message (match-string 3))) 37 | (with-current-buffer source-buffer 38 | (goto-char (point-min)) 39 | (let* ((line-start (line-beginning-position line)) 40 | (pos-start (+ line-start char-pos)) 41 | (pos-end (line-end-position line)) 42 | (olay (make-overlay pos-start pos-end))) 43 | (setq js-check-error-overlays (cons olay js-check-error-overlays)) 44 | (overlay-put olay 'face '(:underline "red")) 45 | (overlay-put olay 'js-check-error err-message) 46 | (condition-case err 47 | (with-silent-modifications 48 | (add-text-properties 49 | pos-start pos-end 50 | `(point-entered ,(quote js-check-point-entered)))) 51 | (error 52 | (message "js-check couldn't add error because: %s" err))))))))))) 53 | 54 | (defconst js-check-eslint-rules 55 | '(block-scoped-var 56 | curly 57 | eqeqeq 58 | no-multi-spaces 59 | ;;no-unused-vars 60 | no-use-before-define 61 | global-require 62 | no-invalid-regexp 63 | no-fallthrough 64 | no-loop-func 65 | camelcase 66 | capitalized-comments) 67 | "The list of eslint rules that we require.") 68 | 69 | (defun js-check (&optional source-buffer) 70 | "Check a JS file with eslint. 71 | Optional argument SOURCE-BUFFER is the buffer that the test will be on." 72 | (interactive) 73 | (with-current-buffer (or source-buffer (current-buffer)) 74 | (condition-case err 75 | (let* ((name (buffer-name)) 76 | (src-buffer (current-buffer)) 77 | (rules (string-join 78 | (mapcar 79 | (lambda (r) (format "%s: 2" (symbol-name r))) 80 | js-check-eslint-rules) 81 | ",")) 82 | (proc (start-process 83 | name 84 | (let ((buf (format "*jslint-%s*" name))) 85 | (with-current-buffer (get-buffer-create buf) 86 | (let ((buffer-read-only nil)) 87 | (erase-buffer) 88 | (current-buffer)))) 89 | "eslint" 90 | "--no-eslintrc" 91 | "--rule" rules 92 | "-f" "unix" 93 | "--parser-options=ecmaVersion:2018" 94 | (buffer-file-name)))) 95 | (set-process-sentinel 96 | proc 97 | (lambda (process status) 98 | (cond 99 | ((or (equal "finished\n" status) 100 | (equal "exited abnormally with code 1\n" status)) 101 | (js-check-parse-errors src-buffer (process-buffer process)))))) 102 | (with-current-buffer (process-buffer proc) 103 | (compilation-mode))) 104 | (error 105 | (message "error: %s ; is eslint installed in your project? try: npm i eslint --save-dev" err))))) 106 | 107 | (defvar js-check-timer nil 108 | "Timer used to continually check change to a buffer.") 109 | 110 | (defun js-check-timer (source-buffer last-buffer-modified-tick) 111 | "The timer function for running the compilation. 112 | 113 | Argument SOURCE-BUFFER is the buffer the check with be run in. 114 | Argument LAST-BUFFER-MODIFIED-TICK the last tick of the timer." 115 | (and (buffer-live-p source-buffer) 116 | (let ((new-tick (buffer-chars-modified-tick source-buffer))) 117 | (when (> new-tick last-buffer-modified-tick) 118 | (message "js-check running...") 119 | (setq last-buffer-modified-tick new-tick) 120 | (save-buffer) 121 | (js-check source-buffer)) 122 | (with-current-buffer source-buffer 123 | (setq js-check-timer (run-at-time 124 | "2 sec" nil 125 | 'js-check-timer 126 | source-buffer last-buffer-modified-tick)))))) 127 | 128 | 129 | (make-variable-buffer-local 'jscheck-timer) 130 | 131 | (defun js-check-calc-indent () 132 | "Calculate the indent for a js file." 133 | (save-excursion 134 | (goto-char (point-min)) 135 | (re-search-forward "^\\( \\|\t\\)+[^ \t]" nil t) 136 | (let* ((space-end (match-end 1)) 137 | (line-start (line-beginning-position))) 138 | (- space-end line-start)))) 139 | 140 | (make-variable-buffer-local 'js-indent-level) 141 | 142 | (defun js-check-choose-indent () 143 | "Define the indent for a js file and make it local." 144 | (let ((indent (js-check-calc-indent))) 145 | (setq js-indent-level indent))) 146 | 147 | 148 | (defun js-check-init () 149 | "Initialize a constantly checking compilation for this buffer." 150 | (interactive) 151 | (js-check-choose-indent) 152 | ;; kick it off 153 | (js-check) 154 | (let* ((source-buffer (current-buffer)) 155 | (last-buffer-modified-tick (buffer-chars-modified-tick source-buffer))) 156 | (setq js-check-timer 157 | (run-at-time 158 | "5 sec" nil 159 | 'js-check-timer 160 | source-buffer last-buffer-modified-tick)))) 161 | 162 | ;;; End 163 | --------------------------------------------------------------------------------