branch: externals/relint
commit 949595727098eb31fb0b3cb4ccbbc6489cb39e71
Author: Mattias EngdegÄrd <matti...@acm.org>
Commit: Mattias EngdegÄrd <matti...@acm.org>

    Grouped diagnostics, handle 'info' messages
---
 relint-test.el   |   3 ++
 relint.el        | 119 ++++++++++++++++++++++++++++------------------
 test/1.expected  |  63 +++++++++++++++++++++++++
 test/10.expected |  30 ++++++++++++
 test/11.expected |   6 +++
 test/13.expected |   3 ++
 test/2.expected  | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/3.expected  |  30 ++++++++++++
 test/4.expected  |  15 ++++++
 test/5.expected  |  21 +++++++++
 test/7.expected  |   9 ++++
 test/9.expected  |  51 ++++++++++++++++++++
 12 files changed, 446 insertions(+), 45 deletions(-)

diff --git a/relint-test.el b/relint-test.el
index 86d1ba9148..d3470dc9c6 100644
--- a/relint-test.el
+++ b/relint-test.el
@@ -140,10 +140,13 @@ and a path."
             (relint-buffer buf)
             '(["In call to looking-at: Repetition of repetition"
                20 28 28 "broken**regexp" 7 7 warning]
+              ["This is the inner expression"
+               20 26 27 "broken**regexp" 5 6 info]
               ["In call to looking-at: Unescaped literal `^'"
                50 nil nil "^^" 1 1 warning]
               ["In call to looking-at: Duplicated `g' inside character 
alternative"
                82 105 105 "abcdef[gg]" 8 8 warning]
+              ["Previous occurrence here" 82 104 104 "abcdef[gg]" 7 7 info]
               ["In call to string-match: Unterminated character alternative"
                125 126 128 "[xy" 0 2 error]))))
       (kill-buffer buf))))
diff --git a/relint.el b/relint.el
index 82927d28f2..69f7856185 100644
--- a/relint.el
+++ b/relint.el
@@ -234,7 +234,8 @@ in case it occupies more than one position in the buffer."
      error-buffer
      (concat
       (format "%s:%s: " file loc-str)
-      (and (eq severity 'error) "error: ")
+      (cond ((eq severity 'error) "error: ")
+            ((eq severity 'info) "info: "))
       message
       (cond ((and beg-idx end-idx (< beg-idx end-idx))
              (format " (pos %d..%d)" beg-idx end-idx))
@@ -245,12 +246,13 @@ in case it occupies more than one position in the buffer."
   
 (defun relint--output-complaints (buffer file complaints error-buffer)
   (with-current-buffer buffer
-    (dolist (complaint complaints)
-      (relint--output-complaint error-buffer file complaint))))
+    (dolist (group complaints)
+      (dolist (complaint group)
+        (relint--output-complaint error-buffer file complaint)))))
 
 (defvar relint--suppression-count)
 (defvar relint--complaints
-  ;; list of
+  ;; list of lists of
   ;;   [MESSAGE EXPR-POS BEG-POS END-POS STRING BEG-IDX END-IDX SEVERITY]
   ;; with fields:
   ;;   MESSAGE   string of message
@@ -263,30 +265,48 @@ in case it occupies more than one position in the buffer."
   ;;   SEVERITY  `error', `warning' or `info'
   )
 
-(defun relint--report (message expr-pos beg-pos end-pos
-                       str beg-idx end-idx severity)
+(defun relint--report-group (group)
+  (let* ((diag (car group))
+         (message (aref diag 0))
+         (expr-pos (aref diag 1))
+         (beg-pos (aref diag 2)))
   (if (relint--suppression (or expr-pos beg-pos) message)
       (setq relint--suppression-count (1+ relint--suppression-count))
-    (push (vector message expr-pos beg-pos end-pos str beg-idx end-idx 
severity)
-          relint--complaints)))
+    (push group relint--complaints))))
 
-(defun relint--report-at-path (start-pos path msg str beg-idx end-idx severity)
-  (let* ((expr-pos (relint--pos-from-start-pos-path start-pos path))
-         (beg-pos (and beg-idx (relint--string-pos expr-pos beg-idx nil)))
+(defsubst relint--make-diag (message expr-pos beg-pos end-pos
+                             str beg-idx end-idx severity)
+  (vector message expr-pos beg-pos end-pos str beg-idx end-idx severity))
+
+(defun relint--report-one (message expr-pos beg-pos end-pos
+                           str beg-idx end-idx severity)
+  (relint--report-group
+   (list (relint--make-diag message expr-pos beg-pos end-pos
+                            str beg-idx end-idx severity))))
+
+(defun relint--diag-on-string (expr-pos string message beg-idx end-idx 
severity)
+  (let* ((beg-pos (and beg-idx (relint--string-pos expr-pos beg-idx nil)))
          (end-pos (and end-idx (relint--string-pos expr-pos end-idx t))))
-    (relint--report msg expr-pos beg-pos end-pos str beg-idx end-idx 
severity)))
+    (relint--make-diag message expr-pos beg-pos end-pos
+                       string beg-idx end-idx severity)))
+
+(defun relint--report-at-path (start-pos path msg str beg-idx end-idx severity)
+  (let ((expr-pos (relint--pos-from-start-pos-path start-pos path)))
+    (relint--report-group
+     (list (relint--diag-on-string
+            expr-pos str msg beg-idx end-idx severity)))))
 
 ;; FIXME: need a way to report a diagnostic for an interval-location
 ;; whose extent is a Lisp expression!
 
 (defun relint--warn-at (beg-pos end-pos message)
-  (relint--report message nil beg-pos end-pos nil nil nil 'warning))
+  (relint--report-one message nil beg-pos end-pos nil nil nil 'warning))
 
 (defun relint--warn (start-pos path message &optional str str-beg str-end)
   (relint--report-at-path start-pos path message str str-beg str-end 'warning))
 
 (defun relint--err-at (pos message)
-  (relint--report message nil pos nil nil nil nil 'error))
+  (relint--report-one message nil pos nil nil nil nil 'error))
 
 (defun relint--escape-string (str escape-printable)
   (replace-regexp-in-string
@@ -329,36 +349,42 @@ in case it occupies more than one position in the buffer."
     ((pred stringp) name)
     ((pred symbolp) (symbol-name name))))
 
-(defun relint--message-with-name (msg name)
+(defun relint--message-with-context (msg name)
   (format "In %s: %s" (relint--expand-name name) msg))
 
-(defun relint--check-string (complaints string name pos path)
-  ;; FIXME: Compatibility hack: if given a list-of-lists, flatten it.
-  ;; We should move to the list-of-lists repr for our purposes too,
-  ;; to preserve diag clusters when sorting.
-  (when (consp (car complaints))
-    (setq complaints (mapcar #'car complaints)) ;HACK!
-    ;;(setq complaints (apply #'append complaints))
-    )
-  (dolist (c complaints)
-    (let* ((beg (nth 0 c))
-           (end (nth 1 c))
-           (msg (nth 2 c))
-           (severity (nth 3 c)))
-      (relint--report-at-path pos path (relint--message-with-name msg name)
-                              string beg end severity))))
+(defun relint--string-complaints (complaints string name start-pos path)
+  (when complaints
+    (let ((expr-pos (relint--pos-from-start-pos-path start-pos path)))
+      (dolist (cg complaints)
+        (relint--report-group
+         (mapcar (lambda (c)
+                   (let* ((beg (nth 0 c))
+                          (end (nth 1 c))
+                          (msg (nth 2 c))
+                          (severity (nth 3 c))
+                          ;; Only use message-with-context for non-info diags
+                          (message (if (eq severity 'info)
+                                       msg
+                                     (relint--message-with-context msg name))))
+                     (relint--diag-on-string expr-pos string
+                                             message beg end severity)))
+                 cg))))))
 
 (defun relint--check-skip-set (skip-set name pos path)
-  (relint--check-string (xr-skip-set-lint skip-set) skip-set name pos path))
+  (relint--string-complaints (xr-skip-set-lint skip-set)
+                             skip-set name pos path))
 
 (defun relint--check-re-string (re name pos path)
-  (relint--check-string (xr-lint re nil relint-xr-checks) re name pos path))
+  (relint--string-complaints (xr-lint re nil relint-xr-checks)
+                             re name pos path))
   
 (defun relint--check-file-re-string (re name pos path)
-  (relint--check-string (xr-lint re 'file relint-xr-checks) re name pos path))
+  (relint--string-complaints (xr-lint re 'file relint-xr-checks)
+                             re name pos path))
   
-(defun relint--check-syntax-string (syn name pos path)
-  (relint--check-string (relint--syntax-string-lint syn) syn name pos path))
+(defun relint--check-syntax-string (syntax name pos path)
+  (relint--string-complaints (relint--syntax-string-lint syntax)
+                             syntax name pos path))
 
 (defconst relint--syntax-codes
   '((?-  . whitespace)
@@ -380,7 +406,8 @@ in case it occupies more than one position in the buffer."
     (?!  . comment-delimiter)))
 
 (defun relint--syntax-string-lint (syntax)
-  "Check the syntax-skip string SYNTAX.  Return list of complaints."
+  "Check the syntax-skip string SYNTAX.
+Return list of complaint groups, each a list of (BEG END MESSAGE SEVERITY)."
   (let ((errs nil)
         (start (if (string-prefix-p "^" syntax) 1 0)))
     (when (member syntax '("" "^"))
@@ -2584,9 +2611,10 @@ The keys are sorted numerically, in ascending order.")
       (cons
        (relint--sort-with-key
         ;; Sort by error position if available, expression position otherwise.
-        (lambda (c)
-          (let ((expr-pos (aref c 1))
-                (error-pos (aref c 2)))
+        (lambda (g)
+          (let* ((c (car g))
+                 (expr-pos (aref c 1))
+                 (error-pos (aref c 2)))
             (or error-pos expr-pos)))
         complaints)
        relint--suppression-count))))
@@ -2741,23 +2769,24 @@ The buffer must be in emacs-lisp-mode."
 ;;;###autoload
 (defun relint-buffer (buffer)
   "Scan BUFFER for regexp errors. Return list of diagnostics.
-Each element in the returned list has the form
+Each element in the returned list is a vector having the form
 
-  (MESSAGE EXPR-POS BEG-POS END-POS STRING BEG-IDX END-IDX SEVERITY)
+  [MESSAGE EXPR-POS BEG-POS END-POS STRING BEG-IDX END-IDX SEVERITY]
 
 where
 
   MESSAGE           the message string
-  EXPR-POS          the location of the flawed expression or nil
-  BEG-POS, END-POS  exact bounds in the buffer of the error, or nil
+  EXPR-POS          the position of the flawed expression or nil
+  BEG-POS, END-POS  exact bounds in the buffer (inclusive), or nil
   STRING            nil or a string to which the message pertains
-  BEG-IDX, END-IDX  bounds in STRING or nil
+  BEG-IDX, END-IDX  bounds in STRING (inclusive) or nil
   SEVERITY          `error', `warning' or `info'
 
 The intent is that BEG-POS..END-POS is the buffer range that
 corresponds to STRING at BEG-IDX..END-IDX, if such a location can be
 determined."
-  (car (relint--scan-buffer buffer)))
+  (let ((groups (car (relint--scan-buffer buffer))))
+    (apply #'append groups)))           ; flatten groups
 
 (defun relint-batch ()
   "Scan elisp source files for regexp-related errors.
diff --git a/test/1.expected b/test/1.expected
index c0bac97f9d..f24933244c 100644
--- a/test/1.expected
+++ b/test/1.expected
@@ -1,15 +1,27 @@
 1.elisp:6:25-25: In bad-regexp: Duplicated `A' inside character alternative 
(pos 2)
   "[AA]"
    ..^
+1.elisp:6:24-24: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 1.elisp:7:24-24: In bad-regex: Duplicated `A' inside character alternative 
(pos 2)
   "[AA]"
    ..^
+1.elisp:7:23-23: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 1.elisp:8:21-21: In bad-re: Duplicated `A' inside character alternative (pos 2)
   "[AA]"
    ..^
+1.elisp:8:20-20: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 1.elisp:9:26-26: In bad-pattern: Duplicated `A' inside character alternative 
(pos 2)
   "[AA]"
    ..^
+1.elisp:9:25-25: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 1.elisp:11:30-30: In bad-regexps: Unescaped literal `+' (pos 0)
   "+a"
    ^
@@ -88,36 +100,63 @@
 1.elisp:31:40-40: In bad-font-lock-keywords (tag): Duplicated `x' inside 
character alternative (pos 2)
   "[xx]"
    ..^
+1.elisp:31:39-39: info: Previous occurrence here (pos 1)
+  "[xx]"
+   .^
 1.elisp:31:54-54: In bad-font-lock-keywords: Duplicated `y' inside character 
alternative (pos 2)
   "[yy]"
    ..^
+1.elisp:31:53-53: info: Previous occurrence here (pos 1)
+  "[yy]"
+   .^
 1.elisp:32:45-45: In more-font-lock-keywords-bad (tag): Duplicated `u' inside 
character alternative (pos 2)
   "[uu]"
    ..^
+1.elisp:32:44-44: info: Previous occurrence here (pos 1)
+  "[uu]"
+   .^
 1.elisp:32:59-59: In more-font-lock-keywords-bad: Duplicated `v' inside 
character alternative (pos 2)
   "[vv]"
    ..^
+1.elisp:32:58-58: info: Previous occurrence here (pos 1)
+  "[vv]"
+   .^
 1.elisp:33:39-39: In font-lock-add-keywords (tag): Duplicated `s' inside 
character alternative (pos 2)
   "[ss]"
    ..^
+1.elisp:33:38-38: info: Previous occurrence here (pos 1)
+  "[ss]"
+   .^
 1.elisp:33:53-53: In font-lock-add-keywords: Duplicated `t' inside character 
alternative (pos 2)
   "[tt]"
    ..^
+1.elisp:33:52-52: info: Previous occurrence here (pos 1)
+  "[tt]"
+   .^
 1.elisp:36:23-23: In bad-var-1: Unescaped literal `^' (pos 1)
   "a^"
    .^
 1.elisp:38:22-22: In bad-var-2: Duplicated `z' inside character alternative 
(pos 2)
   "[zz]"
    ..^
+1.elisp:38:21-21: info: Previous occurrence here (pos 1)
+  "[zz]"
+   .^
 1.elisp:40:24-26: In bad-var-3: Reversed range `o-O' matches nothing (pos 1..3)
   "[o-O]"
    .^^^
 1.elisp:46:28-28: In bad-custom-1: Duplicated `n' inside character alternative 
(pos 2)
   "[nn]"
    ..^
+1.elisp:46:27-27: info: Previous occurrence here (pos 1)
+  "[nn]"
+   .^
 1.elisp:50:28-28: In bad-custom-2: Duplicated `s' inside character alternative 
(pos 2)
   "[ss]"
    ..^
+1.elisp:50:27-27: info: Previous occurrence here (pos 1)
+  "[ss]"
+   .^
 1.elisp:57:9: In bad-custom-3-regexp: Unescaped literal `+' (pos 0)
   "+a+"
    ^
@@ -139,18 +178,33 @@
 1.elisp:73:35-35: In bad-custom-7: Duplicated `a' inside character alternative 
(pos 2)
   "[aa]"
    ..^
+1.elisp:73:34-34: info: Previous occurrence here (pos 1)
+  "[aa]"
+   .^
 1.elisp:80:9: In bad-custom-8: Duplicated `1' inside character alternative 
(pos 2)
   "[11]"
    ..^
+1.elisp:80:9: info: Previous occurrence here (pos 1)
+  "[11]"
+   .^
 1.elisp:80:9: In bad-custom-8: Duplicated `2' inside character alternative 
(pos 2)
   "[22]"
    ..^
+1.elisp:80:9: info: Previous occurrence here (pos 1)
+  "[22]"
+   .^
 1.elisp:85:9: In bad-custom-9-regexp: Duplicated `3' inside character 
alternative (pos 2)
   "[33]"
    ..^
+1.elisp:85:9: info: Previous occurrence here (pos 1)
+  "[33]"
+   .^
 1.elisp:89:9: In bad-custom-10: Duplicated `4' inside character alternative 
(pos 2)
   "[44]"
    ..^
+1.elisp:89:9: info: Previous occurrence here (pos 1)
+  "[44]"
+   .^
 1.elisp:93:11-11: In compilation-error-regexp-alist-alist (aa): Unescaped 
literal `^' (pos 1)
   "a^a"
    .^
@@ -166,6 +220,9 @@
 1.elisp:101:12-12: In define-generic-mode my-mode: Repetition of repetition 
(pos 2)
   "b++"
    ..^
+1.elisp:101:10-11: info: This is the inner expression (pos 0..1)
+  "b++"
+   ^^
 1.elisp:107:6-6: In call to syntax-propertize-rules: Unescaped literal `$' 
(pos 0)
   "$1$"
    ^
@@ -181,9 +238,15 @@
 1.elisp:116:50-50: In my-ts--font-lock-rules: Duplicated `5' inside character 
alternative (pos 2)
   "[55]"
    ..^
+1.elisp:116:49-49: info: Previous occurrence here (pos 1)
+  "[55]"
+   .^
 1.elisp:117:54-54: In my-ts-mode-font-lock-rules: Duplicated `6' inside 
character alternative (pos 2)
   "[66]"
    ..^
+1.elisp:117:53-53: info: Previous occurrence here (pos 1)
+  "[66]"
+   .^
 1.elisp:121:16-18: Suspect range `+-/' (pos 0..2)
   "+-/"
    ^^^
diff --git a/test/10.expected b/test/10.expected
index 625e9f7b9c..7a0cffac96 100644
--- a/test/10.expected
+++ b/test/10.expected
@@ -1,33 +1,63 @@
 10.elisp:7:17-17: In test-1-regexp-list: Duplicated `a' inside character 
alternative (pos 2)
   "[aa]"
    ..^
+10.elisp:7:16-16: info: Previous occurrence here (pos 1)
+  "[aa]"
+   .^
 10.elisp:8:21-21: In test-1-regexp-list: Duplicated `b' inside character 
alternative (pos 2)
   "[bb]"
    ..^
+10.elisp:8:20-20: info: Previous occurrence here (pos 1)
+  "[bb]"
+   .^
 10.elisp:11:13-13: In test-2-regexp-alist: Duplicated `c' inside character 
alternative (pos 2)
   "[cc]"
    ..^
+10.elisp:11:12-12: info: Previous occurrence here (pos 1)
+  "[cc]"
+   .^
 10.elisp:12:5: In test-2-regexp-alist: Duplicated `d' inside character 
alternative (pos 2)
   "[dd]"
    ..^
+10.elisp:12:5: info: Previous occurrence here (pos 1)
+  "[dd]"
+   .^
 10.elisp:13:5: In test-2-regexp-alist: Duplicated `e' inside character 
alternative (pos 2)
   "[ee]"
    ..^
+10.elisp:13:5: info: Previous occurrence here (pos 1)
+  "[ee]"
+   .^
 10.elisp:14:5: In test-2-regexp-alist: Duplicated `f' inside character 
alternative (pos 2)
   "[ff]"
    ..^
+10.elisp:14:5: info: Previous occurrence here (pos 1)
+  "[ff]"
+   .^
 10.elisp:14:5: In test-2-regexp-alist: Duplicated `g' inside character 
alternative (pos 2)
   "[gg]"
    ..^
+10.elisp:14:5: info: Previous occurrence here (pos 1)
+  "[gg]"
+   .^
 10.elisp:15:13-13: In test-2-regexp-alist: Duplicated `h' inside character 
alternative (pos 2)
   "[hh]"
    ..^
+10.elisp:15:12-12: info: Previous occurrence here (pos 1)
+  "[hh]"
+   .^
 10.elisp:16:24-24: Single-character range `z-z' (pos 0)
   "z-z"
    ^
 10.elisp:20:9-9: In test-3-regexp-alist: Duplicated `i' inside character 
alternative (pos 2)
   "[ii]"
    ..^
+10.elisp:20:8-8: info: Previous occurrence here (pos 1)
+  "[ii]"
+   .^
 10.elisp:20:18-18: In test-3-regexp-alist: Duplicated `j' inside character 
alternative (pos 2)
   "[jj]"
    ..^
+10.elisp:20:17-17: info: Previous occurrence here (pos 1)
+  "[jj]"
+   .^
diff --git a/test/11.expected b/test/11.expected
index c44a9f9e62..11f3cc1268 100644
--- a/test/11.expected
+++ b/test/11.expected
@@ -28,9 +28,15 @@
 11.elisp:16:4: In rx `regexp' form: Duplicated `1' inside character 
alternative (pos 2)
   "[11]"
    ..^
+11.elisp:16:4: info: Previous occurrence here (pos 1)
+  "[11]"
+   .^
 11.elisp:16:4: In rx `regex' form: Duplicated `2' inside character alternative 
(pos 2)
   "[22]"
    ..^
+11.elisp:16:4: info: Previous occurrence here (pos 1)
+  "[22]"
+   .^
 11.elisp:16:4: Duplicated character `3' (pos 1)
   "33"
    .^
diff --git a/test/13.expected b/test/13.expected
index 0048c1d310..ee49469c16 100644
--- a/test/13.expected
+++ b/test/13.expected
@@ -24,6 +24,9 @@
 13.elisp:18:30-31: In call to re-search-forward: Repetition of repetition (pos 
4)
   "$.x++"
    ....^
+13.elisp:18:27-29: info: This is the inner expression (pos 2..3)
+  "$.x++"
+   ..^^
 13.elisp:18:30-31: Ineffective string escape `\+'
 13.elisp:19:16-17: Ineffective string escape `\q'
 13.elisp:22:16-17: Ineffective string escape `\8'
diff --git a/test/2.expected b/test/2.expected
index ec000b61ec..b04c603598 100644
--- a/test/2.expected
+++ b/test/2.expected
@@ -1,84 +1,165 @@
 2.elisp:5:18-18: In call to looking-at: Duplicated `a' inside character 
alternative (pos 2)
   "[aa]"
    ..^
+2.elisp:5:17-17: info: Previous occurrence here (pos 1)
+  "[aa]"
+   .^
 2.elisp:6:25-25: In call to re-search-forward: Duplicated `b' inside character 
alternative (pos 2)
   "[bb]"
    ..^
+2.elisp:6:24-24: info: Previous occurrence here (pos 1)
+  "[bb]"
+   .^
 2.elisp:7:26-26: In call to re-search-backward: Duplicated `c' inside 
character alternative (pos 2)
   "[cc]"
    ..^
+2.elisp:7:25-25: info: Previous occurrence here (pos 1)
+  "[cc]"
+   .^
 2.elisp:8:29-29: In call to search-forward-regexp: Duplicated `B' inside 
character alternative (pos 2)
   "[BB]"
    ..^
+2.elisp:8:28-28: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 2.elisp:9:29-29: In call to search-forward-regexp: Duplicated `C' inside 
character alternative (pos 2)
   "[CC]"
    ..^
+2.elisp:9:28-28: info: Previous occurrence here (pos 1)
+  "[CC]"
+   .^
 2.elisp:10:20-20: In call to string-match: Duplicated `d' inside character 
alternative (pos 2)
   "[dd]"
    ..^
+2.elisp:10:19-19: info: Previous occurrence here (pos 1)
+  "[dd]"
+   .^
 2.elisp:11:22-22: In call to string-match-p: Duplicated `e' inside character 
alternative (pos 2)
   "[ee]"
    ..^
+2.elisp:11:21-21: info: Previous occurrence here (pos 1)
+  "[ee]"
+   .^
 2.elisp:12:20-20: In call to looking-at-p: Duplicated `f' inside character 
alternative (pos 2)
   "[ff]"
    ..^
+2.elisp:12:19-19: info: Previous occurrence here (pos 1)
+  "[ff]"
+   .^
 2.elisp:13:20-20: In call to looking-back: Duplicated `g' inside character 
alternative (pos 2)
   "[gg]"
    ..^
+2.elisp:13:19-19: info: Previous occurrence here (pos 1)
+  "[gg]"
+   .^
 2.elisp:14:32-32: In call to replace-regexp-in-string: Duplicated `h' inside 
character alternative (pos 2)
   "[hh]"
    ..^
+2.elisp:14:31-31: info: Previous occurrence here (pos 1)
+  "[hh]"
+   .^
 2.elisp:15:28-28: In call to query-replace-regexp: Duplicated `j' inside 
character alternative (pos 2)
   "[jj]"
    ..^
+2.elisp:15:27-27: info: Previous occurrence here (pos 1)
+  "[jj]"
+   .^
 2.elisp:16:24-24: In call to posix-looking-at: Duplicated `k' inside character 
alternative (pos 2)
   "[kk]"
    ..^
+2.elisp:16:23-23: info: Previous occurrence here (pos 1)
+  "[kk]"
+   .^
 2.elisp:17:29-29: In call to posix-search-backward: Duplicated `l' inside 
character alternative (pos 2)
   "[ll]"
    ..^
+2.elisp:17:28-28: info: Previous occurrence here (pos 1)
+  "[ll]"
+   .^
 2.elisp:18:28-28: In call to posix-search-forward: Duplicated `m' inside 
character alternative (pos 2)
   "[mm]"
    ..^
+2.elisp:18:27-27: info: Previous occurrence here (pos 1)
+  "[mm]"
+   .^
 2.elisp:19:26-26: In call to posix-string-match: Duplicated `n' inside 
character alternative (pos 2)
   "[nn]"
    ..^
+2.elisp:19:25-25: info: Previous occurrence here (pos 1)
+  "[nn]"
+   .^
 2.elisp:20:37-37: In call to load-history-filename-element: Duplicated `o' 
inside character alternative (pos 2)
   "[oo]"
    ..^
+2.elisp:20:36-36: info: Previous occurrence here (pos 1)
+  "[oo]"
+   .^
 2.elisp:21:29-29: In call to kill-matching-buffers: Duplicated `p' inside 
character alternative (pos 2)
   "[pp]"
    ..^
+2.elisp:21:28-28: info: Previous occurrence here (pos 1)
+  "[pp]"
+   .^
 2.elisp:22:18-18: In call to keep-lines: Duplicated `q' inside character 
alternative (pos 2)
   "[qq]"
    ..^
+2.elisp:22:17-17: info: Previous occurrence here (pos 1)
+  "[qq]"
+   .^
 2.elisp:23:19-19: In call to flush-lines: Duplicated `r' inside character 
alternative (pos 2)
   "[rr]"
    ..^
+2.elisp:23:18-18: info: Previous occurrence here (pos 1)
+  "[rr]"
+   .^
 2.elisp:24:16-16: In call to how-many: Duplicated `s' inside character 
alternative (pos 2)
   "[ss]"
    ..^
+2.elisp:24:15-15: info: Previous occurrence here (pos 1)
+  "[ss]"
+   .^
 2.elisp:25:22-22: In call to split-string: Duplicated `t' inside character 
alternative (pos 2)
   "[tt]"
    ..^
+2.elisp:25:21-21: info: Previous occurrence here (pos 1)
+  "[tt]"
+   .^
 2.elisp:25:33-33: In call to split-string: Duplicated `u' inside character 
alternative (pos 2)
   "[uu]"
    ..^
+2.elisp:25:32-32: info: Previous occurrence here (pos 1)
+  "[uu]"
+   .^
 2.elisp:26:34-34: In call to split-string-and-unquote: Duplicated `v' inside 
character alternative (pos 2)
   "[vv]"
    ..^
+2.elisp:26:33-33: info: Previous occurrence here (pos 1)
+  "[vv]"
+   .^
 2.elisp:27:26-26: In call to string-trim-left: Duplicated `w' inside character 
alternative (pos 2)
   "[ww]"
    ..^
+2.elisp:27:25-25: info: Previous occurrence here (pos 1)
+  "[ww]"
+   .^
 2.elisp:28:27-27: In call to string-trim-right: Duplicated `x' inside 
character alternative (pos 2)
   "[xx]"
    ..^
+2.elisp:28:26-26: info: Previous occurrence here (pos 1)
+  "[xx]"
+   .^
 2.elisp:29:21-21: In call to string-trim: Duplicated `y' inside character 
alternative (pos 2)
   "[yy]"
    ..^
+2.elisp:29:20-20: info: Previous occurrence here (pos 1)
+  "[yy]"
+   .^
 2.elisp:29:28-28: In call to string-trim: Duplicated `z' inside character 
alternative (pos 2)
   "[zz]"
    ..^
+2.elisp:29:27-27: info: Previous occurrence here (pos 1)
+  "[zz]"
+   .^
 2.elisp:30:27-27: In call to directory-files: Unescaped literal `+' (pos 0)
   "+1"
    ^
@@ -112,63 +193,123 @@
 2.elisp:52:17-17: In call to f2: Duplicated `B' inside character alternative 
(pos 2)
   "[BB]"
    ..^
+2.elisp:52:16-16: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 2.elisp:52:31-31: In call to f2: Duplicated `D' inside character alternative 
(pos 2)
   "[DD]"
    ..^
+2.elisp:52:30-30: info: Previous occurrence here (pos 1)
+  "[DD]"
+   .^
 2.elisp:52:45-45: In call to f2: Duplicated `F' inside character alternative 
(pos 2)
   "[FF]"
    ..^
+2.elisp:52:44-44: info: Previous occurrence here (pos 1)
+  "[FF]"
+   .^
 2.elisp:52:59-59: In call to f2: Duplicated `H' inside character alternative 
(pos 2)
   "[HH]"
    ..^
+2.elisp:52:58-58: info: Previous occurrence here (pos 1)
+  "[HH]"
+   .^
 2.elisp:52:73-73: In call to f2: Duplicated `J' inside character alternative 
(pos 2)
   "[JJ]"
    ..^
+2.elisp:52:72-72: info: Previous occurrence here (pos 1)
+  "[JJ]"
+   .^
 2.elisp:53:17-17: In call to s2: Duplicated `B' inside character alternative 
(pos 2)
   "[BB]"
    ..^
+2.elisp:53:16-16: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 2.elisp:53:31-31: In call to s2: Duplicated `D' inside character alternative 
(pos 2)
   "[DD]"
    ..^
+2.elisp:53:30-30: info: Previous occurrence here (pos 1)
+  "[DD]"
+   .^
 2.elisp:53:45-45: In call to s2: Duplicated `F' inside character alternative 
(pos 2)
   "[FF]"
    ..^
+2.elisp:53:44-44: info: Previous occurrence here (pos 1)
+  "[FF]"
+   .^
 2.elisp:53:59-59: In call to s2: Duplicated `H' inside character alternative 
(pos 2)
   "[HH]"
    ..^
+2.elisp:53:58-58: info: Previous occurrence here (pos 1)
+  "[HH]"
+   .^
 2.elisp:53:73-73: In call to s2: Duplicated `J' inside character alternative 
(pos 2)
   "[JJ]"
    ..^
+2.elisp:53:72-72: info: Previous occurrence here (pos 1)
+  "[JJ]"
+   .^
 2.elisp:54:17-17: In call to m2: Duplicated `B' inside character alternative 
(pos 2)
   "[BB]"
    ..^
+2.elisp:54:16-16: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 2.elisp:54:31-31: In call to m2: Duplicated `D' inside character alternative 
(pos 2)
   "[DD]"
    ..^
+2.elisp:54:30-30: info: Previous occurrence here (pos 1)
+  "[DD]"
+   .^
 2.elisp:54:45-45: In call to m2: Duplicated `F' inside character alternative 
(pos 2)
   "[FF]"
    ..^
+2.elisp:54:44-44: info: Previous occurrence here (pos 1)
+  "[FF]"
+   .^
 2.elisp:54:59-59: In call to m2: Duplicated `H' inside character alternative 
(pos 2)
   "[HH]"
    ..^
+2.elisp:54:58-58: info: Previous occurrence here (pos 1)
+  "[HH]"
+   .^
 2.elisp:54:73-73: In call to m2: Duplicated `J' inside character alternative 
(pos 2)
   "[JJ]"
    ..^
+2.elisp:54:72-72: info: Previous occurrence here (pos 1)
+  "[JJ]"
+   .^
 2.elisp:62:17-17: In call to f5: Duplicated `b' inside character alternative 
(pos 2)
   "[bb]"
    ..^
+2.elisp:62:16-16: info: Previous occurrence here (pos 1)
+  "[bb]"
+   .^
 2.elisp:62:24-24: In call to f5: Duplicated `c' inside character alternative 
(pos 2)
   "[cc]"
    ..^
+2.elisp:62:23-23: info: Previous occurrence here (pos 1)
+  "[cc]"
+   .^
 2.elisp:62:31-31: In call to f5: Duplicated `d' inside character alternative 
(pos 2)
   "[dd]"
    ..^
+2.elisp:62:30-30: info: Previous occurrence here (pos 1)
+  "[dd]"
+   .^
 2.elisp:65:26-26: In :regexp parameter: Duplicated `1' inside character 
alternative (pos 2)
   "[11]"
    ..^
+2.elisp:65:25-25: info: Previous occurrence here (pos 1)
+  "[11]"
+   .^
 2.elisp:66:20-20: In :regex parameter: Duplicated `2' inside character 
alternative (pos 2)
   "[22]"
    ..^
+2.elisp:66:19-19: info: Previous occurrence here (pos 1)
+  "[22]"
+   .^
 2.elisp:69:31-31: In call to sort-regexp-fields: Unescaped literal `$' (pos 3)
   "^.*$x"
    ...^
diff --git a/test/3.expected b/test/3.expected
index d79ea7c7a8..69e9093b6e 100644
--- a/test/3.expected
+++ b/test/3.expected
@@ -7,33 +7,60 @@
 3.elisp:72:4: In call to looking-at: Duplicated `Q' inside character 
alternative (pos 2)
   "[QQ]"
    ..^
+3.elisp:72:4: info: Previous occurrence here (pos 1)
+  "[QQ]"
+   .^
 3.elisp:94:4: In call to looking-at: Unescaped literal `^' (pos 2)
   "^m^"
    ..^
 3.elisp:131:4: In another-bad-regexp-list: Duplicated `1' inside character 
alternative (pos 2)
   "[11]"
    ..^
+3.elisp:131:4: info: Previous occurrence here (pos 1)
+  "[11]"
+   .^
 3.elisp:132:4: In another-bad-regexp-list: Duplicated `2' inside character 
alternative (pos 2)
   "[22]"
    ..^
+3.elisp:132:4: info: Previous occurrence here (pos 1)
+  "[22]"
+   .^
 3.elisp:132:4: In another-bad-regexp-list: Duplicated `1' inside character 
alternative (pos 2)
   "[11]"
    ..^
+3.elisp:132:4: info: Previous occurrence here (pos 1)
+  "[11]"
+   .^
 3.elisp:133:4: In another-bad-regexp-list: Duplicated `3' inside character 
alternative (pos 2)
   "[33]"
    ..^
+3.elisp:133:4: info: Previous occurrence here (pos 1)
+  "[33]"
+   .^
 3.elisp:134:4: In another-bad-regexp-list: Duplicated `4' inside character 
alternative (pos 2)
   "[44]"
    ..^
+3.elisp:134:4: info: Previous occurrence here (pos 1)
+  "[44]"
+   .^
 3.elisp:135:4: In another-bad-regexp-list: Duplicated `5' inside character 
alternative (pos 2)
   "[55]"
    ..^
+3.elisp:135:4: info: Previous occurrence here (pos 1)
+  "[55]"
+   .^
 3.elisp:136:4: In another-bad-regexp-list: Duplicated `6' inside character 
alternative (pos 2)
   "[66]"
    ..^
+3.elisp:136:4: info: Previous occurrence here (pos 1)
+  "[66]"
+   .^
 3.elisp:137:4: In another-bad-regexp-list: Duplicated `7' inside character 
alternative (pos 2)
   "[77]"
    ..^
+3.elisp:137:4: info: Previous occurrence here (pos 1)
+  "[77]"
+   .^
 3.elisp:141:13-13: In call to looking-at: Unescaped literal `+' (pos 0)
   "+abcdefgh"
    ^
@@ -43,3 +70,6 @@
 3.elisp:164:4: In call to looking-at: Duplicated `A' inside character 
alternative (pos 2)
   "[AA]"
    ..^
+3.elisp:164:4: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
diff --git a/test/4.expected b/test/4.expected
index a7d3a1d79e..baf20ca36c 100644
--- a/test/4.expected
+++ b/test/4.expected
@@ -13,9 +13,15 @@
 4.elisp:17:15: In call to looking-at: Duplicated `A' inside character 
alternative (pos 2)
   "[AA]"
    ..^
+4.elisp:17:15: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 4.elisp:18:15: In call to looking-at: Duplicated `B' inside character 
alternative (pos 2)
   "[BB]"
    ..^
+4.elisp:18:15: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 4.elisp:19:15: In call to looking-at: Unescaped literal `+' (pos 0)
   "+b"
    ^
@@ -37,15 +43,24 @@
 4.elisp:44:15: In call to looking-at: Repetition of repetition (pos 2)
   "b++"
    ..^
+4.elisp:44:15: info: This is the inner expression (pos 0..1)
+  "b++"
+   ^^
 4.elisp:45:15: In call to looking-at: Repetition of repetition (pos 2)
   "c++"
    ..^
+4.elisp:45:15: info: This is the inner expression (pos 0..1)
+  "c++"
+   ^^
 4.elisp:46:15: In call to looking-at: Unescaped literal `$' (pos 1)
   "a$b"
    .^
 4.elisp:47:15: In call to looking-at: Repetition of repetition (pos 2)
   "d++"
    ..^
+4.elisp:47:15: info: This is the inner expression (pos 0..1)
+  "d++"
+   ^^
 4.elisp:48:15: In call to looking-at: Unescaped literal `*' (pos 0)
   "*"
    ^
diff --git a/test/5.expected b/test/5.expected
index d73b1beeac..eefd2bd66f 100644
--- a/test/5.expected
+++ b/test/5.expected
@@ -7,9 +7,15 @@
 5.elisp:21:4: In call to looking-at: Duplicated `A' inside character 
alternative (pos 2)
   "[AA]"
    ..^
+5.elisp:21:4: info: Previous occurrence here (pos 1)
+  "[AA]"
+   .^
 5.elisp:32:17: In call to looking-at: Duplicated `B' inside character 
alternative (pos 2)
   "[BB]"
    ..^
+5.elisp:32:17: info: Previous occurrence here (pos 1)
+  "[BB]"
+   .^
 5.elisp:37:17: In call to looking-at: Unescaped literal `+' (pos 0)
   "+a"
    ^
@@ -19,12 +25,21 @@
 5.elisp:46:15: In call to looking-at: Duplicated `C' inside character 
alternative (pos 2)
   "[CC]"
    ..^
+5.elisp:46:15: info: Previous occurrence here (pos 1)
+  "[CC]"
+   .^
 5.elisp:51:17: In call to looking-at: Duplicated `D' inside character 
alternative (pos 2)
   "[DD]"
    ..^
+5.elisp:51:17: info: Previous occurrence here (pos 1)
+  "[DD]"
+   .^
 5.elisp:59:15: In call to looking-at: Duplicated `E' inside character 
alternative (pos 2)
   "[EE]"
    ..^
+5.elisp:59:15: info: Previous occurrence here (pos 1)
+  "[EE]"
+   .^
 5.elisp:62:15: In call to looking-at: Unescaped literal `*' (pos 0)
   "*b"
    ^
@@ -34,6 +49,12 @@
 5.elisp:72:15: In call to looking-at: Duplicated `U' inside character 
alternative (pos 2)
   "[UU]"
    ..^
+5.elisp:72:15: info: Previous occurrence here (pos 1)
+  "[UU]"
+   .^
 5.elisp:75:15: In call to looking-at: Duplicated `V' inside character 
alternative (pos 2)
   "[VV]"
    ..^
+5.elisp:75:15: info: Previous occurrence here (pos 1)
+  "[VV]"
+   .^
diff --git a/test/7.expected b/test/7.expected
index 994ef63bcb..7991b826e5 100644
--- a/test/7.expected
+++ b/test/7.expected
@@ -7,12 +7,21 @@
 7.elisp:24:15: In call to looking-at: Duplicated `*' inside character 
alternative (pos 2)
   "[**]"
    ..^
+7.elisp:24:15: info: Previous occurrence here (pos 1)
+  "[**]"
+   .^
 7.elisp:31:4: In call to looking-at: Unescaped literal `$' (pos 3)
   "!\"#$%"
    ....^
 7.elisp:34:15: In call to looking-at: Duplicated `X' inside character 
alternative (pos 2)
   "[XX]"
    ..^
+7.elisp:34:15: info: Previous occurrence here (pos 1)
+  "[XX]"
+   .^
 7.elisp:37:15: In call to looking-at: Duplicated `X' inside character 
alternative (pos 2)
   "[XX]"
    ..^
+7.elisp:37:15: info: Previous occurrence here (pos 1)
+  "[XX]"
+   .^
diff --git a/test/9.expected b/test/9.expected
index 50f8ea7498..9157f027a1 100644
--- a/test/9.expected
+++ b/test/9.expected
@@ -1,39 +1,75 @@
 9.elisp:6:33-33: In page-delimiter: Duplicated `a' inside character 
alternative (pos 2)
   "[aa]"
    ..^
+9.elisp:6:32-32: info: Previous occurrence here (pos 1)
+  "[aa]"
+   .^
 9.elisp:7:37-37: In paragraph-separate: Duplicated `b' inside character 
alternative (pos 2)
   "[bb]"
    ..^
+9.elisp:7:36-36: info: Previous occurrence here (pos 1)
+  "[bb]"
+   .^
 9.elisp:8:34-34: In paragraph-start: Duplicated `c' inside character 
alternative (pos 2)
   "[cc]"
    ..^
+9.elisp:8:33-33: info: Previous occurrence here (pos 1)
+  "[cc]"
+   .^
 9.elisp:9:31-31: In sentence-end: Duplicated `d' inside character alternative 
(pos 2)
   "[dd]"
    ..^
+9.elisp:9:30-30: info: Previous occurrence here (pos 1)
+  "[dd]"
+   .^
 9.elisp:10:37-37: In comment-start-skip: Duplicated `e' inside character 
alternative (pos 2)
   "[ee]"
    ..^
+9.elisp:10:36-36: info: Previous occurrence here (pos 1)
+  "[ee]"
+   .^
 9.elisp:11:35-35: In comment-end-skip: Duplicated `f' inside character 
alternative (pos 2)
   "[ff]"
    ..^
+9.elisp:11:34-34: info: Previous occurrence here (pos 1)
+  "[ff]"
+   .^
 9.elisp:13:25-25: In sentence-end: Duplicated `g' inside character alternative 
(pos 2)
   "[gg]"
    ..^
+9.elisp:13:24-24: info: Previous occurrence here (pos 1)
+  "[gg]"
+   .^
 9.elisp:14:50-50: In paragraph-start: Duplicated `h' inside character 
alternative (pos 2)
   "[hh]"
    ..^
+9.elisp:14:49-49: info: Previous occurrence here (pos 1)
+  "[hh]"
+   .^
 9.elisp:16:32-32: In paragraph-separate: Duplicated `i' inside character 
alternative (pos 2)
   "[ii]"
    ..^
+9.elisp:16:31-31: info: Previous occurrence here (pos 1)
+  "[ii]"
+   .^
 9.elisp:17:28-28: In page-delimiter: Duplicated `j' inside character 
alternative (pos 2)
   "[jj]"
    ..^
+9.elisp:17:27-27: info: Previous occurrence here (pos 1)
+  "[jj]"
+   .^
 9.elisp:18:35-35: In comment-start-skip: Duplicated `k' inside character 
alternative (pos 2)
   "[kk]"
    ..^
+9.elisp:18:34-34: info: Previous occurrence here (pos 1)
+  "[kk]"
+   .^
 9.elisp:19:33-33: In comment-end-skip: Duplicated `l' inside character 
alternative (pos 2)
   "[ll]"
    ..^
+9.elisp:19:32-32: info: Previous occurrence here (pos 1)
+  "[ll]"
+   .^
 9.elisp:22:39-39: In treesit-sentence-type-regexp: Unescaped literal `+' (pos 
0)
   "+0"
    ^
@@ -55,18 +91,33 @@
 9.elisp:44:40-40: In font-lock-keywords (tag): Duplicated `m' inside character 
alternative (pos 2)
   "[mm]"
    ..^
+9.elisp:44:39-39: info: Previous occurrence here (pos 1)
+  "[mm]"
+   .^
 9.elisp:45:34-34: In font-lock-keywords (tag): Duplicated `n' inside character 
alternative (pos 2)
   "[nn]"
    ..^
+9.elisp:45:33-33: info: Previous occurrence here (pos 1)
+  "[nn]"
+   .^
 9.elisp:46:56-56: In font-lock-keywords (tag): Duplicated `o' inside character 
alternative (pos 2)
   "[oo]"
    ..^
+9.elisp:46:55-55: info: Previous occurrence here (pos 1)
+  "[oo]"
+   .^
 9.elisp:52:9-9: In my-font-lock-keywords-2 (beta): Duplicated `q' inside 
character alternative (pos 2)
   "[qq]"
    ..^
+9.elisp:52:8-8: info: Previous occurrence here (pos 1)
+  "[qq]"
+   .^
 9.elisp:56:9: In font-lock-defaults (alpha): Duplicated `p' inside character 
alternative (pos 2)
   "[pp]"
    ..^
+9.elisp:56:9: info: Previous occurrence here (pos 1)
+  "[pp]"
+   .^
 9.elisp:62:23-23: In treesit-simple-indent-rules (a): Unescaped literal `+' 
(pos 0)
   "+e+"
    ^


Reply via email to