diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 438e6d9..8b382f9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -49,7 +49,7 @@ jobs:
- name: Setup NodeJS # for JS and TS analyzer
uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 18
- uses: jcs090218/setup-emacs@master
with:
diff --git a/Makefile b/Makefile
index 7caf549..df838cf 100644
--- a/Makefile
+++ b/Makefile
@@ -3,11 +3,11 @@ SHELL := /usr/bin/env bash
EMACS ?= emacs
EASK ?= eask
-TEST-FILES := $(shell ls test/*.el)
+TEST-FILES := $(shell ls test/*-test.el)
-.PHONY: clean checkdoc lint package install compile test
+.PHONY: clean checkdoc lint package install compile download-sonarlint test
-ci: clean package install compile test
+ci: clean package install compile download-sonarlint test
package:
@echo "Packaging..."
@@ -21,6 +21,10 @@ compile:
@echo "Compiling..."
$(EASK) compile
+download-sonarlint:
+ @echo "Downloading SonarLint..."
+ $(EASK) eval '(progn (require (quote lsp-sonarlint)) (lsp-sonarlint-download))'
+
test:
@echo "Testing..."
$(EASK) test ert $(TEST-FILES)
diff --git a/README.md b/README.md
index b7c5bc3..dc8ae19 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,54 @@ Click [here](https://github.com/SonarSource/sonarlint-vscode/blob/master/telemet
* [treemacs](https://github.com/Alexander-Miller/treemacs) : Project viewer.
* [lsp-treemacs](https://github.com/emacs-lsp/lsp-treemacs) : `lsp-mode` GUI controls implemented using treemacs.
+## Development
+
+### Prerequisites
+
+You will need `make` and [`eask`](https://emacs-eask.github.io/) to run `lsp-sonarlint` tests.
+See also (Requirements)(#requirements) section.
+
+If you do not have `eask` installed, you can install it locally with:
+
+``` shell
+npm install @emacs-eask/cli
+export EASK="$PWD/node_modules/@emacs-eask/cli/eask"
+```
+
+Or globally with:
+
+``` shell
+npm install -g @emacs-eask/cli
+```
+
+### Testing
+
+We use [Emacs ERT](https://www.gnu.org/software/emacs/manual/html_node/ert/) for testing.
+You can run tests with:
+
+``` shell
+make package
+make install
+make compile
+make download-sonarlint
+make test
+```
+
+#### Interactive Testing
+
+You can also run the tests one-by-one interactively.
+
+Open a test file from tests/*.el in Emacs.
+Evaluate the file contents (`eval-buffer`) to load test definitions into the session.
+Run `ert` command to run all or selected tests.
+To run the integration tests, you will have to shut down all your lsp workspaces
+to ensure consistent starting state.
+
+Check out `*lsp-log*`, `*lsp-log: sonarlint:NNNNNNNN*`, `*sonarlint*`, `*sonarlint:stderr*`
+for logs when troubleshooting a test.
+
+You can start with test/trivial-test.el to check that your testing harness works.
+
## Contributions
Contributions are very much welcome.
diff --git a/fixtures/compdb/dir1/1.cpp b/fixtures/compdb/dir1/1.cpp
new file mode 100644
index 0000000..6d1a0d4
--- /dev/null
+++ b/fixtures/compdb/dir1/1.cpp
@@ -0,0 +1 @@
+int x;
diff --git a/fixtures/compdb/dir1/compile_commands.json b/fixtures/compdb/dir1/compile_commands.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/fixtures/compdb/dir1/compile_commands.json
@@ -0,0 +1 @@
+{}
diff --git a/fixtures/compdb/dir1/dir11/11.cpp b/fixtures/compdb/dir1/dir11/11.cpp
new file mode 100644
index 0000000..6d1a0d4
--- /dev/null
+++ b/fixtures/compdb/dir1/dir11/11.cpp
@@ -0,0 +1 @@
+int x;
diff --git a/fixtures/compdb/dir1/dir11/compile_commands.json b/fixtures/compdb/dir1/dir11/compile_commands.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/fixtures/compdb/dir1/dir11/compile_commands.json
@@ -0,0 +1 @@
+{}
diff --git a/fixtures/compdb/dir1/dir12/12.cpp b/fixtures/compdb/dir1/dir12/12.cpp
new file mode 100644
index 0000000..6d1a0d4
--- /dev/null
+++ b/fixtures/compdb/dir1/dir12/12.cpp
@@ -0,0 +1 @@
+int x;
diff --git a/fixtures/compdb/dir1/dir12/compile_commands.yml b/fixtures/compdb/dir1/dir12/compile_commands.yml
new file mode 100644
index 0000000..e69de29
diff --git a/fixtures/compdb/dir1/dir12/dir121/121.cpp b/fixtures/compdb/dir1/dir12/dir121/121.cpp
new file mode 100644
index 0000000..6d1a0d4
--- /dev/null
+++ b/fixtures/compdb/dir1/dir12/dir121/121.cpp
@@ -0,0 +1 @@
+int x;
diff --git a/fixtures/compdb/no-compdb/no-compdb.cpp b/fixtures/compdb/no-compdb/no-compdb.cpp
new file mode 100644
index 0000000..6d1a0d4
--- /dev/null
+++ b/fixtures/compdb/no-compdb/no-compdb.cpp
@@ -0,0 +1 @@
+int x;
diff --git a/fixtures/compile_commands.json b/fixtures/cpp/compile_commands.json
similarity index 100%
rename from fixtures/compile_commands.json
rename to fixtures/cpp/compile_commands.json
diff --git a/fixtures/sample.cpp b/fixtures/cpp/sample.cpp
similarity index 100%
rename from fixtures/sample.cpp
rename to fixtures/cpp/sample.cpp
diff --git a/lsp-sonarlint.el b/lsp-sonarlint.el
index 5a0659e..ca5ebaf 100644
--- a/lsp-sonarlint.el
+++ b/lsp-sonarlint.el
@@ -87,42 +87,77 @@ If absent, it will be downloaded from github and unzipped there."
:group 'lsp-sonarlint
:type 'file)
-(defcustom lsp-sonarlint-disable-telemetry t
+(defcustom-lsp lsp-sonarlint-disable-telemetry t
"Disable sending anonymous usage statistics to SonarSource.
To see a sample of the data that are collected
https://github.com/SonarSource/sonarlint-vscode/blob/master/telemetry-sample.md."
:group 'lsp-sonarlint
- :type 'boolean)
+ :type 'boolean
+ :lsp-path "sonarlint.disableTelemetry")
-(defcustom lsp-sonarlint-test-file-pattern "{**/test/**,**/*test*,**/*Test*}"
+(defcustom-lsp lsp-sonarlint-test-file-pattern "{**/test/**,**/*test*,**/*Test*}"
"Files whose name match java global are considered as test files by analyzers.
Most rules are not evaluated on test files.
Example: `{**/test/**,**/*test*,**/*Test*}`"
:group 'lsp-sonarlint
- :type 'string)
+ :type 'string
+ :lsp-path "sonarlint.testFilePattern")
-(defcustom lsp-sonarlint-show-analyzer-logs nil
+(defcustom-lsp lsp-sonarlint-show-analyzer-logs nil
"Show analyzer's logs in the SonarLint output."
:group 'lsp-sonarlint
- :type 'boolean)
+ :type 'boolean
+ :lsp-path "sonarlint.output.showAnalyzerLogs")
-(defcustom lsp-sonarlint-verbose-logs nil
+(defcustom-lsp lsp-sonarlint-verbose-logs nil
"Enable verbose logging of the SonarLint language server."
:group 'lsp-sonarlint
- :type 'boolean)
+ :type 'boolean
+ :lsp-path "sonarlint.output.verboseLogs")
-(defcustom lsp-sonarlint-vmargs ""
+(defcustom-lsp lsp-sonarlint-vmargs ""
"Extra JVM arguments used to launch the SonarLint LSP.
e.g. `-Xmx1024m`."
:group 'lsp-sonarlint
- :type 'string)
-
-(defcustom lsp-sonarlint-cfamily-compile-commands-path
- "${workspaceFolder}/compile_commands.json"
- "Location of the compile_commands.json file.
-It is needed in C/C++ to provide your compilation options."
+ :type 'string
+ :lsp-path "sonarlint.ls.vmargs")
+
+ (defun lsp-sonarlint--find-file-in-parent-folders (fname)
+ "Find the closest FNAME in a buffer folder or one of its parents.
+ Traverse the parent folders from narrow to wide (/a/b/c, /a/b, /a, /).
+ If none of them contains FNAME, return nil."
+ (let ((dir (file-name-directory (expand-file-name (buffer-file-name))))
+ (found-file nil))
+ (while (not (or found-file (string-empty-p dir) (string-equal dir "/")))
+ (let ((potential-file (concat dir fname)))
+ (if (file-exists-p potential-file)
+ (setq found-file potential-file)
+ (setq dir (file-name-directory (directory-file-name dir))))))
+ found-file))
+
+(defcustom-lsp lsp-sonarlint-path-to-compile-commands ""
+ "Path to the compilation DB - the compile_commands.json file."
+ :type 'path
:group 'lsp-sonarlint
- :type 'file)
+ :lsp-path "sonarlint.pathToCompileCommands")
+
+ (defun lsp-sonarlint--get-compile-commands ()
+ "Find compile_commands.json in the parent dir or ask user."
+ (or (lsp-sonarlint--find-file-in-parent-folders "compile_commands.json")
+ (read-file-name "Provide path to compile_commands.json for this project: ")))
+
+ (defun lsp-sonarlint--set-compile-commands (_workspace _params)
+ "Find compile_commands.json and set it for the workspace.
+ As a side effect it will also send the found path to the SonarLint server."
+ (let ((fname (lsp-sonarlint--get-compile-commands)))
+ (unless (string-empty-p fname)
+ (message "Using compilation database from %s." fname)
+ (custom-set-variables `(lsp-sonarlint-path-to-compile-commands ,(expand-file-name fname))))))
+
+(defun lsp-sonarlint-set-compile-commands (fname)
+ "Set FNAME as the path to the compile_commands.json file for current session."
+ (interactive "fCompilation DB path: ")
+ (custom-set-variables `(lsp-sonarlint-path-to-compile-commands ,(expand-file-name fname))))
(defconst lsp-sonarlint-go-doc-url "https://www.sonarsource.com/go/"
"Documentation sonarsource URL.")
@@ -321,14 +356,6 @@ See `lsp-sonarlint-analyze-folder' to see which files are ignored."
(defvar lsp-sonarlint--action-handlers '())
-(lsp-register-custom-settings
- '(("sonarlint.disableTelemetry" lsp-sonarlint-disable-telemetry)
- ("sonarlint.testFilePattern" lsp-sonarlint-test-file-pattern)
- ("sonarlint.pathToCompileCommands" lsp-sonarlint-cfamily-compile-commands-path)
- ("sonarlint.output.showAnalyzerLogs" lsp-sonarlint-show-analyzer-logs)
- ("sonarlint.output.verboseLogs" lsp-sonarlint-verbose-logs)
- ("sonarlint.ls.vmargs" lsp-sonarlint-vmargs)))
-
(defvar lsp-sonarlint--request-handlers
(lsp-ht
;; Check whether the file should be analyzed or not
@@ -380,8 +407,7 @@ See REQUEST-HANDLERS in lsp--client in lsp-mode."
("sonarlint/readyForTests" #'ignore)
;; Sent by cfamily for analysis of C/C++ files. Sonar requires your
;; build commands specified in a compile_commands.json
- ("sonarlint/needCompilationDatabase" (lambda(&rest _)
- (warn "Sonar could not find your compile_commands.json, please check `lsp-sonarlint-cfamily-compile-commands-path'")))
+ ("sonarlint/needCompilationDatabase" #'lsp-sonarlint--set-compile-commands)
;; This is probably just to raise awareness of the new kind of issues:
;; secrets. That'd be too booring to implement. Hopefully, the user is
;; paying attention and will notice anyway.
@@ -398,18 +424,19 @@ See NOTIFICATION-HANDLERS in lsp--client in lsp-mode.")
:priority -1
:request-handlers lsp-sonarlint--request-handlers
:notification-handlers lsp-sonarlint--notification-handlers
- :multi-root t
+ :multi-root nil
:add-on? t
:server-id 'sonarlint
:action-handlers (ht<-alist lsp-sonarlint--action-handlers)
:initialization-options (lambda ()
- (list
- :productKey "emacs"
- :productName "Emacs"))
+ (list
+ :productKey "emacs"
+ :productName "Emacs"))
:initialized-fn (lambda (workspace)
(with-lsp-workspace workspace
(lsp--set-configuration
- (lsp-configuration-section "sonarlint"))))))
+ (lsp-configuration-section "sonarlint"))))
+ :synchronize-sections '("sonarlint")))
(provide 'lsp-sonarlint)
;;; lsp-sonarlint.el ends here
diff --git a/test/lsp-sonarlint-cpp-test.el b/test/lsp-sonarlint-cpp-test.el
new file mode 100644
index 0000000..f87533a
--- /dev/null
+++ b/test/lsp-sonarlint-cpp-test.el
@@ -0,0 +1,53 @@
+;;; lsp-sonarlint-cpp-test.el --- CFamily analyzer-specific tests for Sonarlint LSP client -*- lexical-binding: t; -*-
+;;;
+;; Author: Arseniy Zaostrovnykh
+;; Created: 11 July 2023
+;; License: GPL-3.0-or-later
+;;
+;; This program 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.
+
+;; This program 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 this program. If not, see .
+
+;;; Commentary:
+;; Tests for the C++-specific integration of the LSP mode and SonarLint language server.
+
+;;; Code:
+
+(require 'lsp-mode)
+(require 'lsp-sonarlint)
+(load-file (expand-file-name "lsp-sonarlint-test-utils.el"
+ (file-name-directory (or load-file-name (buffer-file-name)))))
+
+(defun lsp-sonarlint--get-compdb-for-sample-file (cpp-file)
+ "Relative to fixtures folder, get compile_commands.json for CPP-FILE."
+ (let ((buf (find-file-noselect (lsp-sonarlint-sample-file cpp-file))))
+ (with-current-buffer buf
+ (file-relative-name (lsp-sonarlint--get-compile-commands)
+ (lsp-sonarlint-fixtures-dir)))))
+
+(ert-deftest lsp-sonarlint--get-compile-commands-test ()
+ (should (equal (lsp-sonarlint--get-compdb-for-sample-file "compdb/dir1/1.cpp")
+ "compdb/dir1/compile_commands.json"))
+ (should (equal (lsp-sonarlint--get-compdb-for-sample-file "compdb/dir1/dir11/11.cpp")
+ "compdb/dir1/dir11/compile_commands.json"))
+ (should (equal (lsp-sonarlint--get-compdb-for-sample-file "compdb/dir1/dir12/12.cpp")
+ "compdb/dir1/compile_commands.json"))
+ (should (equal (lsp-sonarlint--get-compdb-for-sample-file "compdb/dir1/dir12/dir121/121.cpp")
+ "compdb/dir1/compile_commands.json")))
+
+(ert-deftest lsp-sonarlint--get-compile-commands-interactive-test ()
+ (cl-letf (((symbol-function 'read-file-name) (lambda (_prompt)
+ (concat (lsp-sonarlint-fixtures-dir) "my_db.json"))))
+ (should (equal (lsp-sonarlint--get-compdb-for-sample-file "compdb/no-compdb/no-compdb.cpp")
+ "my_db.json"))))
+
+;;; lsp-sonarlint-cpp-test.el ends here
diff --git a/test/lsp-sonarlint-integration-test.el b/test/lsp-sonarlint-integration-test.el
index 722350a..d7472dc 100644
--- a/test/lsp-sonarlint-integration-test.el
+++ b/test/lsp-sonarlint-integration-test.el
@@ -17,6 +17,7 @@
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
+;; Package-Requires: ((emacs "27.2"))
;;; Commentary:
;; Tests for the integration of the LSP mode and SonarLint language server
@@ -26,6 +27,17 @@
(require 'lsp-mode)
(require 'lsp-sonarlint)
+(load-file (expand-file-name "lsp-sonarlint-test-utils.el"
+ (file-name-directory (or load-file-name (buffer-file-name)))))
+
+(ert-deftest lsp-sonarlint-plugin-downloaded ()
+ "Check whether you have downloaded SonarLint.
+
+This is a prerequisite for all the integration tests. If this
+test fails, you need to download the SonarLint plugin using
+
+make download-sonarlint"
+ (should (file-exists-p (concat lsp-sonarlint-download-dir "/extension/server/sonarlint-ls.jar"))))
(defun lsp-sonarlint--wait-for (predicate hook timeout)
"Register PREDICATE to run on HOOK, and wait until it returns t.
@@ -71,7 +83,6 @@ only works for specific textDocument/didOpen:languageId."
(lsp-enable-snippet nil)
received-warnings)
(let ((buf (find-file-noselect file))
- (lsp-sonarlint-plugin-autodownload t)
(diagnostics-updated nil)
(register-warning (lambda (&rest w) (when (equal (car w) 'lsp-mode)
(push (cadr w) received-warnings)))))
@@ -101,17 +112,6 @@ only works for specific textDocument/didOpen:languageId."
(sort (mapcar (lambda (issue) (gethash "code" issue)) issues) #'string-lessp))
-(defun lsp-sonarlint--fixtures-dir ()
- "Directory of the test fixtures for these tests."
- (concat
- (file-name-directory
- (directory-file-name (file-name-directory (symbol-file #'lsp-sonarlint--fixtures-dir))))
- "fixtures/"))
-
-(defun lsp-sonarlint--sample-file (fname)
- "Get the full path of the sample file FNAME."
- (concat (lsp-sonarlint--fixtures-dir) fname))
-
(defun lsp-sonarlint--get-all-issue-codes (sample-filename &optional major-mode)
"Get all SonarLint issue-codes for given SAMPLE-FILENAME.
This functions takes some time to wait for the LSP mode to init
@@ -120,7 +120,7 @@ MAJOR-MODE specifies the major mode enabled to trigger the analysis.
Some analyzers like cfamily require specific major-modes.
If nil, use python-mode by default."
(lsp-sonarlint--exec-with-diags
- (lsp-sonarlint--sample-file sample-filename)
+ (lsp-sonarlint-sample-file sample-filename)
(lambda (diags)
(lsp-sonarlint--get-codes-of-issues diags))
(if major-mode major-mode 'python-mode)))
@@ -176,11 +176,11 @@ If nil, use python-mode by default."
(ert-deftest lsp-sonarlint-c++-reports-issues ()
"Check that LSP can get go SonarLint issues for a C++ file."
- (should (equal (lsp-sonarlint--get-all-issue-codes "sample.cpp" 'c++-mode)
+ (should (equal (lsp-sonarlint--get-all-issue-codes "cpp/sample.cpp" 'c++-mode)
'("cpp:S995"))))
(defun lsp-sonarlint--find-descr-action-at-point ()
- "Find the 'get rule description' code action for the issue at point."
+ "Find the `get rule description' code action for the issue at point."
(seq-find (lambda (action) (string-match-p "description" (gethash "title" action)))
(lsp-code-actions-at-point)))
@@ -208,24 +208,27 @@ If nil, use python-mode by default."
(ert-deftest lsp-sonarlint-display-rule-descr-test ()
"Check whether you can display rule description for a SonarLint issue."
(lsp-sonarlint--exec-with-diags
- (lsp-sonarlint--sample-file "sample.py")
+ (lsp-sonarlint-sample-file "sample.py")
(lambda (diags)
(lsp-sonarlint--go-to-first-diag diags)
(let ((descr-action (lsp-sonarlint--find-descr-action-at-point)))
(let ((description-opened nil))
- (cl-flet ((check-opened-buffer
- (buf)
- (when (lsp-sonarlint--buf-has-rule-descr-p buf)
- (setq description-opened t))))
+ (cl-flet ((check-opened-buffer (buf)
+ (when (lsp-sonarlint--buf-has-rule-descr-p buf)
+ (setq description-opened t))))
(unwind-protect
(progn
(advice-add 'shr-render-buffer :before #'check-opened-buffer)
- (sit-for 1)
- (lsp-execute-code-action descr-action)
(with-timeout (8 (error "Timeout waiting for rule description"))
(while (not description-opened)
+ ;; Repeat the request multiple times because SonarLint
+ ;; might get distracted with other requests and "forget" to
+ ;; respond
+ (lsp-execute-code-action descr-action)
(message "still waiting")
- (sit-for 0.1)))
+ (sit-for 0.3)))
(should description-opened))
(advice-remove 'shr-render-buffer #'check-opened-buffer))))))
'python-mode))
+
+;;; integration.el ends here
diff --git a/test/lsp-sonarlint-test-utils.el b/test/lsp-sonarlint-test-utils.el
new file mode 100644
index 0000000..da62eaf
--- /dev/null
+++ b/test/lsp-sonarlint-test-utils.el
@@ -0,0 +1,38 @@
+;;; integration.el --- Integration tests for Sonarlint LSP client -*- lexical-binding: t; -*-
+;;;
+;; Author: Arseniy Zaostrovnykh
+;; Created: 11 July 2023
+;; License: GPL-3.0-or-later
+;;
+;; This program 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.
+
+;; This program 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 this program. If not, see .
+
+;;; Commentary:
+;; Utility functions for the LSP SonarLint tests.
+
+;;; Code:
+
+(defun lsp-sonarlint-fixtures-dir ()
+ "Directory of the test fixtures for these tests."
+ (concat
+ (file-name-directory
+ (directory-file-name (file-name-directory (symbol-file #'lsp-sonarlint-fixtures-dir))))
+ "fixtures/"))
+
+(defun lsp-sonarlint-sample-file (fname)
+ "Get the full path of the sample file FNAME."
+ (concat (lsp-sonarlint-fixtures-dir) fname))
+
+(provide 'lsp-sonarlint-test-utils)
+
+;;; lsp-sonarlint-test-utils.el ends here