From 5b3e588762bca171c31672598db10bb5db989ef0 Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Fri, 14 May 2021 13:54:40 +0200 Subject: [PATCH 1/7] plugin/emacs: use --stdin in flycheck Signed-off-by: wagner riffel --- plugin/emacs/flycheck-quicklintjs.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/emacs/flycheck-quicklintjs.el b/plugin/emacs/flycheck-quicklintjs.el index d2d65982bf..f061bc9557 100644 --- a/plugin/emacs/flycheck-quicklintjs.el +++ b/plugin/emacs/flycheck-quicklintjs.el @@ -51,15 +51,15 @@ https://quick-lint-js.com" :command ("quick-lint-js" "--output-format=gnu-like" - (eval flycheck-quicklintjs-args) - source) - :standard-input 't + "--stdin" + (eval flycheck-quicklintjs-args)) + :standard-input t :error-patterns ((error - line-start (file-name) ":" line ":" column ":" + line-start ":" line ":" column ":" (zero-or-more whitespace) "error:" (zero-or-more whitespace) (message) line-end) (warning - line-start (file-name) ":" line ":" column ":" + line-start ":" line ":" column ":" (zero-or-more whitespace) "warning:" (zero-or-more whitespace) (message) line-end)) :modes js-mode) From 05aff9db812110b40490ec78c8b8b5f8ed20f6d4 Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Fri, 14 May 2021 20:13:38 +0200 Subject: [PATCH 2/7] plugin/emacs: support Flycheck error explainer Also while at it, remove tabs accidentally introduced by vim and correct the Flycheck spelling. Signed-off-by: wagner riffel --- plugin/emacs/flycheck-quicklintjs.el | 16 +++++--- plugin/emacs/test/quicklintjs-test.el | 54 ++++++++++++++------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/plugin/emacs/flycheck-quicklintjs.el b/plugin/emacs/flycheck-quicklintjs.el index f061bc9557..61f5c60a12 100644 --- a/plugin/emacs/flycheck-quicklintjs.el +++ b/plugin/emacs/flycheck-quicklintjs.el @@ -1,4 +1,4 @@ -;;; flycheck-quicklintjs --- quick-lint-js flycheck support -*- lexical-binding: t; -*- +;;; flycheck-quicklintjs --- quick-lint-js Flycheck support -*- lexical-binding: t; -*- ;;; Commentary: @@ -50,18 +50,22 @@ https://quick-lint-js.com" :command ("quick-lint-js" - "--output-format=gnu-like" - "--stdin" - (eval flycheck-quicklintjs-args)) + "--output-format=gnu-like" + "--stdin" + (eval flycheck-quicklintjs-args)) :standard-input t :error-patterns ((error line-start ":" line ":" column ":" (zero-or-more whitespace) "error:" (zero-or-more whitespace) - (message) line-end) + (message) "[" (id "E" (one-or-more digit)) "]" line-end) (warning line-start ":" line ":" column ":" (zero-or-more whitespace) "warning:" (zero-or-more whitespace) - (message) line-end)) + (message) "[" (id "E" (one-or-more digit)) "]" line-end)) + :error-explainer (lambda (err) + (let ((error-code (flycheck-error-id err)) + (url "https://quick-lint-js.com/errors/#%s")) + (and error-code `(url . ,(format url error-code))))) :modes js-mode) (add-to-list 'flycheck-checkers 'javascript-quicklintjs t) diff --git a/plugin/emacs/test/quicklintjs-test.el b/plugin/emacs/test/quicklintjs-test.el index c146d8192c..0aef2c176d 100644 --- a/plugin/emacs/test/quicklintjs-test.el +++ b/plugin/emacs/test/quicklintjs-test.el @@ -6,8 +6,8 @@ (require 'package) (defconst cache-dir-name (concat - (expand-file-name default-directory) - ".melpa-cache/")) + (expand-file-name default-directory) + ".melpa-cache/")) (defun quicklintjs-test-main () (setq package-user-dir cache-dir-name @@ -17,14 +17,14 @@ (package-initialize) (unless package-archive-contents - (package-refresh-contents)) + (package-refresh-contents)) (unless (package-installed-p 'flycheck) - ;; the DONT-SELECT argument is only available and make sense - ;; in emacs 25 and above. - (if (> emacs-major-version 24) - (package-install 'flycheck t) - (package-install 'flycheck))) + ;; the DONT-SELECT argument is only available and make sense + ;; in emacs 25 and above. + (if (> emacs-major-version 24) + (package-install 'flycheck t) + (package-install 'flycheck))) (require 'flycheck) (require 'flycheck-ert) @@ -36,25 +36,27 @@ (should (member 'javascript-quicklintjs flycheck-checkers))) (defun def-flycheck-tests () - (flycheck-ert-def-checker-test javascript-quicklintjs javascript error - (let ((flycheck-checker 'javascript-quicklintjs) - (inhibit-message t)) - (flycheck-ert-should-syntax-check - "test/error.js" '(js-mode) - '(1 1 error "missing name in function statement [E061]" - :checker javascript-quicklintjs) - '(1 12 error "unclosed code block; expected '}' by end of file [E134]" - :checker javascript-quicklintjs) - '(2 7 error "unexpected token in variable declaration; expected variable name [E114]" - :checker javascript-quicklintjs)))) + (flycheck-ert-def-checker-test + javascript-quicklintjs javascript error + (let ((flycheck-checker 'javascript-quicklintjs) + (inhibit-message t)) + (flycheck-ert-should-syntax-check + "test/error.js" '(js-mode) + '(1 1 error "missing name in function statement" + :id "E061" :checker javascript-quicklintjs) + '(1 12 error "unclosed code block; expected '}' by end of file" + :id "E134" :checker javascript-quicklintjs) + '(2 7 error "unexpected token in variable declaration; expected variable name" + :id "E114" :checker javascript-quicklintjs)))) - (flycheck-ert-def-checker-test javascript-quicklintjs javascript warning - (let ((flycheck-checker 'javascript-quicklintjs) - (inhibit-message t)) - (flycheck-ert-should-syntax-check - "test/warning.js" '(js-mode) - '(1 1 warning "assignment to undeclared variable [E059]" - :checker javascript-quicklintjs))))) + (flycheck-ert-def-checker-test + javascript-quicklintjs javascript warning + (let ((flycheck-checker 'javascript-quicklintjs) + (inhibit-message t)) + (flycheck-ert-should-syntax-check + "test/warning.js" '(js-mode) + '(1 1 warning "assignment to undeclared variable" + :id "E059":checker javascript-quicklintjs))))) ;; quick-lint-js finds bugs in JavaScript programs. ;; Copyright (C) 2020 Matthew "strager" Glazar From 54bc9ebd31fac7170fca963230ab30e427c610a3 Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Sat, 22 May 2021 06:03:48 +0200 Subject: [PATCH 3/7] plugin/emacs: add Eglot support This commit also ignores client notifications that would make quicklintjs to crash and accept "js" as a language_id for files, that's what Eglot uses for JavaScript. Signed-off-by: wagner riffel --- plugin/emacs/CMakeLists.txt | 1 + plugin/emacs/eglot-quicklintjs.el | 62 ++++++++++++++++++++++++++ plugin/emacs/test/quicklintjs-test.el | 32 +++++++++----- src/lsp-server.cpp | 8 +++- test/test-lsp-server.cpp | 63 +++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 plugin/emacs/eglot-quicklintjs.el diff --git a/plugin/emacs/CMakeLists.txt b/plugin/emacs/CMakeLists.txt index cf67df8b47..42aaaa0a47 100644 --- a/plugin/emacs/CMakeLists.txt +++ b/plugin/emacs/CMakeLists.txt @@ -6,6 +6,7 @@ include(GNUInstallDirs) install( FILES flycheck-quicklintjs.el + FILES eglot-quicklintjs.el DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/emacs/site-lisp" ) diff --git a/plugin/emacs/eglot-quicklintjs.el b/plugin/emacs/eglot-quicklintjs.el new file mode 100644 index 0000000000..fbc7249a16 --- /dev/null +++ b/plugin/emacs/eglot-quicklintjs.el @@ -0,0 +1,62 @@ +;;; eglot-quicklintjs.el --- Eglot support for quick-lint-js -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Eglot support for quick-lint-js. + +;; Example usage in your init.el: +;; +;; (require 'eglot-quicklintjs) +;; +;; (defun my-eglot-quicklintjs-setup () +;; "Configure eglot-quicklintjs for better experience." +;; +;; ;; Remove the time to wait after last change before automatically checking +;; ;; buffer. The default is 0.5 (500ms) +;; (setq-local eglot-send-changes-idle-time 0)) +;; (add-hook 'js-mode-hook #'my-eglot-quicklintjs-setup) + +;;; Code: + +(require 'eglot) + +(defgroup eglot-quicklintjs nil + "quick-lint-js Eglot integration." + :group 'eglot-quicklintjs + :link '(url-link :tag "Website" "https://quick-lint-js.com")) + +(defcustom eglot-quicklintjs-program "quick-lint-js" + "Path to quick-lint-js program to run." + :group 'eglot-quicklintjs + :type 'stringp) + +(defcustom eglot-quicklintjs-args nil + "Arguments to quick-lint-js." + :group 'eglot-quicklintjs + :type '(repeat string)) + +(add-to-list 'eglot-server-programs `(js-mode . (,eglot-quicklintjs-program + "--lsp-server" + ,@eglot-quicklintjs-args))) + +(provide 'eglot-quicklintjs) + +;; quick-lint-js finds bugs in JavaScript programs. +;; Copyright (C) 2020 Matthew Glazar +;; +;; This file is part of quick-lint-js. +;; +;; quick-lint-js is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; quick-lint-js is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with quick-lint-js. If not, see . + +;;; eglot-quicklintjs.el ends here diff --git a/plugin/emacs/test/quicklintjs-test.el b/plugin/emacs/test/quicklintjs-test.el index 0aef2c176d..e664c5dff7 100644 --- a/plugin/emacs/test/quicklintjs-test.el +++ b/plugin/emacs/test/quicklintjs-test.el @@ -9,6 +9,12 @@ (expand-file-name default-directory) ".melpa-cache/")) +(defun quicklintjs-install-deps (deps) + (mapcar (lambda (pkg) (unless (package-installed-p pkg) + (if (> emacs-major-version 24) + (package-install pkg t) + (package-install pkg)))) deps)) + (defun quicklintjs-test-main () (setq package-user-dir cache-dir-name package-check-signature nil) @@ -19,23 +25,27 @@ (unless package-archive-contents (package-refresh-contents)) - (unless (package-installed-p 'flycheck) - ;; the DONT-SELECT argument is only available and make sense - ;; in emacs 25 and above. - (if (> emacs-major-version 24) - (package-install 'flycheck t) - (package-install 'flycheck))) + (quicklintjs-install-deps (if (>= emacs-major-version 26) + '(flycheck eglot) + '(flycheck))) + (def-flycheck-tests) + (def-eglot-tests) + (ert-run-tests-batch-and-exit)) + +(defun def-eglot-tests () + (when (>= emacs-major-version 26) + (require 'eglot-quicklintjs) + (ert-deftest quicklintjs-is-in-eglot-servers () + (should (member '(js-mode "quick-lint-js" "--lsp") eglot-server-programs))))) +(defun def-flycheck-tests () (require 'flycheck) (require 'flycheck-ert) (require 'flycheck-quicklintjs) - (def-flycheck-tests) - (ert-run-tests-batch-and-exit)) -(ert-deftest quicklintjs-is-in-checkers () - (should (member 'javascript-quicklintjs flycheck-checkers))) + (ert-deftest quicklintjs-is-in-flycheck-checkers () + (should (member 'javascript-quicklintjs flycheck-checkers))) -(defun def-flycheck-tests () (flycheck-ert-def-checker-test javascript-quicklintjs javascript error (let ((flycheck-checker 'javascript-quicklintjs) diff --git a/src/lsp-server.cpp b/src/lsp-server.cpp index 622c7ebedb..e154fb5b70 100644 --- a/src/lsp-server.cpp +++ b/src/lsp-server.cpp @@ -120,6 +120,12 @@ void linting_lsp_server_handler::handle_notification( std::exit(this->shutdown_requested_ ? 0 : 1); } else if (starts_with(method, "$/"sv)) { // Do nothing. + } else if (method == "workspace/didChangeConfiguration") { + // Do nothing. + } else if (method == "textDocument/didSave") { + // Do nothing. + } else if (method == "textDocument/willSave") { + // Do nothing. } else { QLJS_UNIMPLEMENTED(); } @@ -266,7 +272,7 @@ void linting_lsp_server_handler:: doc.doc.set_text(make_string_view(text_document["text"])); doc.version_json = get_raw_json(version); - if (language_id == "javascript") { + if (language_id == "javascript" || language_id == "js") { doc.type = document_type::lintable; this->linter_.lint_and_get_diagnostics_notification( *this->get_config(document_path), doc.doc.string(), get_raw_json(uri), diff --git a/test/test-lsp-server.cpp b/test/test-lsp-server.cpp index c481b8102b..778a4b7e66 100644 --- a/test/test-lsp-server.cpp +++ b/test/test-lsp-server.cpp @@ -284,6 +284,69 @@ TEST_F(test_linting_lsp_server, opening_document_lints) { EXPECT_THAT(this->lint_calls, ElementsAre(u8"let x = x;")); } +TEST_F(test_linting_lsp_server, opening_document_language_id_js_lints) { + this->lint_callback = [&](configuration&, padded_string_view code, + string8_view uri_json, string8_view version_json, + byte_buffer& notification_json) { + EXPECT_EQ(code, u8"let x = x;"); + EXPECT_EQ(uri_json, u8"\"file:///test.js\""); + EXPECT_EQ(version_json, u8"10"); + notification_json.append_copy( + u8R"--( + { + "method":"textDocument/publishDiagnostics", + "params":{ + "uri": "file:///test.js", + "version": 10, + "diagnostics": [ + { + "range": { + "start": {"line": 0, "character": 8}, + "end": {"line": 0, "character": 9} + }, + "severity": 1, + "message": "variable used before declaration: x" + } + ] + }, + "jsonrpc":"2.0" + } + )--"); + }; + + this->server.append( + make_message(u8R"({ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "file:///test.js", + "languageId": "js", + "version": 10, + "text": "let x = x;" + } + } + })")); + + ASSERT_EQ(this->client.messages.size(), 1); + ::Json::Value& response = this->client.messages[0]; + EXPECT_EQ(response["method"], "textDocument/publishDiagnostics"); + EXPECT_FALSE(response.isMember("error")); + // LSP PublishDiagnosticsParams: + EXPECT_EQ(response["params"]["uri"], "file:///test.js"); + EXPECT_EQ(response["params"]["version"], 10); + ::Json::Value& diagnostics = response["params"]["diagnostics"]; + EXPECT_EQ(diagnostics.size(), 1); + EXPECT_EQ(diagnostics[0]["range"]["start"]["line"], 0); + EXPECT_EQ(diagnostics[0]["range"]["start"]["character"], 8); + EXPECT_EQ(diagnostics[0]["range"]["end"]["line"], 0); + EXPECT_EQ(diagnostics[0]["range"]["end"]["character"], 9); + EXPECT_EQ(diagnostics[0]["severity"], lsp_error_severity); + EXPECT_EQ(diagnostics[0]["message"], "variable used before declaration: x"); + + EXPECT_THAT(this->lint_calls, ElementsAre(u8"let x = x;")); +} + TEST_F(test_linting_lsp_server, changing_document_with_full_text_lints) { this->server.append( make_message(u8R"({ From 88874b305e7bfc40a3bc48aad4153e639de589fd Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Sat, 22 May 2021 17:54:43 +0200 Subject: [PATCH 4/7] plugin/emacs: add LSP Mode support Signed-off-by: wagner riffel --- plugin/emacs/CMakeLists.txt | 1 + plugin/emacs/lsp-quicklintjs.el | 63 +++++++++++++++++++++++++++ plugin/emacs/test/quicklintjs-test.el | 9 +++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 plugin/emacs/lsp-quicklintjs.el diff --git a/plugin/emacs/CMakeLists.txt b/plugin/emacs/CMakeLists.txt index 42aaaa0a47..9579cb19ac 100644 --- a/plugin/emacs/CMakeLists.txt +++ b/plugin/emacs/CMakeLists.txt @@ -6,6 +6,7 @@ include(GNUInstallDirs) install( FILES flycheck-quicklintjs.el + FILES lsp-quicklintjs.el FILES eglot-quicklintjs.el DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/emacs/site-lisp" ) diff --git a/plugin/emacs/lsp-quicklintjs.el b/plugin/emacs/lsp-quicklintjs.el new file mode 100644 index 0000000000..48a28b0268 --- /dev/null +++ b/plugin/emacs/lsp-quicklintjs.el @@ -0,0 +1,63 @@ +;;; lsp-quicklintjs --- LSP support for quick-lint-js -*- lexical-binding: t; -*- + +;;; Commentary: + +;; LSP Mode support for quick-lint-js. + +;; Example usage in your init.el: +;; +;; (require 'lsp-quicklintjs) +;; +;; (defun my-lsp-quicklintjs-setup () +;; "Configure lsp-quicklintjs for better experience." +;; ;; Remove the time to wait after last change before automatically checking +;; ;; buffer. The default is 0.5 (500ms) +;; (setq-local lsp-idle-delay 0)) +;; (add-hook 'js-mode-hook #'my-lsp-quicklintjs-setup) + +;;; Code: + +(require 'lsp-mode) + +(defgroup lsp-quicklintjs nil + "quick-lint-js LSP Mode integration." + :link '(url-link :tag "Website" "https://quick-lint-js.com")) + +(defcustom lsp-quicklintjs-program "quick-lint-js" + "Path to quick-lint-js program to run." + :group 'lsp-quicklintjs + :type 'stringp) + +(defcustom lsp-quicklintjs-args nil + "Arguments to quick-lint-js." + :group 'lsp-quicklintjs + :type '(repeat string)) + +(lsp-register-client + (make-lsp-client + :new-connection (lsp-stdio-connection `(,lsp-quicklintjs-program "--lsp-server" + ,@lsp-quicklintjs-args)) + :major-modes '(js-mode) + :server-id 'quick-lint-js)) + +(provide 'lsp-quicklintjs) + +;; quick-lint-js finds bugs in JavaScript programs. +;; Copyright (C) 2020 Matthew Glazar +;; +;; This file is part of quick-lint-js. +;; +;; quick-lint-js is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; quick-lint-js is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with quick-lint-js. If not, see . + +;;; lsp-quicklintjs.el ends here diff --git a/plugin/emacs/test/quicklintjs-test.el b/plugin/emacs/test/quicklintjs-test.el index e664c5dff7..5a5c21af96 100644 --- a/plugin/emacs/test/quicklintjs-test.el +++ b/plugin/emacs/test/quicklintjs-test.el @@ -26,10 +26,11 @@ (package-refresh-contents)) (quicklintjs-install-deps (if (>= emacs-major-version 26) - '(flycheck eglot) + '(flycheck eglot lsp-mode) '(flycheck))) (def-flycheck-tests) (def-eglot-tests) + (def-lsp-tests) (ert-run-tests-batch-and-exit)) (defun def-eglot-tests () @@ -38,6 +39,12 @@ (ert-deftest quicklintjs-is-in-eglot-servers () (should (member '(js-mode "quick-lint-js" "--lsp") eglot-server-programs))))) +(defun def-lsp-tests () + (when (>= emacs-major-version 26) + (require 'lsp-quicklintjs) + (ert-deftest quicklintjs-is-in-lsp-clients () + (should (gethash 'quick-lint-js lsp-clients))))) + (defun def-flycheck-tests () (require 'flycheck) (require 'flycheck-ert) From fc0b98bcfbdd171270061346d6218975c86e689f Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Thu, 10 Jun 2021 21:16:59 +0200 Subject: [PATCH 5/7] plugin/emacs: add Flymake backend support Signed-off-by: wagner riffel --- plugin/emacs/CMakeLists.txt | 1 + plugin/emacs/flymake-quicklintjs.el | 138 ++++++++++++++++++++++++++ plugin/emacs/test/quicklintjs-test.el | 29 ++++++ 3 files changed, 168 insertions(+) create mode 100644 plugin/emacs/flymake-quicklintjs.el diff --git a/plugin/emacs/CMakeLists.txt b/plugin/emacs/CMakeLists.txt index 9579cb19ac..6ff75d213a 100644 --- a/plugin/emacs/CMakeLists.txt +++ b/plugin/emacs/CMakeLists.txt @@ -8,6 +8,7 @@ install( FILES flycheck-quicklintjs.el FILES lsp-quicklintjs.el FILES eglot-quicklintjs.el + FILES flymake-quicklintjs.el DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/emacs/site-lisp" ) diff --git a/plugin/emacs/flymake-quicklintjs.el b/plugin/emacs/flymake-quicklintjs.el new file mode 100644 index 0000000000..523b4b6235 --- /dev/null +++ b/plugin/emacs/flymake-quicklintjs.el @@ -0,0 +1,138 @@ +;;; flymake-quicklintjs.el --- Flymake support for quick-lint-js -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Flymake support for quick-lint-js. + +;; Example usage in your init.el: +;; +;; (require 'flymake-quicklintjs) +;; +;; (defun my-flymake-quicklintjs-setup () +;; "Configure flymake-quicklintjs for better experience." +;; +;; ;; Enable Flymake +;; (unless (bound-and-true-p flymake-mode) +;; (flymake-mode)) +;; (add-hook 'flymake-diagnostic-functions #'flymake-quicklintjs nil t) +;; +;; ;; Remove the time to wait after last change before automatically checking +;; ;; buffer. The default is 0.5 (500ms) +;; (setq-local flymake-no-changes-timeout 0)) +;; (add-hook 'js-mode-hook #'my-flymake-quicklintjs-setup) + +;;; Code: + +(defgroup flymake-quicklintjs nil + "Flymake backend for quick-lint-js" + :link '(url-link :tag "Website" "https://quick-lint-js.com")) + +(defcustom flymake-quicklintjs-program "quick-lint-js" + "Path to quick-lint-js program to run." + :group 'flymake-quicklintjs + :type 'stringp) + +(defcustom flymake-quicklintjs-args nil + "Arguments to quick-lint-js." + :group 'flymake-quicklintjs + :type '(repeat 'string)) + +(defconst flymake-quicklintjs--regexp (concat + "^:\\([0-9]+\\):\\([0-9]+\\): " + "\\(warning\\|error\\): " + "\\(.+\\) \\(\\[E[0-9]+\\]\\)$") + "Regular expression to match quick-lint-js gnu-like output format.") + +(defvar-local flymake-quicklintjs--proc nil + "Internal variable for `flymake-quicklintjs'") + +(defun flymake-quicklintjs--error-region (src-buf line col) + "Compute SRC-BUF region (BEG . END) corresponding to LINE and COL." + (with-current-buffer src-buf + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (goto-char (min (+ (line-beginning-position) col) (line-end-position))) + (or (bounds-of-thing-at-point 'word) (cons (point) (point)))))) + +(defun flymake-quicklintjs--make-diagnostics (src-buf) + "Parse gnu-like compilation messages in current buffer. +Return a list of Flymake diagnostic objects for the source buffer +SRC-BUF." + (let (diag-accum) + (while (not (eobp)) + (when (looking-at flymake-quicklintjs--regexp) + (let* ((line (string-to-number (match-string 1))) + (col (string-to-number (match-string 2))) + (type (match-string 3)) + (msg (match-string 4)) + (type-sym (if (string= "error" type) :error :warning))) + (let* ((diag-region (flymake-quicklintjs--error-region src-buf line col)) + (beg (car diag-region)) + (end (min (buffer-size src-buf) (cdr diag-region)))) + (push (flymake-make-diagnostic src-buf beg end type-sym msg) + diag-accum)))) + (forward-line 1)) + diag-accum)) + +;;;###autoload +(defun flymake-quicklintjs (report-fn &rest _args) + "Flymake backend for quick-lint-js linter. +This backend uses `flymake-quicklintjs-program' (which see) to launch a +quick-lint-js process that is passed the current buffer's contents via stdin. +REPORT-FN is Flymake's callback." + (when (process-live-p flymake-quicklintjs--proc) + (kill-process flymake-quicklintjs--proc)) + (let ((src-buf (current-buffer))) + (setq flymake-quicklintjs--proc + (make-process + :name "flymake-quicklintjs" + :connection-type 'pipe + :noquery t + :buffer (generate-new-buffer " *flymake-quicklintjs*") + :command `(,flymake-quicklintjs-program + "--stdin" "--output-format=gnu-like" + ,@flymake-quicklintjs-args) + :sentinel + (lambda (p _ev) + (unwind-protect + (when (eq 'exit (process-status p)) + (when (with-current-buffer src-buf (eq p flymake-quicklintjs--proc)) + (with-current-buffer (process-buffer p) + (goto-char (point-min)) + (let ((diags + (flymake-quicklintjs--make-diagnostics src-buf))) + (if (or diags (zerop (process-exit-status p))) + (funcall report-fn diags + :region (cons (point-min) (point-max))) + (funcall report-fn + :panic :explanation + (buffer-substring + (point-min) (progn (goto-char (point-min)) + (line-end-position))))))))) + (unless (process-live-p p) + (kill-buffer (process-buffer p))))))) + (process-send-region flymake-quicklintjs--proc (point-min) (point-max)) + (process-send-eof flymake-quicklintjs--proc))) + +(provide 'flymake-quicklintjs) + +;; quick-lint-js finds bugs in JavaScript programs. +;; Copyright (C) 2020 Matthew Glazar +;; +;; This file is part of quick-lint-js. +;; +;; quick-lint-js is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; quick-lint-js is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with quick-lint-js. If not, see . + +;;; flymake-quicklintjs.el ends here diff --git a/plugin/emacs/test/quicklintjs-test.el b/plugin/emacs/test/quicklintjs-test.el index 5a5c21af96..0960e22355 100644 --- a/plugin/emacs/test/quicklintjs-test.el +++ b/plugin/emacs/test/quicklintjs-test.el @@ -31,8 +31,37 @@ (def-flycheck-tests) (def-eglot-tests) (def-lsp-tests) + (def-flymake-tests) (ert-run-tests-batch-and-exit)) +(defun def-flymake-tests () + (require 'flymake-quicklintjs) + (ert-deftest quicklintjs-flymake-parse-errors-and-warnings () + (let ((errors-buf (generate-new-buffer "*errors-buf*")) + (js-buf (generate-new-buffer "*js-buf*"))) + (with-current-buffer js-buf + (insert "foobar\n") + (insert "/*💩*/ foobar \n") + (insert "foobar /*💩*/\n") + (insert "function\n")) + (with-current-buffer errors-buf + (insert ":4:1: error: missing name in function statement [E061]\n") + (insert ":1:1: warning: use of undeclared variable: foobar [E057]\n") + (insert ":2:12: warning: use of undeclared variable: foobar [E057]\n") + (insert ":3:1: warning: use of undeclared variable: foobar [E057]\n") + (goto-char (point-min)) + + (let ((diags (list + (flymake-make-diagnostic js-buf 25 31 :warning + "use of undeclared variable: foobar") + (flymake-make-diagnostic js-buf 16 22 :warning + "use of undeclared variable: foobar") + (flymake-make-diagnostic js-buf 1 7 :warning + "use of undeclared variable: foobar") + (flymake-make-diagnostic js-buf 38 46 :error + "missing name in function statement")))) + (should (equal (flymake-quicklintjs--make-diagnostics js-buf) diags))))))) + (defun def-eglot-tests () (when (>= emacs-major-version 26) (require 'eglot-quicklintjs) From 15020fd7f6c352a9a567ac786cb92761fa2180f9 Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Fri, 11 Jun 2021 15:13:17 +0200 Subject: [PATCH 6/7] plugin/emacs: skip test when Emacs version is lower than 26 Signed-off-by: wagner riffel --- plugin/emacs/flymake-quicklintjs.el | 2 ++ plugin/emacs/test/quicklintjs-test.el | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugin/emacs/flymake-quicklintjs.el b/plugin/emacs/flymake-quicklintjs.el index 523b4b6235..a0b1c04d3e 100644 --- a/plugin/emacs/flymake-quicklintjs.el +++ b/plugin/emacs/flymake-quicklintjs.el @@ -23,6 +23,8 @@ ;;; Code: +(require 'flymake) + (defgroup flymake-quicklintjs nil "Flymake backend for quick-lint-js" :link '(url-link :tag "Website" "https://quick-lint-js.com")) diff --git a/plugin/emacs/test/quicklintjs-test.el b/plugin/emacs/test/quicklintjs-test.el index 0960e22355..a7ec041544 100644 --- a/plugin/emacs/test/quicklintjs-test.el +++ b/plugin/emacs/test/quicklintjs-test.el @@ -35,8 +35,9 @@ (ert-run-tests-batch-and-exit)) (defun def-flymake-tests () - (require 'flymake-quicklintjs) (ert-deftest quicklintjs-flymake-parse-errors-and-warnings () + (skip-unless (>= emacs-major-version 26)) + (require 'flymake-quicklintjs) (let ((errors-buf (generate-new-buffer "*errors-buf*")) (js-buf (generate-new-buffer "*js-buf*"))) (with-current-buffer js-buf @@ -63,16 +64,16 @@ (should (equal (flymake-quicklintjs--make-diagnostics js-buf) diags))))))) (defun def-eglot-tests () - (when (>= emacs-major-version 26) + (ert-deftest quicklintjs-is-in-eglot-servers () + (skip-unless (>= emacs-major-version 26)) (require 'eglot-quicklintjs) - (ert-deftest quicklintjs-is-in-eglot-servers () - (should (member '(js-mode "quick-lint-js" "--lsp") eglot-server-programs))))) + (should (member '(js-mode "quick-lint-js" "--lsp-server") eglot-server-programs)))) (defun def-lsp-tests () - (when (>= emacs-major-version 26) + (ert-deftest quicklintjs-is-in-lsp-clients () + (skip-unless (>= emacs-major-version 26)) (require 'lsp-quicklintjs) - (ert-deftest quicklintjs-is-in-lsp-clients () - (should (gethash 'quick-lint-js lsp-clients))))) + (should (gethash 'quick-lint-js lsp-clients)))) (defun def-flycheck-tests () (require 'flycheck) From d7e006af083556bd7f69aa1d08f9d73061a82dbb Mon Sep 17 00:00:00 2001 From: wagner riffel Date: Sat, 12 Jun 2021 16:08:00 +0200 Subject: [PATCH 7/7] plugin/emacs: add installation steps in README for new integrations Signed-off-by: wagner riffel --- plugin/emacs/README.md | 164 ++++++++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 34 deletions(-) diff --git a/plugin/emacs/README.md b/plugin/emacs/README.md index 247c0a97a1..f8ac28e6fc 100644 --- a/plugin/emacs/README.md +++ b/plugin/emacs/README.md @@ -1,53 +1,79 @@ -# flycheck-quicklintjs Emacs plugin +# Emacs plugin -This directory contains a plugin for the [Emacs text editor][Emacs]. - -This plugin integrates with other Emacs plugins to show lint rules when editing -files in Emacs. - -**Important**: Installing this Emacs plugin will not also install quick-lint-js -itself. You must separately install quick-lint-js in order for this plugin to -work. - -Flycheck plugin must be installed and configured in order for this plugin to -work: - -* [Flycheck] - On the fly syntax checking, version 31 or newer +This directory contains quick-lint-js integration for [GNU Emacs][Emacs] plugins +ecosystem including [Flymake], [Flycheck], [Eglot] and [LSP Mode]. ## Installation -### Manually +**Important**: The following steps will not also install quick-lint-js itself. +You must separately install [quick-lint-js](https://quick-lint-js.com/install) +in order for this plugin to work. + +Copy which plugin integration you like inside a folder present in Emacs +`load-path`, the plugin integration files are: +- eglot-quicklintjs.el +- flycheck-quicklintjs.el +- flymake-quicklintjs.el +- lsp-quicklintjs.el -You need to copy flycheck-quicklintjs.el inside a folder present in Emacs -load-path, a default one that is looked up by Emacs is +A default folder that is looked up by Emacs is `/usr/local/share/emacs/site-lisp` but requires root privileges, alternatively -you may append a custom folder of your preference containing the -`flycheck-quicklintjs.el` file to Emacs load-path variable, for example: +you may append a custom folder of your preference containing the plugin +integration files to [Emacs load-path][load-path] variable, for example: ```lisp (add-to-list 'load-path "~/.emacs/quicklintjs") ``` -A less convenient way also possible is appending to `load-path` with Emacs -command line argument `-L folder` +It's also possible to append `load-path` with Emacs command line argument +`-L folder` -### Initialization +### Eglot -When Emacs is able to find the quick-lint-js library, you need to load it, -your [Emacs Initialization] file is a good place: +Make sure you have Eglot installed, it's available on [ELPA] or [MELPA] + +`M-x package-install RET eglot RET` + +Now load `eglot-quicklintjs` and quick-lint-js should be registered as a LSP +server, starting Eglot with `M-x eglot RET` in a js-mode buffer should get you +started. + +Usage example in your [Emacs initialization] file. + +
+init.el ```lisp -(require 'flycheck) -(require 'flycheck-quicklintjs) +(require 'eglot-quicklintjs) + +(defun my-eglot-quicklintjs-setup () + "Configure eglot-quicklintjs for better experience." + + ;; Remove the time to wait after last change before automatically checking + ;; buffer. The default is 0.5 (500ms) + (setq-local eglot-send-changes-idle-time 0)) +(add-hook 'js-mode-hook #'my-eglot-quicklintjs-setup) ``` -At this point quick-lint-js should be registered as a checker, which you can -verify with `M-x flycheck-verify-setup`, optionally we suggest that you add -this hook to `js-mode`, which will remove the delay after you type that -Flycheck waits before running the checker, also registers `quick-lint-js` -as the selected checker by default, instead of `eslint`. +
+ +### Flycheck + +First install the Flycheck, it's available on MELPA. + +`M-x package-install RET flycheck RET` + +Now load flycheck-quicklintjs and quick-lint-js should be registered as a +Flycheck checker, you may inspect your setup with +`M-x flycheck-verify-setup RET`. + +Usage example in your [Emacs initialization] file. +
+ init.el ```lisp +(require 'flycheck-quicklintjs) + (defun my-flycheck-quicklintjs-setup () "Configure flycheck-quicklintjs for better experience." @@ -68,6 +94,76 @@ as the selected checker by default, instead of `eslint`. (add-hook 'js-mode-hook #'my-flycheck-quicklintjs-setup) ``` -[Flycheck]: https://www.flycheck.org/ -[Emacs]: https://www.gnu.org/software/emacs/ -[Emacs Initialization]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Init-File.html +
+ +### Flymake + +Flymake is bultin on Emacs but it's supported only on major version 26 or +higher. + +After loading flymake-quicklintjs, you can use `flymake-quicklintjs` function as +a backend for Flymake. + +Usage example in your [Emacs initialization] file. +
+init.el + +```lisp +(require 'flymake-quicklintjs) + +(defun my-flymake-quicklintjs-setup () + "Configure flymake-quicklintjs for better experience." + + ;; Enable Flymake + (unless (bound-and-true-p flymake-mode) + (flymake-mode)) + (add-hook 'flymake-diagnostic-functions #'flymake-quicklintjs nil t) + + ;; Remove the time to wait after last change before automatically checking + ;; buffer. The default is 0.5 (500ms) + (setq-local flymake-no-changes-timeout 0)) +(add-hook 'js-mode-hook #'my-flymake-quicklintjs-setup) +``` + +
+ +### LSP Mode + +First install the LSP Mode, if you have MELPA configured simply: + +`M-x package-install RET lsp-mode RET` + +Now require lsp-quicklintjs and quick-lint-js should be registered as a LSP +server, starting LSP Mode with `M-x lsp RET` in a js-mode buffer should get you +started. + +Usage example in your [Emacs initialization] file. +
+init.el + +```lisp +(require 'lsp-quicklintjs) + +(defun my-lsp-quicklintjs-setup () + "Configure lsp-quicklintjs for better experience." + ;; Remove the time to wait after last change before automatically checking + ;; buffer. The default is 0.5 (500ms) + (setq-local lsp-idle-delay 0)) +(add-hook 'js-mode-hook #'my-lsp-quicklintjs-setup) +``` + +
+ + +[Flymake]: +https://www.gnu.org/software/emacs/manual/html_node/flymake/index.html +[Flycheck]: https://www.flycheck.org +[Eglot]: https://github.com/joaotavora/eglot +[LSP Mode]: https://emacs-lsp.github.io/lsp-mode +[Emacs]: https://www.gnu.org/software/emacs +[Emacs initialization]: +https://www.gnu.org/software/emacs/manual/html_node/emacs/Init-File.html +[load-path]: +https://www.gnu.org/software/emacs/manual/html_node/emacs/Lisp-Libraries.html +[ELPA]: https://elpa.gnu.org +[MELPA]: https://melpa.org/#/getting-started