Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable automatic and interactive configuration of compile_commands.json path per project #26

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand All @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int x;
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/compile_commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/dir11/11.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int x;
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/dir11/compile_commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/dir12/12.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int x;
Empty file.
1 change: 1 addition & 0 deletions fixtures/compdb/dir1/dir12/dir121/121.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int x;
1 change: 1 addition & 0 deletions fixtures/compdb/no-compdb/no-compdb.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int x;
File renamed without changes.
File renamed without changes.
89 changes: 58 additions & 31 deletions lsp-sonarlint.el
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
53 changes: 53 additions & 0 deletions test/lsp-sonarlint-cpp-test.el
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

;;; 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
Loading
Loading