-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathlsp-sonarlint.el
418 lines (356 loc) · 18.1 KB
/
lsp-sonarlint.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
;;; lsp-sonarlint.el --- Emacs SonarLint lsp client -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Fermin Munoz
;; Author: Fermin MF <[email protected]>
;; Created: 13 Jun 2020
;; Version: 0.0.1
;; Keywords: languages, tools, php, javascript, typescript, go, xml, html, java, python
;; URL: https://github.com/emacs-lsp/lsp-sonarlint
;; Package-Requires: ((emacs "27.1") (dash "2.12.0") (lsp-mode "6.3") (ht "2.3"))
;; 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:
;; SonarLint LSP extensions for GNU Emacs, add support for the majority of sonarlint languages
;; including: php, javascript, typescript, html, python and java.
;; This is NOT an official SonarLint extension.
;;; Code:
(require 'lsp-mode)
(require 'dash)
(require 'cus-edit)
(require 'ht)
(require 'shr)
(defgroup lsp-sonarlint nil
"SonarLint lsp server group"
:group 'lsp-mode
:link '(url-link "https://github.com/emacs-lsp/lsp-sonarlint")
:package-version '(lsp-mode . "6.4"))
(defcustom lsp-sonarlint-download-url
(format "https://github.com/SonarSource/sonarlint-vscode/releases/download/4.6.0%%2B76435/sonarlint-vscode-%s-4.6.0.vsix"
(pcase system-type
('gnu/linux "linux-x64")
('darwin
(pcase (lsp-resolve-value lsp--system-arch)
('x64 "darwin-x64")
('arm64 "darwin-arm64")))
('windows-nt "win32-x64")))
"Official SonarLint VSCode extension (vsix).
It contains the necessary language server and analyzers."
:type 'string)
(defcustom lsp-sonarlint-download-dir
(concat (file-name-directory load-file-name) "sonarlint-vscode")
"Location where VSCode's sonarlint extension should be found.
If absent, it will be downloaded from github and unzipped there."
:group 'lsp-sonarlint
:type 'directory)
(defcustom lsp-sonarlint-modes-enabled '(php-mode
go-mode
html-mode
web-mode
js-mode
js2-mode
rjsx-mode
typescript-mode
typescript-tsx-mode
python-mode
c-mode
c++-mode
c-or-c++-mode
c-ts-mode
c++-ts-mode
c-or-c++-ts-mode
java-mode
xml-mode
nxml-mode)
"List of major modes that enable SonarLint backend for LSP mode."
:group 'lsp-sonarlint
:type 'file)
(defcustom 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)
(defcustom 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)
(defcustom lsp-sonarlint-show-analyzer-logs nil
"Show analyzer's logs in the SonarLint output."
:group 'lsp-sonarlint
:type 'boolean)
(defcustom lsp-sonarlint-verbose-logs nil
"Enable verbose logging of the SonarLint language server."
:group 'lsp-sonarlint
:type 'boolean)
(defcustom 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."
:group 'lsp-sonarlint
:type 'file)
(defconst lsp-sonarlint-go-doc-url "https://www.sonarsource.com/go/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-go-repository-url "https://github.com/SonarSource/slang/"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-html-doc-url "https://www.sonarsource.com/html/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-html-repository-url "https://github.com/SonarSource/sonar-html"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-java-doc-url "https://www.sonarsource.com/java/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-java-repository-url "https://github.com/SonarSource/sonar-java"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-c-doc-url "https://www.sonarsource.com/c"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-c++-doc-url "https://www.sonarsource.com/cpp"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-javascript-doc-url "https://www.sonarsource.com/js/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-javascript-repository-url "https://github.com/SonarSource/SonarJS"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-php-doc-url "https://www.sonarsource.com/php/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-php-repository-url "https://github.com/SonarSource/sonar-php"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-python-doc-url "https://www.sonarsource.com/python/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-python-repository-url "https://github.com/SonarSource/sonar-python"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-text-doc-url "https://www.sonarsource.com/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-text-repository-url "https://github.com/SonarSource/sonar-text"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-typescript-doc-url "https://www.sonarsource.com/ts/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-typescript-repository-url "https://github.com/SonarSource/SonarJS"
"Official sonarlint code extension repository.")
(defconst lsp-sonarlint-xml-doc-url "https://www.sonarsource.com/xml/"
"Documentation sonarsource URL.")
(defconst lsp-sonarlint-xml-repository-url "https://github.com/SonarSource/sonar-xml"
"Official sonarlint code extension repository.")
(defun lsp-sonarlint-download()
"Download the VSCode extension and unzips it.
Follows the customizable variables : `lsp-sonarlint-download-url' and
`lsp-sonarlint-download-dir'"
(interactive)
(let* ((dest-dir (file-name-directory lsp-sonarlint-download-dir))
(vsix-path (concat dest-dir "sonarlint.zip")))
(unless (file-exists-p lsp-sonarlint-download-dir)
(unless (file-exists-p vsix-path)
(url-copy-file lsp-sonarlint-download-url vsix-path))
(lsp-unzip vsix-path lsp-sonarlint-download-dir)
(if (file-exists-p lsp-sonarlint-download-dir)
(progn
(delete-file vsix-path)
(message "Sonarlint : successfully downloaded to %s" lsp-sonarlint-download-dir))
;; For unzip issues on windows, see https://github.com/emacs-lsp/lsp-mode/issues/3022
(warn "Could not unzip the VSCode extension, Either :\n\
- Set `lsp-unzip-script' properly : on windows set it to `lsp-ext-pwsh-script'\n\
- Download manually `lsp-sonarlint-download-url', unzip it, move it to `lsp-sonarlint-download-dir'")))))
(defcustom lsp-sonarlint-auto-download
nil
"If non nil, the analyzers will be downloaded at startup if needed.
See also `lsp-sonarlint-download-dir' and `lsp-sonarlint-download-url'."
:group 'lsp-sonarlint
:set (lambda (sym auto-download) ; Trigger download when set to non-nil
(set sym auto-download)
(when auto-download
(lsp-sonarlint-download)))
:group 'lsp-sonarlint
:type 'boolean)
(defcustom lsp-sonarlint-use-system-jre
nil
"Use the system java runtime instead of the one bundled in VSCode's extension."
:group 'lsp-sonarlint
:type 'boolean)
(defcustom lsp-sonarlint-enabled-analyzers
'all
"Specify which analyzers you want to enable.
- Use the \\='all symbol to enable all analyzers.
- Use a list of analyzer names if you only need a subset. In this case,
use `lsp-sonarlint-available-analyzers' to list available analyzers."
:group 'lsp-sonarlint
:type '(choice (const :tag "All analyzers" all)
(const :tag "No analyzer" nil)
(repeat :tag "A subset (see `lsp-sonarlint-available-analyzers')" string)))
(defun lsp-sonarlint--extract-analyzer-name(analyzer-path)
"Extracts the analyzer name from ANALYZER-PATH"
(replace-regexp-in-string "sonar\\(lint\\)?" "" (file-name-base analyzer-path)))
(defun lsp-sonarlint-available-analyzers()
"Return a list of the available analyzers."
(interactive)
(let* ((root-dir lsp-sonarlint-download-dir)
(plugin-dir (concat root-dir "/extension/analyzers"))
(plugin-paths (directory-files plugin-dir t ".jar"))
(plugin-names (mapcar #'lsp-sonarlint--extract-analyzer-name plugin-paths)))
(message "Analyzers found in %s : %s" plugin-dir plugin-names)
plugin-names))
(defun lsp-sonarlint--check-enabled-analyzer(analyzer)
"Return t if ANALYZER is enabled.
See `lsp-sonarlint-available-analyzers' and `lsp-sonarlint-enabled-analyzers'"
(let* ((analyzer-name (lsp-sonarlint--extract-analyzer-name analyzer)))
(pcase lsp-sonarlint-enabled-analyzers
('all t)
(_ (seq-contains-p lsp-sonarlint-enabled-analyzers analyzer-name)))))
(defun lsp-sonarlint-server-start-fun()
"Start lsp-sonarlint in stdio mode."
;; This will signal an error if `lsp-sonarlint-download-dir' is not a dir
;; or if the java executable is not found there.
;; Thus, it also serves as a check for the presence of SonarLint LSP server.
(let* ((root-dir lsp-sonarlint-download-dir)
(bundled-java-path (car (directory-files-recursively root-dir "java\\(.exe\\)?$")))
(java-path (if lsp-sonarlint-use-system-jre "java" bundled-java-path))
(jar-path (concat root-dir "/extension/server/sonarlint-ls.jar"))
(analyzer-dir (concat root-dir "/extension/analyzers"))
(analyzer-paths (directory-files analyzer-dir t ".jar"))
(enabled-analyzers (seq-filter #'lsp-sonarlint--check-enabled-analyzer analyzer-paths)))
(unless java-path
(warn "lsp-sonarlint : java is required and was not found on your system" :error))
(-concat (list java-path "-jar" jar-path "-stdio" "-analyzers") enabled-analyzers)))
(defun lsp-sonarlint--code-action-open-rule (_workspace params)
"Create a buffer with rendered rule from PARAMS text in it.
Extracts the title ahd htmlDescription, and renders the HTML in a
temporary buffer."
(with-temp-buffer
(let* ((rule-title (format "<h1>%s</h1>" (ht-get params "name")))
(rule-summary (format "%s<hr/>" (ht-get params "htmlDescription")))
(rule-body-array (ht-get params "htmlDescriptionTabs")))
(insert rule-title)
(insert rule-summary)
(insert "\n")
(seq-doseq (rule-body rule-body-array)
(let ((rule-body-title (format "<h2>%s</h2>" (ht-get rule-body "title")))
(rule-body-content (format "%s<hr/>" (ht-get (ht-get rule-body "ruleDescriptionTabNonContextual") "htmlContent"))))
(insert rule-body-title)
(insert "\n")
(insert rule-body-content))))
(shr-render-buffer (current-buffer))))
(defun lsp-sonarlint--analyze-folder(dirname)
"Return t if DIRNAME should be analyzed.
i.e folders starting with '.' will be ignored"
(not (string= (substring (file-name-base dirname) 0 1) ".")))
(defun lsp-sonarlint--invalid-file-name-encoding(file-path)
"Return t if the file-path is not encodable as UTF-8.
This is important since emacs does not support non UTF-8 json serialization,
so a stray accentuated character encoded in a legacy filename in 8859-1
will raise an error for the whole project."
(cl-some (lambda (ch)
(not (encode-coding-char ch 'utf-8 'unicode)))
file-path))
(defun lsp-sonarlint--list-files-in-folder (workspace _params)
"Respond to a listFilesInFolder request.
List all files of interest in WORKSPACE's directory.
See `lsp-sonarlint-analyze-folder' to see which files are ignored."
(let* ((root-dir (lsp--workspace-root workspace))
(files (directory-files-recursively root-dir ".*" nil 'lsp-sonarlint--analyze-folder))
(utf8-filenames (cl-remove-if #'lsp-sonarlint--invalid-file-name-encoding files)))
(lsp-ht
("foundFiles"
(apply 'vector
(mapcar (lambda(file)
(lsp-ht
("fileName" (file-name-nondirectory file))
("filePath" file)))
utf8-filenames))))))
(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
;; This is typically to check if an open file is in an active workspace or not
;; Let's assume here that the file should be analyzed
("sonarlint/shouldAnalyseFile" (lambda (&rest _)
(lsp-ht
("shouldBeAnalysed" t))))
;; Sonarlint sends those before to ask if you want to display a notification
;; On what your environement is missing. e.g If you have an old version of nodejs.
("sonarlint/canShowMissingRequirementsNotification" (lambda (&rest _) t))
;; For some reason, sonarlint requests the list of files in the folder
;; VSCode's extension seems to send everything, even the .git folder.
;; Here we give anything stored inside of the root directory, except
;; hidden folders (in the unix sense, those which start with '.')
("sonarlint/listFilesInFolder" #'lsp-sonarlint--list-files-in-folder)
;; Sonarlint VSCode extension has a setting to specify a glob pattern
;; of files to be excluded from analysis.
;; We do not support this option at the moment, do not filter anything.
("sonarlint/filterOutExcludedFiles" (lambda (_ params) params))
;; Check if the file is ignored by Source Control Manager (a.k.e. VCS, version control system).
;; I think this is related to .gitignore and similar files.
;; Probably safe to assume as a first step that you don't care, and want your diagnostics.
;; TODO: implement a proper check here.
("sonarlint/isIgnoredByScm" #'ignore)
;; Probably only relevant to the java analyzer.
;; Some additional java configuration for the project.
;; TODO: implement
("sonarlint/getJavaConfig" #'ignore))
"SonarLint-specific request handlers.
See REQUEST-HANDLERS in lsp--client in lsp-mode."
)
(defvar lsp-sonarlint--notification-handlers
(lsp-ht
;; Security Hotspots are a special kind of issue that have particular
;; interface on SonarCloud, SonarQube, and in SonarLint. See
;; https://docs.sonarcloud.io/digging-deeper/security-hotspots/ I presume
;; the PARAMS contain a list of issues of this category, similar to the
;; normal issues.
;; TODO: display them, perhaps optionally, as they could be noisy sometimes,
;; especially without the possibility to "review" them once and forever.
("sonarlint/publishSecurityHotspots" #'ignore)
;; Sonarlint sends this to suggest the connected mode, and sends along
;; your previous sonar projects. Connected mode is not currently implemented here.
("sonarlint/suggestConnection" #'ignore)
;; Not sure what this is for. Testing of SonarLint itself?
("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'")))
;; 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.
("sonarlint/showNotificationForFirstSecretsIssue" #'ignore)
("sonarlint/showRuleDescription" #'lsp-sonarlint--code-action-open-rule))
"SonarLint-specific notification handlers.
See NOTIFICATION-HANDLERS in lsp--client in lsp-mode.")
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection #'lsp-sonarlint-server-start-fun)
:major-modes lsp-sonarlint-modes-enabled
:priority -1
:request-handlers lsp-sonarlint--request-handlers
:notification-handlers lsp-sonarlint--notification-handlers
:multi-root t
:add-on? t
:server-id 'sonarlint
:action-handlers (ht<-alist lsp-sonarlint--action-handlers)
:initialization-options (lambda ()
(list
:productKey "emacs"
:productName "Emacs"))
:initialized-fn (lambda (workspace)
(with-lsp-workspace workspace
(lsp--set-configuration
(lsp-configuration-section "sonarlint"))))))
(provide 'lsp-sonarlint)
;;; lsp-sonarlint.el ends here