branch: elpa/flycheck
commit 66ce4095a66be3f872f9677058449837272622c8
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Migrate dockerfile-hadolint to the SARIF parser
    
    hadolint's --format sarif output carries proper severities, ids and
    messages, so the checker no longer needs error patterns that break when
    hadolint changes its human-readable format.  This is the first consumer
    of flycheck-parse-sarif.
    
    Also handle zero-width SARIF regions (start == end) as point locations,
    which hadolint emits for line-level findings; otherwise they would
    produce empty highlights.
---
 CHANGES.rst                             |  8 ++++
 flycheck.el                             | 66 +++++++++++++++++++--------------
 test/specs/languages/test-dockerfile.el |  9 ++---
 test/specs/test-error-parsers.el        | 39 +++++++++++++++++++
 4 files changed, 90 insertions(+), 32 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index dd2cac81eb..004184804f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,14 @@
 =======================
 
 - Drop support for Emacs 27; Flycheck now requires Emacs 28.1 or newer.
+- The ``dockerfile-hadolint`` checker now parses hadolint's SARIF output
+  (``--format sarif``) via ``flycheck-parse-sarif`` instead of matching
+  its text output with error patterns, so it no longer breaks when
+  hadolint tweaks its human-readable format.
+- ``flycheck-parse-sarif`` now treats a zero-width SARIF region (where
+  the start and end positions coincide, as some tools emit for
+  line-level findings) as spanning the whole line, instead of producing
+  an empty highlight.
 - Add ``flycheck-parse-sarif``, a ready-made ``:error-parser`` for the
   SARIF output format that many analyzers can emit.  Like the existing
   ``flycheck-parse-checkstyle``, checker definitions can point
diff --git a/flycheck.el b/flycheck.el
index 4c8505df23..bb18828a4a 100644
--- a/flycheck.el
+++ b/flycheck.el
@@ -8028,18 +8028,38 @@ information about SARIF."
                       (seq-map
                        (lambda (location)
                          (let-alist location
-                           (flycheck-error-new-at
-                            .physicalLocation.region.startLine
-                            .physicalLocation.region.startColumn
-                            level message
-                            :id id
-                            :checker checker
-                            :buffer buffer
-                            :filename
-                            (flycheck-parse-sarif--uri
-                             .physicalLocation.artifactLocation.uri)
-                            :end-line .physicalLocation.region.endLine
-                            :end-column .physicalLocation.region.endColumn)))
+                           (let* ((start-line
+                                   .physicalLocation.region.startLine)
+                                  (start-col
+                                   .physicalLocation.region.startColumn)
+                                  (end-line
+                                   .physicalLocation.region.endLine)
+                                  (end-col
+                                   .physicalLocation.region.endColumn)
+                                  ;; A zero-width region carries no span:
+                                  ;; endColumn equals startColumn on the same
+                                  ;; line (endLine defaults to startLine per
+                                  ;; the SARIF spec).  Some tools emit these
+                                  ;; for line-level findings, so treat them as
+                                  ;; the whole line -- drop the column and end
+                                  ;; and let the highlighting mode take over,
+                                  ;; rather than highlight an empty range.
+                                  (zero-width
+                                   (and end-col
+                                        (equal end-col start-col)
+                                        (or (null end-line)
+                                            (equal end-line start-line)))))
+                             (flycheck-error-new-at
+                              start-line (unless zero-width start-col)
+                              level message
+                              :id id
+                              :checker checker
+                              :buffer buffer
+                              :filename
+                              (flycheck-parse-sarif--uri
+                               .physicalLocation.artifactLocation.uri)
+                              :end-line (unless zero-width end-line)
+                              :end-column (unless zero-width end-col)))))
                        .locations)
                     ;; A result without a location applies to the whole run
                     (list (flycheck-error-new-at
@@ -9275,25 +9295,17 @@ Requires DMD 2.066 or newer.  See URL 
`https://dlang.org/'."
   "A Dockerfile syntax checker using hadolint.
 
 See URL `https://github.com/hadolint/hadolint/'."
-  :command ("hadolint" "--no-color" "-")
+  :command ("hadolint" "--format" "sarif" "-")
   :standard-input t
-  :error-patterns
-  ((error line-start
-          (file-name) ":" line " " (id (one-or-more alnum)) " error: " 
(message)
-          line-end)
-   (warning line-start
-            (file-name) ":" line " " (id (one-or-more alnum))
-            " warning: " (message) line-end)
-   (info line-start
-         (file-name) ":" line " " (id (one-or-more alnum)) " info: " (message)
-         line-end)
-   (error line-start
-          (file-name) ":" line ":" column " " (message)
-          line-end))
+  :error-parser flycheck-parse-sarif
   :error-filter
   (lambda (errors)
+    ;; hadolint reports stdin as "-" for lint findings but as
+    ;; "/dev/stdin" for parse errors; strip both so the errors attach to
+    ;; the current buffer
     (flycheck-sanitize-errors
-     (flycheck-remove-error-file-names "-" errors)))
+     (flycheck-remove-error-file-names
+      "/dev/stdin" (flycheck-remove-error-file-names "-" errors))))
   :modes (dockerfile-mode dockerfile-ts-mode))
 
 (defun flycheck-credo--working-directory (&rest _ignored)
diff --git a/test/specs/languages/test-dockerfile.el 
b/test/specs/languages/test-dockerfile.el
index 8ae2e176a1..211a3fb05a 100644
--- a/test/specs/languages/test-dockerfile.el
+++ b/test/specs/languages/test-dockerfile.el
@@ -7,17 +7,16 @@
   (flycheck-buttercup-def-checker-test dockerfile-hadolint dockerfile error
     (flycheck-buttercup-should-syntax-check
      "language/dockerfile/Dockerfile.error" 'dockerfile-mode
-     '(2 1 error "unexpected 'I' expecting '#', '\\', ADD, ARG, CMD, COPY, 
ENTRYPOINT, ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, MAINTAINER, ONBUILD, RUN, 
SHELL, STOPSIGNAL, USER, VOLUME, WORKDIR, at least one space, or end of input"
-         :checker dockerfile-hadolint)))
+     '(2 nil error "unexpected 'I'
+expecting '#', '\\', ADD, ARG, CMD, COPY, ENTRYPOINT, ENV, EXPOSE, FROM, 
HEALTHCHECK, LABEL, MAINTAINER, ONBUILD, RUN, SHELL, STOPSIGNAL, USER, VOLUME, 
WORKDIR, a pragma, at least one space, or end of input"
+         :id "DL1000" :checker dockerfile-hadolint)))
 
   (flycheck-buttercup-def-checker-test dockerfile-hadolint dockerfile warnings
     (flycheck-buttercup-should-syntax-check
      "language/dockerfile/Dockerfile.warning" 'dockerfile-mode
      '(1 nil warning "Always tag the version of an image explicitly"
          :id "DL3006" :checker dockerfile-hadolint)
-     '(2 nil error "Do not use apt-get upgrade or dist-upgrade"
-         :id "DL3005" :checker dockerfile-hadolint)
-     '(2 nil info "Delete the apt-get lists after installing something"
+     '(2 nil info "Delete the apt lists (/var/lib/apt/lists) after installing 
something"
          :id "DL3009" :checker dockerfile-hadolint)
      '(3 nil error "Use absolute WORKDIR"
          :id "DL3000" :checker dockerfile-hadolint))))
diff --git a/test/specs/test-error-parsers.el b/test/specs/test-error-parsers.el
index 37b036c3f9..347a9ebb72 100644
--- a/test/specs/test-error-parsers.el
+++ b/test/specs/test-error-parsers.el
@@ -111,6 +111,45 @@
                  (car (flycheck-parse-sarif sarif 'checker 'buffer)))
                 :to-equal "/abs/path.py")))
 
+    (it "treats a zero-width region as the whole line"
+      ;; A start == end region carries no span; drop the column and end so
+      ;; the whole line is highlighted rather than an empty range.  Also
+      ;; covers endLine omitted, which defaults to startLine.
+      (dolist (region 
'("\"startLine\":2,\"startColumn\":1,\"endLine\":2,\"endColumn\":1"
+                        "\"startLine\":2,\"startColumn\":4,\"endColumn\":4"))
+        (let* ((sarif (concat 
"{\"runs\":[{\"tool\":{\"driver\":{\"name\":\"d\"}},\
+\"results\":[{\"message\":{\"text\":\"m\"},\"locations\":[{\"physicalLocation\":\
+{\"artifactLocation\":{\"uri\":\"a.txt\"},\"region\":{" region "}}}]}]}]}"))
+               (err (car (flycheck-parse-sarif sarif 'checker 'buffer))))
+          (expect (flycheck-error-line err) :to-equal 2)
+          (expect (flycheck-error-column err) :to-be nil)
+          (expect (flycheck-error-end-line err) :to-be nil)
+          (expect (flycheck-error-end-column err) :to-be nil))))
+
+    (it "keeps a non-empty region's end position"
+      (let ((sarif "{\"runs\":[{\"tool\":{\"driver\":{\"name\":\"d\"}},\
+\"results\":[{\"message\":{\"text\":\"m\"},\"locations\":[{\"physicalLocation\":\
+{\"artifactLocation\":{\"uri\":\"a.txt\"},\"region\":\
+{\"startLine\":2,\"startColumn\":3,\"endLine\":2,\"endColumn\":7}}}]}]}]}"))
+        (let ((err (car (flycheck-parse-sarif sarif 'checker 'buffer))))
+          (expect (flycheck-error-column err) :to-equal 3)
+          (expect (flycheck-error-end-line err) :to-equal 2)
+          (expect (flycheck-error-end-column err) :to-equal 7))))
+
+    (it "keeps multiple results at the same location distinct"
+      (let ((sarif "{\"runs\":[{\"tool\":{\"driver\":{\"name\":\"d\"}},\
+\"results\":[\
+{\"ruleId\":\"A\",\"message\":{\"text\":\"first\"},\"locations\":[\
+{\"physicalLocation\":{\"artifactLocation\":{\"uri\":\"a.txt\"},\
+\"region\":{\"startLine\":3}}}]},\
+{\"ruleId\":\"B\",\"message\":{\"text\":\"second\"},\"locations\":[\
+{\"physicalLocation\":{\"artifactLocation\":{\"uri\":\"a.txt\"},\
+\"region\":{\"startLine\":3}}}]}]}]}"))
+        (expect (mapcar (lambda (e) (list (flycheck-error-id e)
+                                          (flycheck-error-message e)))
+                        (flycheck-parse-sarif sarif 'checker 'buffer))
+                :to-equal '(("A" "first") ("B" "second")))))
+
     (it "utf-8-decodes percent escapes in file URIs"
       (let ((sarif "{\"runs\":[{\"tool\":{\"driver\":{\"name\":\"d\"}},\
 
\"results\":[{\"message\":{\"text\":\"m\"},\"locations\":[{\"physicalLocation\":\

Reply via email to