relint-el_2.1+repack/0000755000175000017500000000000014742644202014431 5ustar dogslegdogslegrelint-el_2.1+repack/test/0000755000175000017500000000000014734334740015414 5ustar dogslegdogslegrelint-el_2.1+repack/test/11.elisp0000644000175000017500000000201514734334740016671 0ustar dogslegdogsleg;;; Relint test file 11 -*- emacs-lisp -*- ;; Test errors in rx (defun my-fun () (list (rx nonl (in ?c "abc" ?b)) (rx (: (* (not (char "0-9ac-ceg-h3")) (any "a-m" (?f . ?t) "!s") (opt (not-char space "q" digit space))) (any "0-9()-+") (any "0-9+-.") (any "-a-e") (any "k-m-") (any "A-F-K-T"))) (rx (regexp "[11]") (regex "[22]") (eval (list 'any "33"))) (rx-to-string '(: bol (any "AA"))) (rx-to-string `(: bol ,(list 'in "BB"))) `(rx ,(list 'char "CC")) `(rx ,@(list 'nonl (list 'any "DD"))) (let ((things '(?x ?y ?z ?y))) `(rx (any ?z . ,things)))) ;; No error here. (rx (any "\000-\377" ?å) (any "\377" 255)) ;; But here. (rx (any "\000-\377" "\177" "\240")) (rx (any "a-z" ?m)) (rx (any "a-f" "\000-\377")) (rx (any "\240-\277" "\000-\377")) (rx (or "abc" ?A "def" "ghi" ?A "def")) (rx (| "abc" (= 3 ?*) "def" (= 3 ?*) "ghi" "abc")) ) relint-el_2.1+repack/test/9.expected0000644000175000017500000001106714734334740017314 0ustar dogslegdogsleg9.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" ^ 9.elisp:23:35-35: In treesit-sexp-type-regexp: Unescaped literal `+' (pos 0) "+1" ^ 9.elisp:36:28-28: In imenu-generic-expression: Unescaped literal `+' (pos 0) "+a+" ^ 9.elisp:37:34: In imenu-generic-expression: Unescaped literal `+' (pos 0) "+b+" ^ 9.elisp:38:45: In treesit-simple-imenu-settings: Unescaped literal `+' (pos 0) "+c+" ^ 9.elisp:41:21-21: In treesit-simple-imenu-settings: Unescaped literal `+' (pos 0) "+d+" ^ 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+" ^ 9.elisp:62:29-29: In treesit-simple-indent-rules (a): Unescaped literal `+' (pos 0) "+f+" ^ 9.elisp:62:35-35: In treesit-simple-indent-rules (a): Unescaped literal `+' (pos 0) "+g+" ^ 9.elisp:63:24-24: In treesit-simple-indent-rules (a): Unescaped literal `+' (pos 0) "+h+" ^ 9.elisp:63:30-30: In treesit-simple-indent-rules (a): Unescaped literal `+' (pos 0) "+i+" ^ 9.elisp:63:36-36: In treesit-simple-indent-rules (a): Unescaped literal `+' (pos 0) "+j+" ^ 9.elisp:64:27-27: In treesit-simple-indent-rules (b): Unescaped literal `+' (pos 0) "+k+" ^ 9.elisp:65:25-25: In treesit-simple-indent-rules (b): Unescaped literal `+' (pos 0) "+l+" ^ 9.elisp:66:26-26: In treesit-simple-indent-rules (b): Unescaped literal `+' (pos 0) "+m+" ^ 9.elisp:69:36-36: In treesit-defun-type-regexp: Unescaped literal `+' (pos 0) "+n+" ^ 9.elisp:70:35: In treesit-defun-type-regexp: Unescaped literal `+' (pos 0) "+o+" ^ relint-el_2.1+repack/test/8.expected0000644000175000017500000000035414734334740017310 0ustar dogslegdogsleg8.elisp:5:4: In call to looking-at: Unescaped literal `+' (pos 0) "+A" ^ 8.elisp:10:4: In call to looking-at: Unescaped literal `+' (pos 0) "+B" ^ 8.elisp:15:4: In call to looking-at: Unescaped literal `*' (pos 0) "*C" ^ relint-el_2.1+repack/test/13.elisp0000644000175000017500000000077014734334740016701 0ustar dogslegdogsleg;;; Relint test file 13 -*- emacs-lisp -*- ;; Test ineffective backslashes in strings (defun f1 () "doc string: \! \( \) \[ \] \' all ignored not ignored: \{ \} \| \` \? \* \. \+ \; \q etc" (list "\more" "not ignored: \( \) \[ \] \'" "long string \; \; and so on" "valid: \\ \" \x5e \172 \u1234 \u00000041 \C-x \M-e \ \ and \a \b \f \n \r \t \v \d \e \s \^P")) (defun f2 () (re-search-forward "\$\.x\+\+") '("bracketed \q string")) (defun f3 () (print "a \7 \8 \9 b")) relint-el_2.1+repack/test/10.elisp0000644000175000017500000000065414734334740016677 0ustar dogslegdogsleg;;; Relint test file 10 -*- emacs-lisp -*- ;; Test error position in lists (defconst test-1-regexp-list (append (list "1" "[aa]") (list "2" "3" "[bb]"))) (defconst test-2-regexp-alist `((c . "[cc]") ,(cons 'd "[dd]") (e . ,(concat "[e" "e]")) ,@(list '(f . "[ff]") '(g . "[gg]")) (i . "[hh]") (j . ((rx bol (in "z-z")))))) (defconst test-3-regexp-alist (list '("[ii]" . "[jj]"))) relint-el_2.1+repack/test/1.elisp0000644000175000017500000000647514734334740016626 0ustar dogslegdogsleg;;; Relint test file 1 -*- emacs-lisp -*- ;; Test variable name heuristics for detecting regexps. (defconst innocent-thing "+bad**regexp^") (defconst bad-regexp "[AA]") (defconst bad-regex "[AA]") (defconst bad-re "[AA]") (defconst bad-pattern "[AA]") (defconst bad-regexps '("a" "+a" "a")) (defconst bad-regexes '("b" "+b" "b")) (defconst bad-regexp-list '("c" "+c" "c")) (defconst bad-regex-list '("d" "+d" "d")) (defconst bad-re-list '("e" "+e" "e")) (defconst bad-regexp-alist '((a . "**") ("??" . a) (".^" . "$."))) (defconst bad-regex-alist '((a . "**") ("??" . a) (".^" . "$."))) (defconst bad-re-alist '((a . "**") ("??" . a) (".^" . "$."))) (defconst bad-pattern-alist '((a . "**") ("??" . a) (".^" . "$."))) (defconst bad-mode-alist '((a . "**") ("??" . a) (".^" . "$."))) (defconst bad-rules-list '((eins (this . that) (regexp . "$$")) (zwei (tata . toto) (regexp . "[a-Z]")))) (defconst bad-font-lock-keywords '(("[xx]" . tag) "[yy]")) (defconst more-font-lock-keywords-bad '(("[uu]" . tag) "[vv]")) (font-lock-add-keywords 'mymode '(("[ss]" . tag) "[tt]")) ;; Test variable doc string heuristics. (defconst bad-var-1 "a^" "Regexp, or something.") (defvar bad-var-2 "[zz]" "A regular expression with flaws.") (defcustom bad-var-3 "[o-O]" "This regexp looks at you." :group 'relint-test :type 'string) ;; Test defcustom type heuristics. (defcustom bad-custom-1 "[nn]" "Doc" :group 'relint-test :type 'regexp) (defcustom bad-custom-2 "[ss]" "Doc" :group 'relint-test :type '(regexp :tag "tag")) (defcustom bad-custom-3-regexp nil "Doc" :group 'relint-test :type '(choice (const :tag "*" "+a+") (radio (const "*b*") (const "^c^")))) (defcustom bad-custom-4-regexp nil "Doc" :group 'relint-test :type 'string :options '("a" "+b")) (defcustom bad-custom-5 '(("a" . tata) ("^x^" . toto)) "Doc" :group 'relint-test :type '(alist :key-type regexp :value-type symbol)) (defcustom bad-custom-6 '((toto . "humbug") (tata . "[[:bah:]]")) "Doc" :group 'relint-test :type '(alist :key-type symbol :value-type regexp)) (defcustom bad-custom-7 '("aa" "[aa]") "Doc" :group 'relint-test :type '(repeat regexp)) (defcustom bad-custom-8 nil "Doc" :type '(choice (regexp :tag "*" :value "[11]") (string :tag "+" :value "[22]"))) (defcustom bad-custom-9-regexp nil "Doc" :type '(string :tag "+" :value "[33]")) (defcustom bad-custom-10 nil "regular expression" :type '(string :tag "+" "[44]")) ;; Special case. (defvar compilation-error-regexp-alist-alist '((aa "a^a" 1 2) (bb "b$b" 3 4))) (define-generic-mode my-mode nil nil '(("1^" bla) ("2^" argl)) '("a" "b++" "c") nil) (defun my-syn-1 () (syntax-propertize-rules ("thing" stuff) ("$1$" bad) ("^2^" alsobad))) (defun my-syn-2 () (syntax-propertize-precompile-rules ("thing" stuff) ("$3$" bad) ("^4^" alsobad))) (defvar my-ts--font-lock-rules '(((a) (:match "[55]" @a)))) (defvar my-ts-mode-font-lock-rules '(((b) (:match "[66]" @b)))) (defun my-bug () `( (,(rx (in "+-/")) ; <- (1 font-lock-keyword-face) (2 font-lock-type-face)) (,(rx (in "+-/")) ; <- . font-lock-constant-face) )) relint-el_2.1+repack/test/8.elisp0000644000175000017500000000144014734334740016620 0ustar dogslegdogsleg;;; Relint test file 8 -*- emacs-lisp -*- (defun test-cl-flet () (looking-at (cl-flet ((f (x) (concat "+" x))) ; "+A" (f "A")))) (defun test-cl-flet* () (looking-at (cl-flet* ((f (x) (concat "+" x))) ; "+B" (f "B")))) (defun test-cl-labels () (looking-at (cl-labels ((f (x) (concat "*" x))) ; "*C" (f "C")))) ;; Safety tests (defun test-cl-flet-safety () (looking-at (concat (cl-flet ((f (x) (concat "+" x))) (eval-when-compile (print "safety failure") (kill-emacs 1)) (f "S")) (cl-flet* ((f (x) (concat "+" x))) (eval-when-compile (print "safety failure") (kill-emacs 1)) (f "S")) (cl-labels ((f (x) (concat "+" x))) (eval-when-compile (print "safety failure") (kill-emacs 1)) (f "S")) ))) relint-el_2.1+repack/test/13.expected0000644000175000017500000000267214734334740017371 0ustar dogslegdogsleg13.elisp:8:14-15: Ineffective string escape `\{' 13.elisp:8:17-18: Ineffective string escape `\}' 13.elisp:8:20-21: Ineffective string escape `\|' 13.elisp:8:23-24: Ineffective string escape `\`' 13.elisp:8:26-27: Ineffective string escape `\?' 13.elisp:8:29-30: Ineffective string escape `\*' 13.elisp:8:32-33: Ineffective string escape `\.' 13.elisp:8:35-36: Ineffective string escape `\+' 13.elisp:8:38-39: Ineffective string escape `\;' 13.elisp:8:41-42: Ineffective string escape `\q' 13.elisp:10:5-6: Ineffective string escape `\m' 13.elisp:11:18-19: Ineffective string escape `\(' 13.elisp:11:21-22: Ineffective string escape `\)' 13.elisp:11:24-25: Ineffective string escape `\[' 13.elisp:11:27-28: Ineffective string escape `\]' 13.elisp:11:30-31: Ineffective string escape `\'' 13.elisp:12:17-18: Ineffective string escape `\;' 13.elisp:18:23-24: In call to re-search-forward: Unescaped literal `$' (pos 0) "$.x++" ^ 13.elisp:18:23-24: Ineffective string escape `\$' 13.elisp:18:25-26: Ineffective string escape `\.' 13.elisp:18:28-29: Ineffective string escape `\+' 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' 13.elisp:22:19-20: Ineffective string escape `\9' relint-el_2.1+repack/test/3.elisp0000644000175000017500000001424414734334740016621 0ustar dogslegdogsleg;;; Relint test file 3 -*- emacs-lisp -*- ;;; Test evaluation of built-in functions (defun test-eval-num () (looking-at (format "%c" (+ (string-to-number (number-to-string ?*)) ; <-- bad regexp (1+ 0) (1- 0) (* 2 2) (- (/ 8 2)) (% 5 3) (- (mod 5 3)) (max 3 4) (min -3 -4) (abs 0) (ash 0 -2) (lsh 0 1) (logand 5 10) (logior 0 0) (logxor 3 3) (- (length "abc") (safe-length '(1 2 3))) (and (= 3 3) (< 2 3) (> 4 3) (/= 1 2) (<= 1 1 2) (>= 2 1 1) 0))))) (defun test-eval-bool () (looking-at (if (and t (not t)) (/ 1 0) (or nil (and (consp '(a)) (atom 'a) (stringp "s") (symbolp 'a) (listp nil) (nlistp 0) (booleanp t) (integerp 3) (numberp -5) (natnump 1) (characterp ?A) (zerop 0) (sequencep nil) (vectorp []) (arrayp []) (eq 'a 'a) (eql 2 2) (equal '(1 2) '(1 2)) (string-equal "ab" "ab") (string= "ab" "ab") (string-lessp "ab" "bc") (string< "ab" "bc") (string-greaterp "bc" "ab") (string> "bc" "ab") (char-equal ?! ?!) (string-match "b" "abc") (string-match-p "b" "abc") (string-prefix-p "A" "AB") (string-suffix-p "B" "AB") (string-blank-p " ") (identity "++")))))) ; <--- bad regexp (defun test-eval-str () (looking-at (substring (concat (format "%s!" "[QQ]") ; 1 <--- bad regexp (string ?a ?b) ; 2 (make-string 3 ?x) ; 3 (symbol-name 'bof) ; 3 (string-to-unibyte (string-to-multibyte "AB")) ; 2 (string-join (split-string "I think") "-") ; 7 (string-trim-left " mm") ; 2 (string-trim-right "nn ") ; 2 (string-trim " kk ") ; 2 (string-remove-prefix "A" "AB") ; 1 (string-remove-suffix "A" "AB") ; 2 (char-to-string (string-to-char "t")) ; 1 (wildcard-to-regexp "az") ; 6 (upcase (downcase (capitalize "zzA"))) ; 3 (combine-and-quote-strings (split-string-and-unquote "oh dear"))) ; 7 0 (- (+ 1 2 3 3 2 7 2 2 2 1 2 1 6 3 7))))) (defun test-eval-list () (looking-at (nth (+ 1 1 2 1 2 1 1 2 2 2 2 2 2 2 2 2 1 1 2 3 3 2 1 4 2 1 2 2) (append (cons 'a nil) ; 1 (cadr '(a (b))) ; 1 (cdar '((b c d))) ; 2 (caar '(((e)))) ; 1 (cddr '(d e f g)) ; 2 (car-safe '((h))) ; 1 (cdr-safe '(g h)) ; 1 (nthcdr 2 '(g h i j)) ; 2 (member "B" '("A" "B" "C")) ; 2 (memq 'k '(j k l)) ; 2 (memql 2 '(1 2 3)) ; 2 (member-ignore-case "d" '("C" "D" "E")) ; 2 (remove "!" '("F" "!" "G" "!")) ; 2 (remq 'a '(m a n a)) ; 2 (assoc "p" '(("p" 1) ("q" 2))) ; 2 (assq 'r '((q 1) (r 2))) ; 2 (car (rassoc 2 '(((u) . 1) ((v) . 2)))) ; 1 (car (rassq 'x '(((w) . x) ((u) . y)))) ; 1 (butlast '(z x y)) ; 2 (number-sequence 1 3) ; 3 (make-list 3 'M) ; 3 (reverse '(9 8)) ; 2 (last '(a b c)) ; 1 (nconc (list 1 2) (list 3 4)) ; 4 (delete 3 (list 1 2 3)) ; 2 (delq 'x (list 'x 'y)) ; 1 (nreverse (list 1 2)) ; 2 (nbutlast (list 1 2 3)) ; 2 (list "^m^"))))) ; <-- bad regexp (defvar my-unknown (list (unknown-fun) "[11]")) ;; Test partial list evaluation: skip elements that cannot be computed (defvar another-bad-regexp-list (append my-unknown (cons "[22]" my-unknown) (cons my-unknown '(my-unknown "[33]")) (purecopy (reverse (list my-unknown "[44]"))) (copy-sequence (remove my-unknown (list my-unknown "[55]"))) (copy-alist (remq my-unknown (list my-unknown "[66]"))) (delete-dups (list my-unknown "[77]" my-unknown "[77]")))) (defun test-eval-cxxxr () (looking-at (concat "+" ; "+abcdefgh" (mapconcat #'symbol-name (list (caaar '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (cdaar '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (cadar '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (cddar '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (caadr '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (cdadr '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (caddr '(((a . b) . (c . d)) . ((e . f) . (g . h)))) (cdddr '(((a . b) . (c . d)) . ((e . f) . (g . h))))) "")))) (defun test-eval-intern () (looking-at (concat "?" ; "?abc" (symbol-name (intern "a")) (symbol-name (intern-soft "b")) (symbol-name (make-symbol "c"))))) (defun test-eval-compare-strings () (looking-at (progn ; "[AA]" (cl-assert 'haha) (and (compare-strings "abc" 0 2 "ABC" 0 2 t) "[AA]")))) ;;; `flatten-tree' appeared in 27.1; disable this part until that Emacs ;;; version has been released. ;;; ;;;(defun test-eval-flatten-tree () ;;; (looking-at ;;; (apply #'concat (flatten-tree '("$" ((("a") "b") ("c"))))))) ; "$abc" relint-el_2.1+repack/test/14.elisp0000644000175000017500000000203514734334740016676 0ustar dogslegdogsleg;;; Relint test file 14 -*- emacs-lisp -*- ;; Test tree-sitter queries. (defun test-treesit-font-lock () (treesit-font-lock-rules :language "+a+" :feature "+b+" '("+c+" ("+d+") (:match "+e+")) :language nil :feature nil `(((f @f) (:match "+f+" @f)) [((g) @g (:match ,(string ?+ ?g ?+) @g))] ((h @h) (:match "h+" @h))))) (defun test-treesit-ranges () (treesit-range-rules :embed "+i+" :host "+j+" '(["+k+"] (("+l+") (:match @l "+m+")) (:match "+n+")) #'ignore :embed nil :host nil `(((o field: _ @o) (:match "+o+" @o)) [((p) @p (:match ,(string ?+ ?p ?+) @p))] ((q field: (_) @q) (:match "q+" @q))))) (treesit-query-expand '(((r) (:match "+r+" @r)))) (treesit-query-compile "+s+" '(((s) @s (:match "s+" @s)) [[((t) @t (:match "+t+" @t))]])) (treesit-node-top-level nil '(((u) (:match "+u+" @u)))) (treesit-query-capture nil '(((v) (:match "+v+" @v)))) (treesit-query-range nil '(((w) (:match "+w+" @w)))) (treesit-query-string nil '(((x) (:match "+x+" @x)))) relint-el_2.1+repack/test/4.expected0000644000175000017500000000371514734334740017310 0ustar dogslegdogsleg4.elisp:10:15: In call to looking-at: Unescaped literal `$' (pos 0) "$%&'()*" ^ 4.elisp:14:15: In call to looking-at: Unescaped literal `+' (pos 0) "+x" ^ 4.elisp:15:15: In call to looking-at: Unescaped literal `*' (pos 0) "*" ^ 4.elisp:16:15: In call to looking-at: Unescaped literal `^' (pos 2) "x-^" ..^ 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" ^ 4.elisp:20:15: In call to looking-at: Unescaped literal `+' (pos 0) "++" ^ 4.elisp:21:15: In call to looking-at: Unescaped literal `$' (pos 0) "$+a" ^ 4.elisp:25:15: In call to looking-at: Unescaped literal `*' (pos 0) "*ab" ^ 4.elisp:26:24-24: In call to looking-at: Unescaped literal `+' (pos 0) "+c" ^ 4.elisp:32:15: In call to looking-at: Unescaped literal `^' (pos 13) ".+ab\\(?:c+\\)d^" ...............^ 4.elisp:43:15: In call to looking-at: Unescaped literal `^' (pos 1) "a^" .^ 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: Unescaped literal `*' (pos 0) "*" ^ 4.elisp:54:15: In call to looking-at: Unescaped literal `*' (pos 0) "*b" ^ 4.elisp:55:15: In call to looking-at: Unescaped literal `*' (pos 0) "*bc" ^ 4.elisp:61:15: In call to looking-at: Unescaped literal `*' (pos 0) "*a" ^ relint-el_2.1+repack/test/7.elisp0000644000175000017500000000164314734334740016624 0ustar dogslegdogsleg;;; Relint test file 7 -*- emacs-lisp -*- (defun my-dolist-fun (seq) (let ((s "")) (dolist (c seq) (setq s (concat s (char-to-string c)))) s)) (defun test-dolist () (looking-at (my-dolist-fun '(?a ?b ?^)))) (defun my-while-fun () (let ((s "") (c ?!)) (while (< c ?&) (setq s (concat s (char-to-string c))) (setq c (1+ c))) s)) (defun test-while () (looking-at (my-while-fun))) (defun test-mapc () (looking-at (let ((s "")) ; "[**]" (mapc (lambda (x) (setq s (concat s x))) '("[" "*" "*" "]")) s))) (defun test-eval-cl-loop () (looking-at ; "!\"#$%" (apply 'string (cl-loop for i in (number-sequence ?! ?%) collect i)))) (defun test-catch () (looking-at (catch 'boing "[XX]"))) (defun test-condition-case () (looking-at (condition-case err "[XX]" (error "Y")))) relint-el_2.1+repack/test/4.elisp0000644000175000017500000000360014734334740016614 0ustar dogslegdogsleg;;; Relint test file 4 -*- emacs-lisp -*- ;; Test user-defined function application. (defun my-char-seq-str (start end) (if (<= start end) (concat (string start) (my-char-seq-str (1+ start) end)) "")) (defun test-fun-call () (looking-at (my-char-seq-str ?$ ?*))) ; "$%&'()*" ;; Test higher-order functions. (defun test-hof () (looking-at (apply 'string '(?+ ?x))) (looking-at (funcall #'string ?*)) (looking-at (mapconcat #'identity '("x" "^") "-")) (looking-at (if (cl-some 'numberp '('a 2 nil)) "[AA]" "")) (looking-at (if (cl-every 'numberp '('a 2 nil)) "" "[BB]")) (looking-at (apply 'string (mapcar (lambda (c) (1+ c)) '(?* ?a)))) (looking-at (apply 'string (mapcan (lambda (c) (list ?+)) '(1 2)))) (looking-at (apply 'string (sort (list ?$ ?+ ?a) #'<)))) ;; Test higher-order functions with keywords. (defun test-hof-kw () (looking-at (cl-reduce #'concat '(?* ?a ?b) :key #'char-to-string)) (looking-at (concat "+" (cl-find "c" '("a" "b" "c") :test #'equal)))) ;; Test rx (defvar my-sub-rx '(one-or-more nonl)) (defun test-rx () (looking-at (concat (rx (eval my-sub-rx) (literal (string ?a ?b)) (regexp (concat "c" "+")) (regex (string ?d))) "^"))) ;; Test macro expansion (defmacro my-macro (x) (list 'string ?a x)) (defun test-macro () (looking-at (my-macro ?^)) (looking-at (when t "b++")) (looking-at (unless nil "c++")) (looking-at (string-join `("a" ,@(list "$") ,"b"))) (looking-at (cl-case 'z (b "m") (z "*")))) ;; Test repeated use of global variable (defconst my-var-a "*") (defconst my-var-b (concat my-var-a "b")) (defun test-var-ref () (looking-at my-var-b) (looking-at (concat my-var-b "c"))) ;; Test global variable redefinition (defconst my-var-a (concat my-var-a "a")) (defun test-var-redef () (looking-at my-var-a)) relint-el_2.1+repack/test/12.elisp0000644000175000017500000000141214734334740016672 0ustar dogslegdogsleg;;; Relint test file 12 -*- emacs-lisp -*- ;; Test auto-mode-alist (define-generic-mode my-mode nil nil nil '(".aa\\'" "\\.bb$" "^cc.*dd")) (add-to-list 'auto-mode-alist '("\\.ee$" . some-mode)) (push '(".ff\\'" . some-mode) auto-mode-alist) (setq auto-mode-alist (cons '(".gg\\'" . some-mode) auto-mode-alist)) (setq auto-mode-alist (append '((".hh\\'" . some-mode) (".ii\\'" . some-mode)) auto-mode-alist)) ;; File-matching regexp functions (defun f12 (d) (directory-files d nil ".txt\\'") (directory-files-and-attributes d nil "\\.pas$") (directory-files-recursively d "^abc") (modify-coding-system-alist 'file "\\.ml$" 'utf-8) (modify-coding-system-alist 'process "+xx$" 'utf-8)) relint-el_2.1+repack/test/12.expected0000644000175000017500000000276314734334740017371 0ustar dogslegdogsleg12.elisp:9:6-6: In define-generic-mode my-mode: Possibly unescaped `.' in file-matching regexp (pos 0) ".aa\\'" ^ 12.elisp:9:20-20: In define-generic-mode my-mode: Use \' instead of $ in file-matching regexp (pos 4) "\\.bb$" .....^ 12.elisp:9:24-24: In define-generic-mode my-mode: Use \` instead of ^ in file-matching regexp (pos 0) "^cc.*dd" ^ 12.elisp:11:39-39: In add-to-list: Use \' instead of $ in file-matching regexp (pos 4) "\\.ee$" .....^ 12.elisp:12:10-10: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".ff\\'" ^ 12.elisp:13:32-32: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".gg\\'" ^ 12.elisp:14:35-35: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".hh\\'" ^ 12.elisp:15:35-35: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".ii\\'" ^ 12.elisp:21:27-27: In call to directory-files: Possibly unescaped `.' in file-matching regexp (pos 0) ".txt\\'" ^ 12.elisp:22:48-48: In call to directory-files-and-attributes: Use \' instead of $ in file-matching regexp (pos 5) "\\.pas$" ......^ 12.elisp:23:35-35: In call to directory-files-recursively: Use \` instead of ^ in file-matching regexp (pos 0) "^abc" ^ 12.elisp:24:43-43: In call to modify-coding-system-alist: Use \' instead of $ in file-matching regexp (pos 4) "\\.ml$" .....^ 12.elisp:25:41-41: In call to modify-coding-system-alist: Unescaped literal `+' (pos 0) "+xx$" ^ relint-el_2.1+repack/test/2.expected0000644000175000017500000002272314734334740017306 0ustar dogslegdogsleg2.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" ^ 2.elisp:31:42-42: In call to directory-files-and-attributes: Unescaped literal `+' (pos 0) "+2" ^ 2.elisp:32:35-35: In call to directory-files-recursively: Unescaped literal `+' (pos 0) "+3" ^ 2.elisp:33:27-27: In call to delete-matching-lines: Unescaped literal `+' (pos 0) "+4" ^ 2.elisp:34:31-31: In call to delete-non-matching-lines: Unescaped literal `+' (pos 0) "+5" ^ 2.elisp:35:19-19: In call to count-matches: Unescaped literal `+' (pos 0) "+6" ^ 2.elisp:36:34-34: In call to treesit-induce-sparse-tree: Unescaped literal `+' (pos 0) "+7" ^ 2.elisp:37:30-30: In call to treesit-search-forward: Unescaped literal `+' (pos 0) "+8" ^ 2.elisp:38:35-35: In call to treesit-search-forward-goto: Unescaped literal `+' (pos 0) "+9" ^ 2.elisp:39:30-30: In call to treesit-search-subtree: Unescaped literal `+' (pos 0) "+10" ^ 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" ...^ 2.elisp:70:35-37: In call to sort-regexp-fields: Escaped non-special character `%' (pos 0..1) "\\%" ^^^ relint-el_2.1+repack/test/10.expected0000644000175000017500000000351314734334740017361 0ustar dogslegdogsleg10.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]" .^ relint-el_2.1+repack/test/1.expected0000644000175000017500000001635214734334740017306 0ustar dogslegdogsleg1.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" ^ 1.elisp:12:30-30: In bad-regexes: Unescaped literal `+' (pos 0) "+b" ^ 1.elisp:13:34-34: In bad-regexp-list: Unescaped literal `+' (pos 0) "+c" ^ 1.elisp:14:33-33: In bad-regex-list: Unescaped literal `+' (pos 0) "+d" ^ 1.elisp:15:30-30: In bad-re-list: Unescaped literal `+' (pos 0) "+e" ^ 1.elisp:17:36-36: In bad-regexp-alist: Unescaped literal `*' (pos 0) "**" ^ 1.elisp:17:43-43: In bad-regexp-alist: Unescaped literal `?' (pos 0) "??" ^ 1.elisp:17:55-55: In bad-regexp-alist: Unescaped literal `^' (pos 1) ".^" .^ 1.elisp:17:61-61: In bad-regexp-alist: Unescaped literal `$' (pos 0) "$." ^ 1.elisp:18:35-35: In bad-regex-alist: Unescaped literal `*' (pos 0) "**" ^ 1.elisp:18:42-42: In bad-regex-alist: Unescaped literal `?' (pos 0) "??" ^ 1.elisp:18:54-54: In bad-regex-alist: Unescaped literal `^' (pos 1) ".^" .^ 1.elisp:18:60-60: In bad-regex-alist: Unescaped literal `$' (pos 0) "$." ^ 1.elisp:19:32-32: In bad-re-alist: Unescaped literal `*' (pos 0) "**" ^ 1.elisp:19:39-39: In bad-re-alist: Unescaped literal `?' (pos 0) "??" ^ 1.elisp:19:51-51: In bad-re-alist: Unescaped literal `^' (pos 1) ".^" .^ 1.elisp:19:57-57: In bad-re-alist: Unescaped literal `$' (pos 0) "$." ^ 1.elisp:20:37-37: In bad-pattern-alist: Unescaped literal `*' (pos 0) "**" ^ 1.elisp:20:44-44: In bad-pattern-alist: Unescaped literal `?' (pos 0) "??" ^ 1.elisp:20:56-56: In bad-pattern-alist: Unescaped literal `^' (pos 1) ".^" .^ 1.elisp:20:62-62: In bad-pattern-alist: Unescaped literal `$' (pos 0) "$." ^ 1.elisp:22:41-41: In bad-mode-alist: Unescaped literal `?' (pos 0) "??" ^ 1.elisp:22:53-53: In bad-mode-alist: Unescaped literal `^' (pos 1) ".^" .^ 1.elisp:26:40-40: In bad-rules-list (eins): Unescaped literal `$' (pos 0) "$$" ^ 1.elisp:29:41-43: In bad-rules-list (zwei): Reversed range `a-Z' matches nothing (pos 1..3) "[a-Z]" .^^^ 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+" ^ 1.elisp:57:9: In bad-custom-3-regexp: Unescaped literal `*' (pos 0) "*b*" ^ 1.elisp:57:9: In bad-custom-3-regexp: Unescaped literal `^' (pos 2) "^c^" ..^ 1.elisp:64:19-19: In bad-custom-4-regexp: Unescaped literal `+' (pos 0) "+b" ^ 1.elisp:65:44-44: In bad-custom-5: Unescaped literal `^' (pos 2) "^x^" ..^ 1.elisp:69:55-61: error: In bad-custom-6: No character class `[:bah:]' (pos 1..7) "[[:bah:]]" .^^^^^^^ 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" .^ 1.elisp:94:11-11: In compilation-error-regexp-alist-alist (bb): Unescaped literal `$' (pos 1) "b$b" .^ 1.elisp:99:8-8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) "1^" .^ 1.elisp:100:8-8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) "2^" .^ 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$" ^ 1.elisp:108:8-8: In call to syntax-propertize-rules: Unescaped literal `^' (pos 2) "^2^" ..^ 1.elisp:113:6-6: In call to syntax-propertize-precompile-rules: Unescaped literal `$' (pos 0) "$3$" ^ 1.elisp:114:8-8: In call to syntax-propertize-precompile-rules: Unescaped literal `^' (pos 2) "^4^" ..^ 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) "+-/" ^^^ 1.elisp:124:16-18: Suspect range `+-/' (pos 0..2) "+-/" ^^^ relint-el_2.1+repack/test/5.expected0000644000175000017500000000325214734334740017305 0ustar dogslegdogsleg5.elisp:5:15: In call to looking-at: Unescaped literal `^' (pos 1) "a^" .^ 5.elisp:16:19: In call to looking-at: Unescaped literal `^' (pos 1) "A^" .^ 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" ^ 5.elisp:42:17: In call to looking-at: Unescaped literal `^' (pos 1) "b^" .^ 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" ^ 5.elisp:67:15: In call to looking-at: Unescaped literal `+' (pos 0) "+vu" ^ 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]" .^ relint-el_2.1+repack/test/7.expected0000644000175000017500000000141714734334740017310 0ustar dogslegdogsleg7.elisp:10:15: In call to looking-at: Unescaped literal `^' (pos 2) "ab^" ..^ 7.elisp:21:15: In call to looking-at: Unescaped literal `$' (pos 3) "!\"#$%" ....^ 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]" .^ relint-el_2.1+repack/test/14.expected0000644000175000017500000000166114734334740017367 0ustar dogslegdogsleg14.elisp:13:23-23: In call to treesit-font-lock-rules: Unescaped literal `+' (pos 0) "+f+" ^ 14.elisp:14:6: In call to treesit-font-lock-rules: Unescaped literal `+' (pos 0) "+g+" ^ 14.elisp:29:32-32: In call to treesit-range-rules: Unescaped literal `+' (pos 0) "+o+" ^ 14.elisp:30:6: In call to treesit-range-rules: Unescaped literal `+' (pos 0) "+p+" ^ 14.elisp:33:39-39: In call to treesit-query-expand: Unescaped literal `+' (pos 0) "+r+" ^ 14.elisp:37:4: In call to treesit-query-compile: Unescaped literal `+' (pos 0) "+t+" ^ 14.elisp:39:45-45: In call to treesit-node-top-level: Unescaped literal `+' (pos 0) "+u+" ^ 14.elisp:40:44-44: In call to treesit-query-capture: Unescaped literal `+' (pos 0) "+v+" ^ 14.elisp:41:42-42: In call to treesit-query-range: Unescaped literal `+' (pos 0) "+w+" ^ 14.elisp:42:43-43: In call to treesit-query-string: Unescaped literal `+' (pos 0) "+x+" ^ relint-el_2.1+repack/test/9.elisp0000644000175000017500000000374414734334740016632 0ustar dogslegdogsleg;;; Relint test file 9 -*- emacs-lisp -*- ;; Test mutation/binding of known variables for detecting regexps. (defun test-9 () (setq-local page-delimiter "[aa]") (setq-local paragraph-separate "[bb]") (setq-local paragraph-start "[cc]") (setq-local sentence-end "[dd]") (setq-local comment-start-skip "[ee]") (setq-local comment-end-skip "[ff]") (setq sentence-end "[gg]") (set (make-local-variable 'paragraph-start) "[hh]") (let ((paragraph-separate "[ii]") (page-delimiter "[jj]")) (let* ((comment-start-skip "[kk]") (comment-end-skip "[ll]")) (asdf))) (setq treesit-sentence-type-regexp "+0") (setq treesit-sexp-type-regexp "+1")) (defvar bad-imenu '(("*less*" "" 0) (nil "+b+" 0))) (defvar bad-treesit-imenu '((nil "node" nil nil) ("+++" "+c+" nil nil))) (defun test-9-ge () (setq-local imenu-generic-expression '((nil "oh" 0) ("*more*" "+a+" 0))) (setq imenu-generic-expression bad-imenu) (setq-local treesit-simple-imenu-settings bad-treesit-imenu) (set (make-local-variable 'treesit-simple-imenu-settings) '((nil "type" nil nil) ("+more+" "+d+" nil nil)))) (defun test-9-fl-kw () (setq-local font-lock-keywords '(("[mm]" . tag))) (setq font-lock-keywords '(("[nn]" . tag))) (set (make-local-variable 'font-lock-keywords) '(("[oo]" . tag)))) (defconst my-fl-keyw-1 '(("[pp]" . alpha))) (defconst my-font-lock-keywords-2 '(("[qq]" . beta))) (defun test-9-fl-def () (setq font-lock-defaults '((my-fl-keyw-1 my-font-lock-keywords-2) moo mooo))) (defun test-9-ts-indent () (setq treesit-simple-indent-rules '((a ((match "+e+" "+f+" "+g+") a 0) ((n-p-gp "+h+" "+i+" "+j+") a 0)) (b ((parent-is "+k+") b 0) ((node-is "+l+") b 0) ((field-is "+m+") b 0))))) (defun test-9-ts-defun () (setq treesit-defun-type-regexp "+n+") (setq treesit-defun-type-regexp (cons "+o+" #'ignore))) relint-el_2.1+repack/test/3.expected0000644000175000017500000000423314734334740017303 0ustar dogslegdogsleg3.elisp:7:4: In call to looking-at: Unescaped literal `*' (pos 0) "*" ^ 3.elisp:35:4: In call to looking-at: Unescaped literal `+' (pos 0) "++" ^ 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" ^ 3.elisp:157:13-13: In call to looking-at: Unescaped literal `?' (pos 0) "?abc" ^ 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]" .^ relint-el_2.1+repack/test/6.expected0000644000175000017500000000416214734334740017307 0ustar dogslegdogsleg6.elisp:11:19: Value from `some-regexp' cannot be spliced into `[...]' 6.elisp:12:22: Value from `regexp-quote' cannot be spliced into `[...]' 6.elisp:13:16: Value from `regexp-opt' cannot be spliced into `[...]' 6.elisp:14:16: Value from `rx' cannot be spliced into `[...]' 6.elisp:15:16: Value from `rx-to-string' cannot be spliced into `[...]' 6.elisp:16:32: Value from `some-regex' cannot be spliced into `[...]' 6.elisp:17:19: Value from `regexp-quote' cannot be spliced into `[...]' 6.elisp:18:19: Value from `regexp-opt' cannot be spliced into `[...]' 6.elisp:19:19: Value from `rx' cannot be spliced into `[...]' 6.elisp:20:19: Value from `rx-to-string' cannot be spliced into `[...]' 6.elisp:23:19: Value from `regexp-quote' cannot be spliced into `[...]' 6.elisp:28:24-28: In call to skip-chars-forward: Suspect skip set framed in `[...]' (pos 0..4) "[a-z]" ^^^^^ 6.elisp:29:26-26: In call to skip-chars-backward: Duplicated character `a' (pos 1) "aa" .^ 6.elisp:30:23: `some-re' cannot be used for arguments to `skip-chars-forward' 6.elisp:31:24: `regexp-quote' cannot be used for arguments to `skip-chars-backward' 6.elisp:32:23: `regexp-opt' cannot be used for arguments to `skip-chars-forward' 6.elisp:33:24: `rx' cannot be used for arguments to `skip-chars-backward' 6.elisp:34:23: `rx-to-string' cannot be used for arguments to `skip-chars-forward' 6.elisp:38:27-27: In call to skip-syntax-forward: Invalid char `s' in syntax string (pos 1) "\\s-" ..^ 6.elisp:39:27-27: In call to skip-syntax-backward: Duplicated syntax code ` ' (pos 1) "- " .^ 6.elisp:40:24: `some-re' cannot be used for arguments to `skip-syntax-forward' 6.elisp:41:25: `regexp-quote' cannot be used for arguments to `skip-syntax-backward' 6.elisp:42:24: `regexp-opt' cannot be used for arguments to `skip-syntax-forward' 6.elisp:43:25: `rx' cannot be used for arguments to `skip-syntax-backward' 6.elisp:44:24: `rx-to-string' cannot be used for arguments to `skip-syntax-forward' 6.elisp:68:23: `make-a-nice-regexp' cannot be used for arguments to `skip-chars-forward' 6.elisp:69:24: `make-something' cannot be used for arguments to `skip-chars-backward' relint-el_2.1+repack/test/6.elisp0000644000175000017500000000412114734334740016615 0ustar dogslegdogsleg;;; Relint test file 6 -*- emacs-lisp -*- ;; Test mixup of regexp and character alternative (defun myrefun (things) (if (listp things) (regexp-opt things) things)) (defun test-mixup (x y z) (list (concat "a*[^" some-regexp "]") (concat stuff "[" (regexp-quote x) "]" stuff) (concat "[" (myrefun y) "]") (concat "[" (rx ?a "b" nonl) "]") (concat "[" (rx-to-string z) "]") (format "^%s[%s]*%s$" stuff some-regex stuff) (format "[%s]" (regexp-quote x)) (format "[%s]" (myrefun y)) (format "[%s]" (rx ?a "b" nonl)) (format "[%s]" (rx-to-string z)) (format "[%s]" (f (string-match (regexp-quote x) a))) ; ok (format "[%s]" (f (replace-regexp-in-string (regexp-quote x) a b))) ; ok (format "[%s]" (f (replace-regexp-in-string a b (regexp-quote x)))) ; bad )) ;; Test skip-chars (defun test-skip-chars (x y z) (skip-chars-forward "[a-z]") (skip-chars-backward "aa") (skip-chars-forward some-re) (skip-chars-backward (regexp-quote x)) (skip-chars-forward (myrefun y)) (skip-chars-backward (rx "abc")) (skip-chars-forward (rx-to-string z))) ;; Test skip-syntax (defun test-skip-syntax (x y z) (skip-syntax-forward "\\s-") (skip-syntax-backward "- ") (skip-syntax-forward some-re) (skip-syntax-backward (regexp-quote x)) (skip-syntax-forward (myrefun y)) (skip-syntax-backward (rx "w-")) (skip-syntax-forward (rx-to-string z))) ;; Test incorrect provenance tracing (defun test-regexp-in-condition (x y z) (format "[%s]" (if (regexp-quote x) y z)) (skip-chars-forward (when some-regexp y)) (skip-syntax-backward (unless (myrefun x) y)) (skip-chars-backward (while (myrefun x) z))) ;; Test suppression (defun test-suppression () ;; relint suppression: Unescaped literal .\$ ;; relint suppression: Duplicated .a (looking-at "$[aa]")) ;; Test user-defined regexp-generating functions (defun make-a-nice-regexp () (stuff)) (defun make-something () "Return a regexp made from whole cloth." (stuff)) (defun test-user-defined-generator () (skip-chars-forward (make-a-nice-regexp)) (skip-chars-backward (make-something))) relint-el_2.1+repack/test/5.elisp0000644000175000017500000000434314734334740016622 0ustar dogslegdogsleg;;; Relint test file 5 -*- emacs-lisp -*- ;; Test let bindings (defun test-let-inside (x y) (looking-at (let ((x "a") (y "b")) (let* ((y "^") (z (concat x y))) z)))) (defun test-let-outside (x y) (let ((x "A") (y "B")) (let* ((y "^") (z (concat x y))) (looking-at z)))) ;; Test setq (defun test-setq-inside (x) (looking-at (progn (let ((y "A") (z "B")) (setq z "A") (concat "[" y z "]"))))) (defun test-setq-outside (x c) (setq x "[") (let ((y "B") (z "M")) (setq z "B") (looking-at (concat x y z "]")))) (defun test-push (x) (let ((x (list "a"))) (push "+" x) (looking-at (string-join x)))) (defun test-pop (x) (let ((x (list "a" "b" "^"))) (pop x) (looking-at (string-join x)))) (defun test-setq-defun (x) (setq x "[CC]") (looking-at x)) (defun test-setq-lambda () (lambda (y) (setq y "[DD]") (looking-at y))) (defun f1 (x) (let ((y "D")) (setq x "E" y "E") (concat x y))) (defun test-setq-inside-fun () (looking-at (concat "[" (f1 "C") "]"))) (defun test-push-inside () (looking-at (let ((x (list "b"))) (push "*" x) (string-join x)))) (defun test-pop-inside () (looking-at (let* ((x (list "u" "+" "v")) (y (pop x))) (string-join (append x (list y)))))) (defun test-prog1 () (looking-at (prog1 "[UU]" "a" "b" "c"))) (defun test-prog2 () (looking-at (prog2 "a" "[VV]" "b" "c"))) ;; Don't complain (just skip) the following. (defconst my-circle '(#1=a . #1#)) ;; Make sure we don't enter infinite recursion when checking this one. (defalias 'test-recursive #'(lambda (x) (if x (test-recursive (cdr x)) 'ok))) ;; Check for alias loops. (defalias 'test-recursive-0 'test-recursive-0) (test-recursive-0 'z) (defalias 'test-recursive-1 'test-recursive-2) (defalias 'test-recursive-2 'test-recursive-1) (test-recursive-1 'a) (test-recursive-2 'b) (defalias 'test-recursive-3 'test-recursive-4) (defalias 'test-recursive-4 'test-recursive-5) (defalias 'test-recursive-5 'test-recursive-3) (test-recursive-3 'c) (test-recursive-4 'd) (test-recursive-3 'e) relint-el_2.1+repack/test/2.elisp0000644000175000017500000000453714734334740016624 0ustar dogslegdogsleg;;; Relint test file 2 -*- emacs-lisp -*- ;; Test regexp detection in arguments of known regexp-detecting functions. (defun f1 (s) (looking-at "[aa]") (re-search-forward "[bb]") (re-search-backward "[cc]") (search-forward-regexp "[BB]") (search-forward-regexp "[CC]") (string-match "[dd]" s) (string-match-p "[ee]" s) (looking-at-p "[ff]") (looking-back "[gg]" nil) (replace-regexp-in-string "[hh]" "x" s) (query-replace-regexp "[jj]" s) (posix-looking-at "[kk]") (posix-search-backward "[ll]") (posix-search-forward "[mm]") (posix-string-match "[nn]" s) (load-history-filename-element "[oo]") (kill-matching-buffers "[pp]") (keep-lines "[qq]") (flush-lines "[rr]") (how-many "[ss]") (split-string s "[tt]" nil "[uu]") (split-string-and-unquote s "[vv]") (string-trim-left s "[ww]") (string-trim-right s "[xx]") (string-trim s "[yy]" "[zz]") (directory-files s nil "+1") (directory-files-and-attributes s nil "+2") (directory-files-recursively s "+3") (delete-matching-lines "+4") (delete-non-matching-lines "+5") (count-matches "+6") (treesit-induce-sparse-tree n "+7") (treesit-search-forward n "+8") (treesit-search-forward-goto n "+9") (treesit-search-subtree n "+10")) ;; Test argument names as means of detecting regexps. (defun f2 (x1 my-regexp x2 my-regex x3 my-re x4 my-pattern x5 re) (list x1 my-regexp x1 my-regex x3 my-re x4 my-pattern x5 re)) (defsubst s2 (x1 my-regexp x2 my-regex x3 my-re x4 my-pattern x5 re) (list x1 my-regexp x1 my-regex x3 my-re x4 my-pattern x5 re)) (defmacro m2 (x1 my-regexp x2 my-regex x3 my-re x4 my-pattern x5 re) (list 'quote (list x1 my-regexp x1 my-regex x3 my-re x4 my-pattern x5 re))) (defun f4 (s) (f2 "[AA]" "[BB]" "[CC]" "[DD]" "[EE]" "[FF]" "[GG]" "[HH]" "[II]" "[JJ]") (s2 "[AA]" "[BB]" "[CC]" "[DD]" "[EE]" "[FF]" "[GG]" "[HH]" "[II]" "[JJ]") (m2 "[AA]" "[BB]" "[CC]" "[DD]" "[EE]" "[FF]" "[GG]" "[HH]" "[II]" "[JJ]")) ;; Test function doc string as means of detecting regexps. (defun f5 (a b c d e) "Chew regular expression B, regexp C and regex D." (list a b c d e)) (defun f6 () (f5 "[aa]" "[bb]" "[cc]" "[dd]" "[ee]")) (defun f7 () (alpha beta :regexp "[11]") (gamma :regex "[22]" delta)) (defun f8 () (sort-regexp-fields nil "^.*$x" "\\&" (point-min) (point-max)) (sort-regexp-fields nil "^.*$" "\\%" (point-min) (point-max))) relint-el_2.1+repack/test/11.expected0000644000175000017500000000416214734334740017363 0ustar dogslegdogsleg11.elisp:7:23-23: Duplicated character `c' (pos 2) "abc" ..^ 11.elisp:7:26: Duplicated character `b' 11.elisp:8:30-30: Single-character range `c-c' (pos 4) "0-9ac-ceg-h3" ....^ 11.elisp:8:34-36: Two-character range `g-h' (pos 8..10) "0-9ac-ceg-h3" ........^^^ 11.elisp:8:37-37: Character `3' included in range `0-9' (pos 11) "0-9ac-ceg-h3" ...........^ 11.elisp:9:25: Range `f-t' overlaps previous `a-m' 11.elisp:9:37-37: Character `s' included in range `f-t' (pos 1) "!s" .^ 11.elisp:10:45: Duplicated class `space' 11.elisp:11:21-23: Suspect range `)-+' (pos 4..6) "0-9()-+" ....^^^ 11.elisp:12:20-22: Suspect range `+-.' (pos 3..5) "0-9+-." ...^^^ 11.elisp:15:20-20: Literal `-' not first or last (pos 3) "A-F-K-T" ...^ 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" .^ 11.elisp:20:29-29: Duplicated character `A' (pos 1) "AA" .^ 11.elisp:21:19: Duplicated character `B' (pos 1) "BB" .^ 11.elisp:23:5: Duplicated character `C' (pos 1) "CC" .^ 11.elisp:24:5: Duplicated character `D' (pos 1) "DD" .^ 11.elisp:26:7: Duplicated character `z' 11.elisp:26:7: Duplicated character `y' 11.elisp:31:25-28: Character `\177' included in range `\000-\177' (pos 0) "\177" ^^^^ 11.elisp:31:32-35: Character `\240' included in range `\200-\377' (pos 0) "\240" ^^^^ 11.elisp:33:18: Character `m' included in range `a-z' 11.elisp:34:19-27: Range `\000-\377' overlaps previous `a-f' (pos 0..2) "\000-\377" ^^^^^^^^^ 11.elisp:35:25-33: Range `\000-\377' overlaps previous `\240-\277' (pos 0..2) "\000-\377" ^^^^^^^^^ 11.elisp:37:17: Duplicated rx form in or-pattern: ?A 11.elisp:37:20: Duplicated rx form in or-pattern: "def" 11.elisp:38:10: Duplicated rx form in or-pattern: "abc" 11.elisp:38:16: Duplicated rx form in or-pattern: (= 3 ?*) relint-el_2.1+repack/NEWS0000644000175000017500000001076214734467771015155 0ustar dogslegdogsleg relint version history ====================== Version 2.1 - Robustness fixes - Summary now counts unsuppressed problems Version 2.0 - Compatibility break: `relint-buffer` now returns a list of `relint-diag` objects. Use the `relint-diag-` accessors for reading their slots. Each object now has BEGIN..END ranges instead of just the starting point, which allows a user interface to highlight the corresponding part of the code buffer in a suitable way. There are now the severity levels `error`, `warning` and `info`. - When running interactively (`relint-directory`, `relint-file` or `relint-current-buffer`), the new `relint-buffer-highlight` face is used for relevant parts of a string in the `*relint*` buffer. This face can be customised or themed by the user. - In batch mode (`relint-batch`), the new variable `relint-batch-highlight` is used to determine how relevant parts of a string are marked in the output. The default is to use reverse video for terminal display. This variable can be customised by the user. - Some performance improvements - Requires xr 2.0 and Emacs 27.1 or later Version 1.24 - Fix a `next-error' bug - Some performance improvements Version 1.23 - New defcustom `relint-xr-checks' that enables optional xr checks. - Add regexp detection in uses of the treesit API. - Better backquote expansion inside rx forms. Version 1.22 - String char escape check now detects \8, \9, and \x without hex digit Version 1.21 - Check for duplicates in rx or-forms - Robustness improvements Version 1.20 - More compact distribution Version 1.19 - Progress indicator in `relint-directory' - Some performance improvements - Fix some false positives in the regexp provenance detector - Scan assignments to `font-lock-defaults' correctly - Recognise regexp arguments to functions in the s.el package Version 1.18 - New check for ineffective backslashes in all strings (not just regexps) - Warnings emitted in order of their position in file or buffer - Performance improvements Version 1.17 - Fixed message display on Emacs 26 Version 1.16 - Suppression comments now use regexp matching of messages - New filename-specific checks in calls to `directory-files' etc - Check some keyword arguments (:regexp and :regex) - Improved rx checks - `relint-directory' now displays number of files found Version 1.15 - Improved position accuracy in various lists of regexps - Check for mistake in rx `any' forms - `relint-buffer' now also returns severity (warning, error) - Relint can now also check the *scratch* buffer Version 1.14 - Added `relint-buffer' - Report error position inside string literals when possible - Scan arguments to `search-forward-regexp' and `search-backward-regexp' - Use text quoting for messages Version 1.13 - Look in function/macro doc strings to find regexp arguments and return values - Detect binding and mutation of some known regexp variables - Detect regexps as arguments to `syntax-propertize-rules' - More font-lock-keywords variables are scanned for regexps - `relint-batch' no longer outputs a summary if there were no errors Version 1.12 - Improved detection of regexps in defcustom declarations - Better suppression of false positives - Nonzero exit status upon error in `relint-batch' Version 1.11 - Improved evaluator, now handling limited local variable mutation - Bug fixes - Test suite Version 1.10 - Check arguments to `skip-syntax-forward' and `skip-syntax-backward' - Add error suppression mechanism Version 1.9 - Limited tracking of local variables in regexp finding - Recognise new variable `literal' and `regexp' rx clauses - Detect more regexps in defcustom declarations - Requires xr 1.13 Version 1.8 - Updated diagnostics list - Requires xr 1.12 Version 1.7 - Expanded regexp-generating heuristics - Some `defalias' are now followed - All diagnostics are now documented (see README.org) Version 1.6 - Add `relint-current-buffer' - Show relative file names in *relint* - Extended regexp-generating heuristics, warning about suspiciously-named variables used as skip-sets - "-patterns" and "-pattern-list" are no longer interesting variable suffixes Version 1.5 - Substantially improved evaluator, able to evaluate some functions and macros defined in the same file, even when passed as parameters - Detect regexps spliced into [...] - Check bad skip-set provenance - The *relint* buffer now uses a new relint-mode for better usability, with "g" bound to `relint-again' Version 1.4 - First version after name change to `relint' relint-el_2.1+repack/relint-test.el0000644000175000017500000002457314734334740017244 0ustar dogslegdogsleg;;; relint-test.el --- Tests for relint.el -*- lexical-binding: t -*- ;; Copyright (C) 2019-2020 Free Software Foundation, Inc. ;; Author: Mattias Engdegård ;; 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 . (require 'relint) (require 'ert) (require 'cl-lib) ;; Required for some of the source in test/ (require 'subr-x) (defconst relint-test--this-file (or load-file-name buffer-file-name)) ;; A nonsense test value to exercise the location code. (defconst relint-test--value '(t1 t2 13 -7 14.0 1.0e+INF "ab\"\\" ?a ?? ?\\ ?\( ?\) [xy 4 uv] (t3 (((t4) t5) (t6)) ('t7 't8 '(t9 () (())))) (r . s) (t10 t11 . t12) #'zz '''qqq `(t13 ,t14 ,(t15 t16) ,@t17 ,@(t18 t19)))) (defun relint-test--enumerate-nodes (form path) "List of (NODE . PATH-TO-NODE) for each node in FORM, starting at PATH. A node is either an atom or a list, but not a proper tail of a list." (if (consp form) (let ((r (list (cons form path))) (i 0)) (while (consp form) (let ((node (car form))) (setq r (append (reverse (relint-test--enumerate-nodes node (cons i path))) r)) (setq i (1+ i)) (setq form (cdr form)))) (reverse r)) (list (cons form path)))) (defun relint-test--find-toplevel-form (pred) "Find first toplevel form satisfying PRED. Return (FORM . POSITION)." (catch 'found (while t (let* ((pos (point)) (form (read (current-buffer)))) (when (funcall pred form) (throw 'found (cons form pos))))))) (ert-deftest relint-find-pos () "Test the mechanism that computes a position from a toplevel-position and a path." (with-temp-buffer (emacs-lisp-mode) (insert-file-contents relint-test--this-file) (let* ((form-pos (relint-test--find-toplevel-form (lambda (x) (pcase x (`(defconst relint-test--value . ,_) t))))) (toplevel-form (car form-pos)) (toplevel-pos (cdr form-pos))) ;; We have a toplevel form and position. Enumerate its parts. (dolist (item (relint-test--enumerate-nodes toplevel-form nil)) (let* ((node (car item)) (path (cdr item)) (pos (relint--pos-from-start-pos-path toplevel-pos path))) ;; Skip sugared items; they cannot be read in isolation. (unless (memq node '(quote function \` \, \,@)) (goto-char pos) ;; The position should not be at whitespace or comment. (should-not (looking-at (rx (any " \t\n;")))) ;; Read what we find at the position to check. (let ((thing-here (read (current-buffer)))) (should (equal thing-here node))))))))) (defun relint-test--insert-file (file) (insert-file-contents (expand-file-name file (file-name-directory relint-test--this-file)))) (defun relint-test--scan-file (file) "Scan FILE and return the results as a string." (with-temp-buffer ;; The reference files (*.expected) are kept in the `grave' style, ;; to make the test independent of `text-quoting-style'. (let ((text-quoting-style 'grave) (relint--force-batch-output t) (relint-batch-highlight 'caret)) (relint--buffer (find-file-noselect file t) (current-buffer) t)) (buffer-string))) (defun relint-test--read-file (file) (with-temp-buffer (relint-test--insert-file file) (buffer-string))) ;; The scan tests are divided more-or-less arbitarily into chunks ;; instead of having one big file, to make it easier to find errors. (defmacro relint-test--deftest (basename) (let* ((testfile (concat "test/" basename ".elisp")) (expected (concat "test/" basename ".expected")) (name (intern (concat "relint-check-file-" basename)))) `(ert-deftest ,name () (should (equal (relint-test--scan-file ,testfile) (relint-test--read-file ,expected)))))) (dolist (f (directory-files (concat (file-name-directory relint-test--this-file) "/test") nil (rx ".elisp" eos))) (let ((base (string-remove-suffix ".elisp" f))) (eval `(relint-test--deftest ,base)))) (ert-deftest relint-buffer () (let ((buf (get-buffer-create " *relint-test*")) (text-quoting-style 'grave)) (unwind-protect (progn (with-current-buffer buf (emacs-lisp-mode) (insert ";hello\n(looking-at \"broken**regexp\")\n") (insert "(looking-at (make-string 2 ?^))\n") (insert "(looking-at (concat \"ab\" \"cdef\" \"[gg]\"))\n") (insert "(string-match \"[xy\" s)\n")) (let ((diags (relint-buffer buf))) (should (equal diags '(["In call to looking-at: Repetition of repetition" 28 28 string "broken**regexp" 7 7 warning] ["This is the inner expression" 26 27 string "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" 105 105 string "abcdef[gg]" 8 8 warning] ["Previous occurrence here" 104 104 string "abcdef[gg]" 7 7 info] ["In call to string-match: Unterminated character alternative" 126 128 string "[xy" 0 2 error]))) ;; test accessors (let ((d (nth 1 diags))) (should (equal (relint-diag-message d) "This is the inner expression")) (should (equal (relint-diag-beg-pos d) 26)) (should (equal (relint-diag-end-pos d) 27)) (should (equal (relint-diag-pos-type d) 'string)) (should (equal (relint-diag-string d) "broken**regexp")) (should (equal (relint-diag-beg-idx d) 5)) (should (equal (relint-diag-end-idx d) 6)) (should (equal (relint-diag-severity d) 'info))))) (kill-buffer buf)))) (ert-deftest relint-buffer-huge () ;; Regression test for regexp stack overflow in the scan for ineffective ;; backslashes. (should (equal (with-temp-buffer (emacs-lisp-mode) (insert "(message \"hello\\? anyone?\")\n") (insert "(defconst my-const '(") (dotimes (i 200000) (insert (format "%d " i))) (insert "))\n") (insert "(message \"goodbye\\! everyone!\")\n") (let ((text-quoting-style 'grave)) (relint-buffer (current-buffer)))) '(["Ineffective string escape `\\?'" 16 17 nil nil nil nil warning] ["Ineffective string escape `\\!'" 1288960 1288961 nil nil nil nil warning])))) (ert-deftest relint-bad-hex-escape () ;; Test the bad \x character escape warning. We do this separately because ;; it signals an error in newer Emacs. (let ((buf (get-buffer-create " *relint-test*")) (text-quoting-style 'grave)) (unwind-protect (progn (with-current-buffer buf (emacs-lisp-mode) (insert "(print \"c \\xf \\xg \\x d\")\n")) (let ((diags (relint-buffer buf))) (should (equal ;; Ignore 'invalid escape char syntax' error. (cl-remove-if (lambda (d) (eq (relint-diag-severity d) 'error)) diags) '(["Character escape `\\x' not followed by hex digit" 15 16 nil nil nil nil warning] ["Character escape `\\x' not followed by hex digit" 19 20 nil nil nil nil warning] ))))) (kill-buffer buf)))) (ert-deftest relint-xr-checks () ;; Test optional checks enabled with `relint-xr-checks'. (dolist (checks '(nil all)) (let* ((relint-xr-checks checks) (warnings (with-temp-buffer (emacs-lisp-mode) (insert (format "(looking-at %S)\n" "\\(:?xy\\)+")) (let ((text-quoting-style 'grave)) (relint-buffer (current-buffer))))) (msg "In call to looking-at: Possibly mistyped `:?' at start of group")) (should (equal warnings (and checks `([,msg 17 18 string "\\(:?xy\\)+" 2 3 warning]))))))) (defun relint-test--batch (prog) (with-temp-buffer (let ((errbuf (current-buffer))) (let ((progbuf (get-buffer-create "relint--test.el"))) (unwind-protect (with-current-buffer progbuf (insert prog) (emacs-lisp-mode) (let ((text-quoting-style 'grave) (relint--force-batch-output t)) (relint--buffer (current-buffer) errbuf t))) (kill-buffer progbuf))) (buffer-string)))) (ert-deftest relint-batch-highlight () (let ((prog "(looking-at \"[pqrf-az]\")\n") (msg (concat "relint--test.el:1:18-20: " "In call to looking-at: " "Reversed range `f-a' matches nothing (pos 4..6)\n"))) (let ((relint-batch-highlight nil)) (should (equal (relint-test--batch prog) (concat msg " \"[pqrf-az]\"\n")))) (let ((relint-batch-highlight 'caret)) (should (equal (relint-test--batch prog) (concat msg " \"[pqrf-az]\"\n" " ....^^^\n")))) (let ((relint-batch-highlight '("{" . "}"))) (should (equal (relint-test--batch prog) (concat msg " \"[pqr{f-a}z]\"\n")))))) (provide 'relint-test) relint-el_2.1+repack/README0000644000175000017500000004050614652774016015325 0ustar dogslegdogsleg relint -- Emacs regexp mistake finder ===================================== Relint scans Emacs Lisp files for mistakes in regexps, including deprecated syntax and bad practice. It also checks the regexp-like arguments to the functions skip-chars-forward, skip-chars-backward, skip-syntax-forward and skip-syntax-backward. * Contents - Usage - Installation - Configuration - What the diagnostics mean - Suppressing diagnostics - How it works - Bugs * Usage - Check a single file: M-x relint-file - Check all .el files in a directory tree: M-x relint-directory - Check current buffer: M-x relint-current-buffer In the *relint* buffer, pressing "g" will re-run the same check. - From batch mode: emacs -batch -l relint -f relint-batch FILES-AND-DIRS... where directories are scanned recursively. (Options for finding relint and xr need to be added after -batch, either -f package-initialize or -L DIR.) - From Emacs Lisp code, use one of the above functions or (relint-buffer BUFFER) which returns a list of diagnostics. - From other packages: The flycheck-relint and flymake-relint packages run relint automatically via flycheck and flymake, respectively. They are available from the MELPA package archive. * Installation From GNU ELPA (https://elpa.gnu.org/packages/relint.html): M-x package-install RET relint RET Relint requires the package xr (https://elpa.gnu.org/packages/xr.html); it will be installed automatically. * User options - variable 'relint-xr-checks' If set to 'all', it enables checks that detect more errors and performance problems but may also produce more false warnings. The default value is 'nil' which limits warnings to ones that are likely to be accurate. - variable 'relint-batch-highlight' This variable controls the diagnostics output of 'relint-batch'. If set to a string pair (BEGIN . END), these strings will be used to highlight the part of a regexp that a message is talking about. The default value makes that part appear in reverse video in a (VT100-compatible) terminal. The value 'caret' uses ASCII symbols to mark the interesting part instead. The value 'nil' disables highlighting entirely. - face 'relint-buffer-highlight' This is the face used to highlight text warned about for a message appearing in the '*relint*' buffer. * What the diagnostics mean Tip: if a regexp string is difficult to understand, consider using 'xr' to decode it, as in (xr-pp "your-messy-regexp"). - Unescaped literal 'X' A special character is taken literally because it occurs in a position where it does not need to be backslash-escaped. It is good style to do so anyway (assuming that it should occur as a literal character). - Escaped non-special character 'X' A character is backslash-escaped even though this is not necessary and does not turn it into a special sequence. Maybe the backslash was in error, or should be doubled if a literal backslash was expected. - Duplicated 'X' inside character alternative A character occurs twice inside [...]; this is obviously pointless. In particular, backslashes are not special inside [...]; they have no escaping power, and do not need to be escaped in order to include a literal backslash. - Repetition of repetition - Repetition of option - Optional repetition - Optional option A repetition construct is applied to an expression that is already repeated, such as a*+ or \(x?\)?. These expressions can be written with a single repetition and often indicate a different mistake, perhaps a missing backslash. When a repetition construct is ? or ??, it is termed 'option' instead; the principle is the same. - Reversed range 'Y-X' matches nothing The last character of a range precedes the first and therefore includes no characters at all (not even the endpoints). Most such ranges are caused by a misplaced hyphen. - Character 'B' included in range 'A-C' - Range 'A-C' includes character 'B' A range includes a character that also occurs individually. This is often caused by a misplaced hyphen. - Ranges 'A-M' and 'D-Z' overlap Two ranges have at least one character in common. This is often caused by a misplaced hyphen. - Two-character range 'A-B' A range only consists of its two endpoints, since they have consecutive character codes. This is often caused by a misplaced hyphen. - Range 'A-z' between upper and lower case includes symbols A range spans over upper and lower case letters, which also includes some symbols. This is probably unintentional. To cover both upper and lower case letters, use separate ranges, as in [A-Za-z]. - Suspect character range '+-X': should '-' be literal? A range has + as one of its endpoints, which could mean that the hyphen was actually intended to be literal in order to match both + and -. This check is only enabled when relint-xr-checks = all. - Possibly erroneous '\X' in character alternative A character alternative includes something that looks like a escape sequence, but no escape sequences are allowed there since backslash is not a special character in that context. It could also be a caused by too many backslashes. For example, "[\\n\\t]" matches the characters 'n', 't' and backslash, but could be an attempt to match newline and tab. This check is only enabled when relint-xr-checks = all. - Duplicated character class '[:class:]' A character class occurs twice in a single character alternative or skip set. - Or-pattern more efficiently expressed as character alternative When an or-pattern can be written as a character alternative, it becomes more efficient and reduces regexp stack usage. For example, a\|b is better written [ab], and \s-\|\sw is usually better written [[:space:][:word:]]. (There is a subtle difference in how syntax properties are handled but it rarely matters.) This check is only enabled when relint-xr-checks = all. - Duplicated alternative branch The same expression occurs in two different branches, like in A\|A. This has the effect of only including it once. - Branch matches superset/subset of a previous branch A branch in an or-expression matches a superset or subset of what another branch matches, like in [ab]\|a. This means that one of the branches can be eliminated without changing the meaning of the regexp. - Repetition subsumes/subsumed by preceding repetition An repeating expression matches a superset or subset of what the previous expression matches, in such a way that one of them is unnecessary. For example, [ab]+a* matches the same text as [ab]+, so the a* could be removed without changing the meaning of the regexp. - First/last item in repetition subsumes last/first item (wrapped) The first and last items in a repeated sequence, being effectively adjacent, match a superset or subset of each other, which makes for an unexpected inefficiency. For example, \(?:a*c[ab]+\)* can be seen as a*c[ab]+a*c[ab]+... where the [ab]+a* in the middle is a slow way of writing [ab]+ which is made worse by the outer repetition. The general remedy is to move the subsumed item out of the repeated sequence, resulting in a*\(?:c[ab]+\)* in the example above. - Non-newline follows end-of-line anchor - Line-start anchor follows non-newline A pattern that does not match a newline occurs right after an end-of-line anchor ($) or before a line-start anchor (^). This combination can never match. - Non-empty pattern follows end-of-text anchor A pattern that only matches a non-empty string occurs right after an end-of-text anchor (\'). This combination can never match. - Use \` instead of ^ in file-matching regexp - Use \' instead of $ in file-matching regexp In a regexp used for matching a file name, newlines are usually not relevant. Line-start and line-end anchors should therefore probably be replaced with string-start and string-end, respectively. Otherwise, the regexp may fail for file names that do contain newlines. - Possibly unescaped '.' in file-matching regexp In a regexp used for matching a file name, a naked dot is usually more likely to be a mistake (missing escaping backslash) than an actual intent to match any character except newline, since literal dots are very common in file name patterns. - Uncounted repetition The construct A\{,\} repeats A zero or more times which was probably not intended. - Implicit zero repetition The construct A\{\} only matches the empty string, which was probably not intended. - Suspect '[' in char alternative This warning indicates badly-placed square brackets in a character alternative, as in [A[B]C]. A literal ] must come first (possibly after a negating ^). - Literal '-' not first or last It is good style to put a literal hyphen last in character alternatives and skip sets, to clearly indicate that it was not intended as part of a range. - Repetition of zero-width assertion - Optional zero-width assertion A repetition operator was applied to a zero-width assertion, like ^ or \<, which is completely pointless. The error may be a missing escaping backslash. - Repetition of expression matching an empty string - Optional expression matching an empty string A repetition operator was applied to a sub-expression that could match the empty string; this is not necessarily wrong, but such constructs run very slowly on Emacs's regexp engine. Consider rewriting them into a form where the repeated expression cannot match the empty string. Example: \(?:a*b*\)* is equivalent to the much faster \(?:a\|b\)*. Another example: \(?:a?b*\)? is better written a?b*. In general, A?, where A matches the empty string, can be simplified to just A. - Repetition of effective repetition A repetition construct is applied to an expression that itself contains a repetition, in addition to some patterns that may match the empty string. This can lead to bad matching performance. Example: \(?:a*b+\)* is equivalent to the much faster \(?:a\|b\)* . Another example: \(?:a*b+\)+ is better written a*b[ab]* . - Possibly mistyped ':?' at start of group A group starts as \(:? which makes it likely that it was really meant to be \(?: -- ie, a non-capturing group. This check is only enabled when relint-xr-checks = all. - Unnecessarily escaped 'X' A character is backslash-escaped in a skip set despite not being one of the three special characters - (hyphen), \ (backslash) and ^ (caret). It could be unnecessary, or a backslash that should have been escaped. - Single-element range 'X-X' A range in a skip set has identical first and last elements. It is rather pointless to have it as a range. - Stray '\\' at end of string A single backslash at the end of a skip set is always ignored; double it if you want a literal backslash to be included. - Suspect skip set framed in '[...]' A skip set appears to be enclosed in [...], as if it were a regexp. Skip sets are not regexps and do not use brackets. To include the brackets themselves, put them next to each other. - Suspect character class framed in '[...]' A skip set contains a character class enclosed in double pairs of square brackets, as if it were a regexp. Character classes in skip sets are written inside a single pair of square brackets, like [:digit:]. - Empty set matches nothing The empty string is a skip set that does not match anything, and is therefore pointless. - Negated empty set matches anything The string "^" is a skip set that matches anything, and is therefore pointless. - 'X' cannot be used for arguments to 'F' An expression that looks like a regexp was given as an argument to a function that expects a skip-set. - Value from 'X' cannot be spliced into '[...]' An expression that looks like a regexp was used to form a string where it is surrounded by square brackets, as if it were part of a character alternative. Regexps are not valid inside character alternatives; they use a different syntax. If you are just building a string containing a regexp for display purposes, consider using other delimiters than square brackets; displaying the regexp 0-9 as [0-9] is very misleading. - Invalid char 'X' in syntax string A string argument to skip-syntax-forward or skip-syntax-backward contains a character that doesn't indicate a syntax class. Such a string is not a regexp or skip-set, but just a string of syntax codes, possibly with a leading ^ for negation. - Duplicated char 'X' in syntax string A string argument to skip-syntax-forward or skip-syntax-backward contains a duplicated class, which is pointless and may indicate a mistake. Note that some characters indicate the same syntax class: '.' and ' ' (space) both mean the 'space' class. - Empty syntax string A string argument to skip-syntax-forward or skip-syntax-backward is empty or "^", neither of which makes sense. - Ineffective string escape '\X' A backslash precedes a character that does not need escaping in a string literal (any string, not just regexps), like in "hot\-dog". If the backslash should be part of the string then it probably needs to be doubled; otherwise, it is pointless and should be removed to avoid confusion. In Emacs versions older than 27.1, a left round or square bracket, '(' or '[', at the very start of a line in a multi-line string could sometimes fool the Emacs-Lisp mode into believing it to be the start of a function, thus people sometimes precede such brackets with an otherwise unnecessary backslash. However, there is usually no reason to put backslashes before brackets in strings in general. - Character escape '\x' not followed by hex digit In Emacs versions older than 30.1, a hex escape without any actual hex digits, as in "\x", was silently accepted as a null byte which is not what anyone would expect. If the backslash should be included in the string, double it as usual. - Suspect range '+-X' or 'X-+' A character range with '+' as one of its endpoints is more often an incorrect attempt to include both '+' and '-' in the set. * Suppressing diagnostics While relint has been designed to avoid false positives, there may be cases where it emits unfounded complaints. Most of the time, it is worth the trouble to change the code to make them go away, but sometimes it cannot be done in a reasonable way. To suppress such diagnostics, add a comment on the form ;; relint suppression: REGEXP on the line before the code where the error occurred. REGEXP matches the message to be suppressed. Multiple suppression comment lines can precede a line of code to eliminate several complaints on the same line. * How it works Relint uses a combination of ad-hoc rules to locate regexps: - Arguments to certain standard functions such as re-search-forward, or to user-defined functions whose arguments have regexp-sounding names (like 'regexp') - Values of variables believed to be a regexp from their name (ending in '-regexp', for instance), from their doc string, or from their type (for defcustom forms) - Assignment to certain standard variables, such as page-delimiter It will then try to evaluate expressions statically as far as possible in order to arrive at strings which can be analysed. The regexp analysis is done by the xr library. This means that if relint complains about something that isn't actually a regexp, some names in your code may be misleading. * Bugs The simplistic method employed means that many errors will go undetected, but false warnings are usually rare. If you believe that an error could have been discovered but wasn't, or that an unwarranted complaint could be avoided, please report it as a bug. If you find a complaint hard to understand, don't be afraid to ask. Maybe it could be explained in a better way. relint-el_2.1+repack/relint-pkg.el0000644000175000017500000000062314734467772017050 0ustar dogslegdogsleg;; Generated package description from relint.el -*- no-byte-compile: t -*- (define-package "relint" "2.1" "Elisp regexp mistake finder" '((xr "2.0") (emacs "27.1")) :commit "9eda48e439e13479151be4abbf47906326bc732f" :authors '(("Mattias Engdegård" . "mattiase@acm.org")) :maintainer '("Mattias Engdegård" . "mattiase@acm.org") :keywords '("lisp" "regexps") :url "https://github.com/mattiase/relint") relint-el_2.1+repack/relint.el0000644000175000017500000035120614734467771016276 0ustar dogslegdogsleg;;; relint.el --- Elisp regexp mistake finder -*- lexical-binding: t -*- ;; Copyright (C) 2019-2024 Free Software Foundation, Inc. ;; Author: Mattias Engdegård ;; Version: 2.1 ;; Package-Requires: ((xr "2.0") (emacs "27.1")) ;; URL: https://github.com/mattiase/relint ;; Keywords: lisp, regexps ;; 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: ;; Relint scans elisp files for regexps and reports potential errors, ;; including deprecated syntax and bad practice. See the README file ;; for more information. ;;; Code: (require 'xr) (require 'compile) (require 'cl-lib) (require 'subr-x) (defcustom relint-xr-checks nil "Extra checks to be performed by `xr' for each regexp. This is the CHECKS argument passed to `xr-lint' (q.v.). It is either nil, meaning the standard set of checks with minimal false positives, or `all', enabling all checks." :group 'relint :type '(choice (const :tag "Standard checks only" nil) (const :tag "All checks" all))) (defface relint-buffer-highlight '((t :inherit highlight)) "Face for highlighting the string part warned about in the `*relint*' buffer." :group 'relint) ;; FIXME: default to underline or reverse? Or `caret' if stdout is non-tty? (defcustom relint-batch-highlight '("\e[7m" . "\e[m") "How to emphasise part of a string warned about in batch output. The value is one of the following: A pair of strings for turning on and off highlighting in the terminal; these are typically escape sequences. `caret', which adds an ASCII caret on the line under the string. `nil', which disables highlighting. The default value produces reverse video in a VT100-compatible terminal. In interactive mode, relint uses the `relint-buffer-highlight' face instead." :group 'relint :type '(choice (const :tag "Terminal reverse" ("\e[7m" . "\e[m")) (const :tag "Terminal underline" ("\e[4m" . "\e[m")) (cons :tag "Escape sequences" (string :tag "Sequence for turning highlighting on" "\e[7m") (string :tag "Sequence for turning highlighting off" "\e[m")) (const :tag "ASCII caret" caret) (const :tag "Highlighting disabled" nil))) (defvar relint--force-batch-output nil) ; for testing only (cl-defstruct (relint-diag (:constructor nil) (:constructor relint--make-diag (message beg-pos end-pos pos-type string beg-idx end-idx severity)) (:copier nil) (:type vector)) message ; string of message beg-pos ; first buffer position end-pos ; last buffer position, or nil if only the start available pos-type ; `string' if buffer at BEG-POS..END-POS is inside a string literal ; corresponding to STRING at BEG-IDX..END-IDX; ; any other value means that BEG-POS..END-POS just point to code string ; string inside which the complaint occurs, or nil beg-idx ; first index into STRING, or nil end-idx ; last index into STRING, or nil severity ; `error', `warning' or `info' ) (defun relint--get-error-buffer () "Buffer to which errors are printed, or nil if noninteractive." (and (not noninteractive) (let ((buf (get-buffer-create "*relint*"))) (with-current-buffer buf (unless (eq major-mode 'relint-mode) (relint-mode)) (let ((inhibit-read-only t)) (compilation-forget-errors) (erase-buffer))) buf))) (defun relint--add-to-error-buffer (error-buffer string) (with-current-buffer error-buffer (goto-char (point-max)) (let ((inhibit-read-only t)) (insert string)))) (defun relint--skip-whitespace () (when (looking-at (rx (1+ (or blank "\n" "\f" (seq ";" (0+ nonl)))))) (goto-char (match-end 0)))) (defun relint--follow-path (path) "Move point forward along PATH (reversed list of list indices to follow to target). For example, if point is before the form (A B (C ((D E F G)))) and PATH is (3 0 1 2), then the returned position is right before G." (let ((p (reverse path))) (while p (relint--skip-whitespace) (let ((skip (car p))) ;; Enter next sexp and skip past the `skip' first sexps inside. (when (looking-at (rx ".")) ;; Skip `. (' since it represents zero sexps. (forward-char) (relint--skip-whitespace) (when (looking-at (rx "(")) (forward-char) (relint--skip-whitespace))) (cond ((looking-at (rx (or "'" "#'" "`" ",@" ","))) (goto-char (match-end 0)) (setq skip (1- skip))) ((looking-at (rx "(")) (forward-char 1))) (while (> skip 0) (relint--skip-whitespace) (if (looking-at (rx ".")) (progn (goto-char (match-end 0)) (relint--skip-whitespace) (cond ((looking-at (rx (or "'" "#'" "`" ",@" ","))) ;; Sugar after dot represents one sexp. (goto-char (match-end 0)) (setq skip (1- skip))) ((looking-at (rx "(")) ;; `. (' represents zero sexps. (goto-char (match-end 0))))) (forward-sexp) (setq skip (1- skip))))) (setq p (cdr p)))) (relint--skip-whitespace) (when (looking-at (rx ".")) (forward-char) (relint--skip-whitespace))) (defun relint--pos-from-start-pos-path (start-pos path) "Compute position from START-POS and PATH (reversed list of list indices to follow to target)." (save-excursion (goto-char start-pos) (relint--follow-path path) (point))) (defun relint--literal-string-pos (string-pos n) "Position of character N in a literal string at STRING-POS." (save-excursion (goto-char (1+ string-pos)) ; Skip first double quote. (dotimes (_ n) ;; Match a single character in a string. Since we already read it, ;; we know that it's well-formed. (looking-at (rx (* ?\\ (any " \n")) ; Skip escaped space and newline. (or (not (any ?\\)) ; Unescaped char. (seq ?\\ (or (** 1 3 (any "0-7")) ; Octal. (seq ?x (+ (any "0-9A-Fa-f"))) ; Hex. (seq ?u (= 4 (any "0-9A-Fa-f"))) ; Unicode. (seq ?U (= 8 (any "0-9A-Fa-f"))) ; Unicode. (seq "N{" (+ (not (any "}"))) "}") ; Named. (seq (any "CMS") "-" anything) ; Keystroke. anything))))) (goto-char (match-end 0))) (point))) (defun relint--string-pos (pos n endp) "Position of character N in a string expression at POS, or nil if no position could be determined. If ENDP is true, use the last position of the character, otherwise the first, in case it occupies more than one position in the buffer." (save-excursion (goto-char pos) (pcase (read (current-buffer)) ((pred stringp) (if endp (1- (relint--literal-string-pos pos (1+ n))) (relint--literal-string-pos pos n))) (`(concat . ,args) ;; Find out in which argument the sought position is. (let ((index 1)) (while (and args (stringp (car args)) (>= n (length (car args)))) (setq n (- n (length (car args)))) (setq index (1+ index)) (setq args (cdr args))) (and args (stringp (car args)) (let ((string-pos (relint--pos-from-start-pos-path pos (list index)))) (if endp (1- (relint--literal-string-pos string-pos (1+ n))) (relint--literal-string-pos string-pos n))))))))) (defun relint--suppression (pos message) "Whether there is a suppression for MESSAGE at POS." (save-excursion ;; On a preceding line, look for a comment on the form ;; ;; relint suppression: REGEXP ;; ;; where REGEXP matches MESSAGE. There can be ;; multiple suppression lines preceding a line of code with ;; several errors. (goto-char pos) (forward-line -1) (let ((matched nil)) (while (and (not (setq matched (and (looking-at (rx (0+ blank) (1+ ";") (0+ blank) "relint suppression:" (1+ blank) (group (0+ nonl) (not (any "\n" blank))))) (let ((regexp (match-string 1))) (string-match-p regexp message))))) (looking-at (rx bol (0+ blank) (opt ";" (0+ nonl)) eol)) (not (bobp))) (forward-line -1)) matched))) (defun relint--output-message (error-buffer string) (if error-buffer (relint--add-to-error-buffer error-buffer (concat string "\n")) (message "%s" string))) (defun relint--col-at-pos (pos) (save-excursion (goto-char pos) (1+ (current-column)))) (defun relint--output-complaint (error-buffer file diag) (let* ((str (relint-diag-string diag)) (beg-idx (relint-diag-beg-idx diag)) (end-idx (relint-diag-end-idx diag)) (severity (relint-diag-severity diag)) (beg (relint-diag-beg-pos diag)) (end (relint-diag-end-pos diag)) (beg-line (line-number-at-pos beg t)) (end-line (cond ((eq beg end) beg-line) (end (line-number-at-pos end t)))) (beg-col (relint--col-at-pos beg)) (end-col (cond ((eq beg end) beg-col) (end (relint--col-at-pos end)))) (loc-str (cond ((and end-line (< beg-line end-line)) (format "%d.%d-%d.%d" beg-line beg-col end-line end-col)) (end-col (format "%d:%d-%d" beg-line beg-col end-col)) (t (format "%d:%d" beg-line beg-col)))) (quoted-str (and str (relint--quote-string str))) (caret-str nil)) (when beg-idx (let* ((bounds (relint--caret-bounds str beg-idx end-idx)) ;; Indices into quoted-str, which includes double quotes: (beg-qs (+ (car bounds) 1)) (end-qs (+ (cdr bounds) 2))) ; exclusive (cond ((and error-buffer (not relint--force-batch-output)) ;; Output to buffer: apply highlight face to part of string. (put-text-property beg-qs end-qs 'font-lock-face 'relint-buffer-highlight quoted-str)) ((eq relint-batch-highlight 'caret) (let* ((col-from (car bounds)) (col-to (cdr bounds))) (setq caret-str (concat (make-string col-from ?.) (make-string (- col-to col-from -1) ?^))))) ((consp relint-batch-highlight) (setq quoted-str (concat (substring quoted-str 0 beg-qs) (car relint-batch-highlight) (substring quoted-str beg-qs end-qs) (cdr relint-batch-highlight) (substring quoted-str end-qs))))))) (relint--output-message error-buffer (concat (format "%s:%s: " file loc-str) (cond ((eq severity 'error) "error: ") ((eq severity 'info) "info: ")) (relint-diag-message diag) (cond ((and beg-idx end-idx (< beg-idx end-idx)) (format " (pos %d..%d)" beg-idx end-idx)) (beg-idx (format " (pos %d)" beg-idx))) (and quoted-str (format "\n %s" quoted-str)) (and caret-str (format "\n %s" caret-str)))))) (defun relint--output-complaints (buffer file complaints error-buffer) (with-current-buffer buffer (dolist (group complaints) (dolist (complaint group) (relint--output-complaint error-buffer file complaint))))) (defvar relint--suppression-count) (defvar relint--complaints ;; list of lists of `relint-diag' objects ) (defun relint--report-group (group) (let ((diag (car group))) (if (relint--suppression (relint-diag-beg-pos diag) (relint-diag-message diag)) (setq relint--suppression-count (1+ relint--suppression-count)) (push group relint--complaints)))) (defun relint--report-nonstring (message beg-pos end-pos severity) (relint--report-group (list (relint--make-diag message beg-pos end-pos nil nil nil nil 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--make-diag message (or beg-pos expr-pos) end-pos (and beg-pos end-pos 'string) 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: if all we have is the start of a Lisp sexp, it should be easy to ;; find where it ends! (defun relint--warn-at-pos (beg-pos end-pos message) (relint--report-nonstring message beg-pos end-pos '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 (pos message) (relint--report-nonstring message pos nil 'error)) (defun relint--escape-string (str escape-printable) (replace-regexp-in-string (rx (any cntrl ?\177 (#x3fff80 . #x3fffff) ?\\ ?\")) (lambda (s) (let ((c (logand (string-to-char s) #xff))) (or (cdr (assq c '((?\b . "\\b") (?\t . "\\t") (?\n . "\\n") (?\v . "\\v") (?\f . "\\f") (?\r . "\\r") (?\e . "\\e")))) (if (memq c '(?\\ ?\")) (if escape-printable (string ?\\ c) (string c)) (format "\\%03o" c))))) str t t)) (defun relint--quote-string (str) (concat "\"" (relint--escape-string str t) "\"")) (defun relint--caret-bounds (string beg end) (let* ((beg-col (length (relint--escape-string (substring string 0 beg) t))) (end-col (if end ;; handle escaped chars such as \n (1- (length (relint--escape-string (substring string 0 (1+ end)) t))) beg-col))) (cons beg-col end-col))) (defun relint--expand-name (name) (pcase-exhaustive name (`(call-to . ,x) (format "call to %s" x)) (`(parameter . ,x) (format "%s parameter" x)) (`(at ,x ,y) (format "%s (%s)" x y)) ((pred stringp) name) ((pred symbolp) (symbol-name name)))) (defun relint--message-with-context (msg name) (format "In %s: %s" (relint--expand-name name) msg)) (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--string-complaints (xr-skip-set-lint skip-set) skip-set name pos path)) (defun relint--check-re-string (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--string-complaints (xr-lint re 'file relint-xr-checks) re 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) (?\s . whitespace) (?. . punctuation) (?w . word) (?W . word) ; undocumented (?_ . symbol) (?\( . open-parenthesis) (?\) . close-parenthesis) (?' . expression-prefix) (?\" . string-quote) (?$ . paired-delimiter) (?\\ . escape) (?/ . character-quote) (?< . comment-start) (?> . comment-end) (?| . string-delimiter) (?! . comment-delimiter))) (defun relint--syntax-string-lint (syntax) "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 '("" "^")) (push (list start nil "Empty syntax string" 'warning) errs)) (let ((seen nil)) (dolist (i (number-sequence start (1- (length syntax)))) (let* ((c (aref syntax i)) (sym (cdr (assq c relint--syntax-codes)))) (if sym (if (memq sym seen) (push (list (list i i (relint--escape-string (format-message "Duplicated syntax code `%c'" c) nil) 'warning)) errs) (push sym seen)) (push (list (list i i (relint--escape-string (format-message "Invalid char `%c' in syntax string" c) nil) 'warning)) errs))))) (nreverse errs))) (defvar relint--variables nil "Alist of global variable definitions. Each element is either (NAME expr EXPR), for unevaluated expressions, or (NAME val VAL), for values.") ;; List of variables that have been checked, so that we can avoid ;; checking direct uses of it. (defvar relint--checked-variables) ;; Alist of functions taking regexp argument(s). ;; The names map to a list of the regexp argument indices. (defvar relint--regexp-functions) ;; List of functions defined in the current file, each element on the ;; form (FUNCTION ARGS BODY), where ARGS is the lambda list and BODY ;; its body expression list. (defvar relint--function-defs) ;; List of macros defined in the current file, each element on the ;; form (MACRO ARGS BODY), where ARGS is the lambda list and BODY its ;; body expression list. (defvar relint--macro-defs) ;; Alist of alias definitions in the current file. (defvar relint--alias-defs) ;; Alist of local variables. Each element is either (NAME VALUE), ;; where VALUE is the (evaluated) value, or just (NAME) if the binding ;; exists but the value is unknown. (defvar relint--locals) (defvar relint--eval-mutables nil "List of local variables mutable in the current evaluation context.") (eval-and-compile (defconst relint--safe-functions '(cons list append concat car cdr caar cadr cdar cddr car-safe cdr-safe nth nthcdr caaar cdaar cadar cddar caadr cdadr caddr cdddr format format-message regexp-quote regexp-opt regexp-opt-charset regexp-opt-depth reverse member memq memql remove remq member-ignore-case assoc assq rassoc rassq assoc-string identity string make-string make-list substring substring-no-properties length safe-length symbol-name gensym intern intern-soft make-symbol null not xor eq eql equal string-equal string= string< string-lessp string> string-greaterp compare-strings char-equal string-match-p string-match split-string wildcard-to-regexp combine-and-quote-strings split-string-and-unquote string-to-multibyte string-as-multibyte string-to-unibyte string-as-unibyte string-join string-trim-left string-trim-right string-trim string-prefix-p string-suffix-p string-blank-p string-remove-prefix string-remove-suffix string-search string-replace vector aref elt vconcat char-to-string string-to-char number-to-string string-to-number int-to-string string-to-list string-to-vector string-or-null-p string-bytes upcase downcase capitalize purecopy copy-sequence copy-alist copy-tree flatten-tree member-ignore-case last butlast number-sequence take plist-get plist-member 1value consp atom stringp symbolp listp nlistp booleanp keywordp integerp numberp natnump fixnump bignump characterp zerop floatp sequencep vectorp arrayp type-of + - * / % mod 1+ 1- max min < <= = > >= /= abs expt sqrt ash lsh logand logior logxor lognot logb logcount floor ceiling round truncate float cl--block-wrapper ; alias for `identity' ) "Functions that are safe to call during evaluation. Except for altering the match state, these are side-effect-free and reasonably pure (some depend on variables in fairly uninteresting ways, like `case-fold-search'). More functions could be added if there is evidence that it would help in evaluating more regexp strings.") ) (defconst relint--safe-alternatives '((nconc . append) (delete . remove) (delq . remq) (nreverse . reverse) (nbutlast . butlast) (ntake . take)) "Alist mapping non-safe functions to semantically equivalent safe alternatives.") (defconst relint--safe-cl-alternatives '((cl-delete-duplicates . cl-remove-duplicates) (cl-delete . cl-remove) (cl-delete-if . cl-remove-if) (cl-delete-if-not . cl-remove-if-not) (cl-nsubstitute . cl-substitute) (cl-nunion . cl-union) (cl-nintersection . cl-intersection) (cl-nset-difference . cl-set-difference) (cl-nset-exclusive-or . cl-set-exclusive-or) (cl-nsublis . cl-sublis)) "Alist mapping non-safe cl functions to semantically equivalent safe alternatives. They may still require wrapping their function arguments.") (defun relint--rx-safe (rx) "Return RX safe to translate; throw `relint-eval' `no-value' if not." (cond ((atom rx) rx) ;; These cannot contain rx subforms. ((memq (car rx) '(any in char not-char not backref syntax not-syntax category)) rx) ;; We ignore the differences in evaluation time between `eval' and ;; `regexp', and just use what environment we have. ((eq (car rx) 'eval) (let ((arg (relint--eval (cadr rx)))) ;; For safety, make sure the result isn't another evaluating form. (when (and (consp arg) (memq (car arg) '(literal eval regexp regex))) (throw 'relint-eval 'no-value)) arg)) ((memq (car rx) '(literal regexp regex)) (let ((arg (relint--eval (cadr rx)))) (if (stringp arg) (list (car rx) arg) (throw 'relint-eval 'no-value)))) (t (cons (car rx) (mapcar #'relint--rx-safe (cdr rx)))))) (defun relint--eval-rx (args) "Evaluate an `rx-to-string' expression." (let ((safe-args (cons (relint--rx-safe (car args)) (cdr args)))) (condition-case nil (apply #'rx-to-string safe-args) (error (throw 'relint-eval 'no-value))))) (defun relint--apply (formals actuals body) "Bind FORMALS to ACTUALS and evaluate BODY." (let ((bindings nil)) (while formals (cond ((eq (car formals) '&rest) (push (cons (cadr formals) (list actuals)) bindings) (setq formals nil)) ((eq (car formals) '&optional) (setq formals (cdr formals))) (t (push (cons (car formals) (list (car actuals))) bindings) (setq formals (cdr formals)) (setq actuals (cdr actuals))))) ;; This results in dynamic binding, but that doesn't matter for our ;; purposes. (let ((relint--locals (append bindings relint--locals)) (relint--eval-mutables (append (mapcar #'car bindings) relint--eval-mutables))) (relint--eval-body body)))) (defun relint--no-value (&rest _) "A function that fails when called." (throw 'relint-eval 'no-value)) (defun relint--wrap-function (form) "Transform an evaluated function (typically a symbol or lambda expr) into something that can be called safely." (cond ((symbolp form) (if (memq form relint--safe-functions) form (or (cdr (assq form relint--safe-alternatives)) (let ((def (cdr (assq form relint--function-defs)))) (if def (let ((formals (car def)) (body (cadr def))) (lambda (&rest args) (relint--apply formals args body))) 'relint--no-value))))) ((and (consp form) (eq (car form) 'lambda)) (let ((formals (cadr form)) (body (cddr form))) (lambda (&rest args) (relint--apply formals args body)))) (t 'relint--no-value))) (defun relint--wrap-cl-keyword-args (args) "Wrap the function arguments :test, :test-not, :key in ARGS." (let ((test (plist-get args :test)) (test-not (plist-get args :test-not)) (key (plist-get args :key)) (ret (copy-sequence args))) (when test (plist-put ret :test (relint--wrap-function test))) (when test-not (plist-put ret :test-not (relint--wrap-function test-not))) (when key (plist-put ret :key (relint--wrap-function key))) ret)) (defun relint--eval-to-binding (form) "Evaluate a form, returning (VALUE) on success or nil on failure." (let ((val (catch 'relint-eval (list (relint--eval form))))) (if (eq val 'no-value) nil val))) (defun relint--eval-body (body) "Evaluate a list of forms; return result of last form." (if (consp body) (progn (while (consp (cdr body)) (relint--eval (car body)) (setq body (cdr body))) (if (cdr body) (throw 'relint-eval 'no-value) (relint--eval (car body)))) (if body (throw 'relint-eval 'no-value) nil))) (defalias 'relint--take (if (fboundp 'take) #'take (lambda (n list) (cl-loop repeat n for x in list collect x)))) (defalias 'relint--proper-list-p (if (fboundp 'proper-list-p) #'proper-list-p (lambda (x) (and (listp x) (ignore-errors (length x)))))) (defun relint--eval (form) "Evaluate a form. Throw `relint-eval' `no-value' if something could not be evaluated safely." (if (atom form) (cond ((booleanp form) form) ((keywordp form) form) ((symbolp form) (let ((local (assq form relint--locals))) (if local (if (cdr local) (cadr local) (throw 'relint-eval 'no-value)) (let ((binding (assq form relint--variables))) (if binding (if (eq (cadr binding) 'val) (caddr binding) (let ((val (relint--eval (caddr binding)))) (setcdr binding (list 'val val)) val)) (throw 'relint-eval 'no-value)))))) (t form)) (let ((head (car form)) (body (cdr form))) (cond ((eq head 'quote) (if (and (consp (car body)) (eq (caar body) '\,)) ; In case we are inside a backquote. (throw 'relint-eval 'no-value) (car body))) ((memq head '(function cl-function)) ;; Treat cl-function like plain function (close enough). (car body)) ((eq head 'lambda) form) ;; Functions considered safe. ((memq head (eval-when-compile relint--safe-functions)) (let ((args (mapcar #'relint--eval body))) ;; Catching all errors isn't wonderful, but sometimes a global ;; variable argument has an unsuitable default value which is ;; supposed to have been changed at the expression point. (condition-case nil (apply head args) (error (throw 'relint-eval 'no-value))))) ;; replace-regexp-in-string: wrap the rep argument if it's a function. ((eq head 'replace-regexp-in-string) (let ((all-args (mapcar #'relint--eval body))) (let* ((rep-arg (cadr all-args)) (rep (if (stringp rep-arg) rep-arg (relint--wrap-function rep-arg))) (args (append (list (car all-args) rep) (cddr all-args)))) (condition-case nil (apply head args) (error (throw 'relint-eval 'no-value)))))) ;; alist-get: wrap the optional fifth argument (testfn). ((eq head 'alist-get) (let* ((all-args (mapcar #'relint--eval body)) (testfn (nth 4 all-args)) (args (if testfn (append (relint--take 4 all-args) (list (relint--wrap-function testfn))) all-args))) (condition-case nil (apply head args) (error (throw 'relint-eval 'no-value))))) ((eq head 'if) (let ((condition (relint--eval (car body)))) (let ((then-part (nth 1 body)) (else-tail (nthcdr 2 body))) (cond (condition (relint--eval then-part)) (else-tail (relint--eval-body else-tail)))))) ((eq head 'and) (if body (let ((val (relint--eval (car body)))) (if (and val (cdr body)) (relint--eval (cons 'and (cdr body))) val)) t)) ((eq head 'or) (if body (let ((val (relint--eval (car body)))) (if (and (not val) (cdr body)) (relint--eval (cons 'or (cdr body))) val)) nil)) ((eq head 'cond) (and body (let ((clause (car body))) (if (consp clause) (let ((val (relint--eval (car clause)))) (if val (if (cdr clause) (relint--eval-body (cdr clause)) val) (relint--eval (cons 'cond (cdr body))))) ;; Syntax error (throw 'relint-eval 'no-value))))) ((memq head '(progn ignore-errors eval-when-compile eval-and-compile with-no-warnings)) (relint--eval-body body)) ;; Hand-written implementation of `cl-assert' -- good enough. ((eq head 'cl-assert) (unless (relint--eval (car body)) (throw 'relint-eval 'no-value))) ((eq head 'prog1) (let ((val (relint--eval (car body)))) (relint--eval-body (cdr body)) val)) ((eq head 'prog2) (relint--eval (car body)) (let ((val (relint--eval (cadr body)))) (relint--eval-body (cddr body)) val)) ;; delete-dups: Work on a copy of the argument. ((eq head 'delete-dups) (let ((arg (relint--eval (car body)))) (delete-dups (copy-sequence arg)))) ;; `cl-flet', `cl-flet*' and `cl-labels': these macroexpand their bodies ;; eagerly which would be unsafe, so instead we transform them into ;; `let' etc, as if it were a Lisp-1. Calls to local functions are then ;; transformed as we encounter them. ((memq head '(cl-flet cl-flet* cl-labels)) (let ((f (cdr (assq head '((cl-flet . let) (cl-flet* . let*) (cl-labels . letrec))))) (bindings (mapcar (lambda (b) (if (and (consp b) (symbolp (car b)) (> (length b) 2)) `(,(car b) (lambda ,(cadr b) ,@(cddr b))) b)) (car body)))) (relint--eval `(,f ,bindings ,@(cdr body))))) ;; Safe macros that expand to pure code, and their auxiliary macros. ;; FIXME: Is this safe? ((memq head '(when unless \` backquote-list* letrec cl-case cl-block cl-loop)) (relint--eval ;; Suppress any warning message arising from macro-expansion; ;; it will just confuse the user and we can't give a good location. (let ((inhibit-message t)) (macroexpand-1 form)))) ;; catch: as long as nobody throws, this naïve code is fine. ((eq head 'catch) (relint--eval-body (cdr body))) ;; condition-case: as long as there is no error... ((eq head 'condition-case) (relint--eval (cadr body))) ;; Functions taking a function as first argument. ((memq head '(apply funcall mapconcat cl-some cl-every cl-notany cl-notevery)) (let ((fun (relint--wrap-function (relint--eval (car body)))) (args (mapcar #'relint--eval (cdr body)))) (condition-case nil (apply head fun args) (error (throw 'relint-eval 'no-value))))) ;; Functions with functions as keyword arguments :test, :test-not, :key ((memq head '(cl-remove-duplicates cl-remove cl-substitute cl-member cl-find cl-position cl-count cl-mismatch cl-search cl-union cl-intersection cl-set-difference cl-set-exclusive-or cl-subsetp cl-assoc cl-rassoc cl-sublis)) (let ((args (relint--wrap-cl-keyword-args (mapcar #'relint--eval body)))) (condition-case nil (apply head args) (error (throw 'relint-eval 'no-value))))) ;; Functions taking a function as first argument, ;; and with functions as keyword arguments :test, :test-not, :key ((memq head '(cl-reduce cl-remove-if cl-remove-if-not cl-find-if cl-find-if-not cl-position-if cl-position-if-not cl-count-if cl-count-if-not cl-member-if cl-member-if-not cl-assoc-if cl-assoc-if-not cl-rassoc-if cl-rassoc-if-not)) (let ((fun (relint--wrap-function (relint--eval (car body)))) (args (relint--wrap-cl-keyword-args (mapcar #'relint--eval (cdr body))))) (condition-case nil (apply head fun args) (error (throw 'relint-eval 'no-value))))) ;; mapcar, mapcan, mapc: accept missing items in the list argument. ((memq head '(mapcar mapcan mapc)) (let* ((fun (relint--wrap-function (relint--eval (car body)))) (arg (relint--eval-list (cadr body))) (seq (if (listp arg) (remq nil arg) arg))) (condition-case nil (funcall head fun seq) (error (throw 'relint-eval 'no-value))))) ;; sort: accept missing items in the list argument. ((eq head 'sort) ;; FIXME: handle new-style `sort' args (let* ((arg (relint--eval-list (car body))) (seq (cond ((listp arg) (remq nil arg)) ((sequencep arg) (copy-sequence arg)) (arg))) (pred (relint--wrap-function (relint--eval (cadr body))))) (condition-case nil (sort seq pred) (error (throw 'relint-eval 'no-value))))) ;; rx, rx-to-string: check for lisp expressions in constructs first, ;; then apply. ((eq head 'rx) (relint--eval-rx (list (cons 'seq body) t))) ((eq head 'rx-to-string) (let ((args (mapcar #'relint--eval body))) (relint--eval-rx args))) ;; setq: set local variables if permitted. ((eq head 'setq) (if (and (symbolp (car body)) (consp (cdr body))) (let* ((name (car body)) ;; FIXME: Consider using relint--eval-to-binding instead, ;; tolerating unevaluatable expressions. (val (relint--eval (cadr body)))) ;; Somewhat dubiously, we ignore the side-effect for ;; non-local (or local non-mutable) variables and hope ;; it doesn't matter. (when (memq name relint--eval-mutables) (let ((local (assq name relint--locals))) (setcdr local (list val)))) (if (cddr body) (relint--eval (cons 'setq (cddr body))) val)) (throw 'relint-eval 'no-value))) ; Syntax error. ((eq head 'push) (let* ((expr (car body)) (name (cadr body)) (local (assq name relint--locals))) (if (and (memq name relint--eval-mutables) (cdr local)) (let ((new-val (cons (relint--eval expr) (cadr local)))) (setcdr local (list new-val)) new-val) (throw 'relint-eval 'no-value)))) ((eq head 'pop) (let* ((name (car body)) (local (assq name relint--locals))) (if (and (memq name relint--eval-mutables) (cdr local) (consp (cadr local))) (let ((val (cadr local))) (setcdr local (list (cdr val))) (car val)) (throw 'relint-eval 'no-value)))) ;; let and let*: do not permit multi-expression bodies, since they ;; will contain necessary side-effects that we don't handle. ((eq head 'let) (let ((bindings (mapcar (lambda (binding) (if (consp binding) (cons (car binding) (relint--eval-to-binding (cadr binding))) (cons binding (list nil)))) (car body)))) (let ((relint--locals (append bindings relint--locals)) (relint--eval-mutables (append (mapcar #'car bindings) relint--eval-mutables))) (relint--eval-body (cdr body))))) ((eq head 'let*) (let ((bindings (car body))) (if bindings (let* ((bindspec (car bindings)) (binding (if (consp bindspec) (cons (car bindspec) (relint--eval-to-binding (cadr bindspec))) (cons bindspec (list nil)))) (relint--locals (cons binding relint--locals)) (relint--eval-mutables (cons (car binding) relint--eval-mutables))) (relint--eval `(let* ,(cdr bindings) ,@(cdr body)))) (relint--eval-body (cdr body))))) ;; dolist: simulate its operation. We could also expand it, ;; but this is somewhat faster. ((eq head 'dolist) (unless (and (>= (length body) 2) (consp (car body))) (throw 'relint-eval 'no-value)) (let ((var (nth 0 (car body))) (seq-arg (nth 1 (car body))) (res-arg (nth 2 (car body)))) (unless (symbolp var) (throw 'relint-eval 'no-value)) (let ((seq (relint--eval-list seq-arg))) (while (consp seq) (let ((relint--locals (cons (list var (car seq)) relint--locals))) (relint--eval-body (cdr body))) (setq seq (cdr seq)))) (and res-arg (relint--eval res-arg)))) ;; while: this slows down simulation noticeably, but catches some ;; mistakes. ((eq head 'while) (let ((condition (car body)) (loops 0)) (while (and (relint--eval condition) (< loops 100)) (relint--eval-body (cdr body)) (setq loops (1+ loops))) nil)) ;; Loose comma: can occur if we unwittingly stumbled into a backquote ;; form. Just eval the arg and hope for the best. ((eq head '\,) (relint--eval (car body))) ;; functionp: be optimistic, for determinism ((eq head 'functionp) (let ((arg (relint--eval (car body)))) (cond ((symbolp arg) (not (memq arg '(nil t)))) ((consp arg) (eq (car arg) 'lambda))))) ;; featurep: only handle features that we are reasonably sure about, ;; to avoid depending too much on the particular host Emacs. ((eq head 'featurep) (let ((arg (relint--eval (car body)))) (cond ((eq arg 'xemacs) nil) ((memq arg '(emacs mule font-lock lisp-float-type)) t) (t (throw 'relint-eval 'no-value))))) ;; Locally defined functions: try evaluating. ((assq head relint--function-defs) (let* ((fn (cdr (assq head relint--function-defs))) (formals (car fn)) (fn-body (cadr fn))) (let ((args (mapcar #'relint--eval body))) (relint--apply formals args fn-body)))) ;; Locally defined macros: try expanding. ((assq head relint--macro-defs) (let ((args body)) (let* ((macro (cdr (assq head relint--macro-defs))) (formals (car macro)) (macro-body (cadr macro))) (relint--eval (relint--apply formals args macro-body))))) ;; Alias: substitute and try again. ((assq head relint--alias-defs) (relint--eval (cons (cdr (assq head relint--alias-defs)) body))) ((assq head relint--safe-alternatives) (relint--eval (cons (cdr (assq head relint--safe-alternatives)) body))) ((assq head relint--safe-cl-alternatives) (relint--eval (cons (cdr (assq head relint--safe-cl-alternatives)) body))) ;; Pretend that we are using a Lisp-1 for calls to functions that are ;; locally bound, to make `cl-labels' etc work. This should be good ;; enough in practice. ((assq head relint--locals) (let ((fval (car-safe (cdr (assq head relint--locals))))) (if (eq (car-safe fval) 'lambda) (relint--eval `(funcall ,fval ,@body)) (throw 'relint-eval 'no-value)))) (t (throw 'relint-eval 'no-value)))))) (defun relint--eval-or-nil (form) "Evaluate FORM. Return nil if something prevents it from being evaluated." (let ((val (catch 'relint-eval (relint--eval form)))) (if (eq val 'no-value) nil val))) (defun relint--eval-list-body (body) (and (consp body) (progn (while (consp (cdr body)) (relint--eval-list (car body)) (setq body (cdr body))) (relint--eval-list (car body))))) (defun relint--eval-list (form) "Evaluate a form as far as possible, attempting to keep its list structure even if all subexpressions cannot be evaluated. Parts that cannot be evaluated are nil." (cond ((symbolp form) (and form (let ((local (assq form relint--locals))) (if local (and (cdr local) (cadr local)) (let ((binding (assq form relint--variables))) (and binding (if (eq (cadr binding) 'val) (caddr binding) ;; Since we are only doing a list evaluation, don't ;; update the variable here. (relint--eval-list (caddr binding))))))))) ((atom form) form) ((memq (car form) '(progn ignore-errors eval-when-compile eval-and-compile)) (relint--eval-list-body (cdr form))) ;; Pure structure-generating functions: Apply even if we cannot evaluate ;; all arguments (they will be nil), because we want a reasonable ;; approximation of the structure. ((memq (car form) '(list append cons reverse remove remq)) (apply (car form) (mapcar #'relint--eval-list (cdr form)))) ((eq (car form) 'delete-dups) (let ((arg (relint--eval-list (cadr form)))) (delete-dups (copy-sequence arg)))) ((memq (car form) '(purecopy copy-sequence copy-alist)) (relint--eval-list (cadr form))) ((memq (car form) '(\` backquote-list*)) (relint--eval-list (let ((inhibit-message t)) (macroexpand-1 form)))) ((assq (car form) relint--safe-alternatives) (relint--eval-list (cons (cdr (assq (car form) relint--safe-alternatives)) (cdr form)))) (t (relint--eval-or-nil form)))) (defun relint--eval-list-iter (fun form path) "Evaluate FORM to a list and call FUN for each non-nil element with (ELEM ELEM-PATH LITERAL) as arguments. ELEM-PATH is the best approximation to a path to ELEM and has the same base position as PATH; LITERAL is true if ELEM-PATH leads to a literal ELEM in the source." (pcase form (`(quote ,arg) (when (consp arg) (let ((i 0) (p (cons 1 path))) (dolist (elem arg) (when elem (funcall fun elem (cons i p) t)) (setq i (1+ i)))))) (`(list . ,args) (let ((i 1)) (dolist (expr args) (pcase expr ((pred stringp) (funcall fun expr (cons i path) t)) (`(quote ,elem) (when elem (funcall fun elem (cons 1 (cons i path)) t))) (_ (let ((elem (relint--eval-or-nil expr))) (when elem (funcall fun elem (cons i path) nil))))) (setq i (1+ i))))) (`(append . ,args) (let ((i 1)) (dolist (arg args) (relint--eval-list-iter fun arg (cons i path)) (setq i (1+ i))))) (`(,'\` ,args) (when (consp args) (let ((i 0)) (let ((p0 (cons 1 path))) (dolist (arg args) (let* ((expanded (relint--eval-or-nil (list '\` arg))) (values (if (and (consp arg) (eq (car arg) '\,@)) expanded (list expanded))) (p (cons i p0))) (dolist (elem values) (when elem (funcall fun elem p (equal arg expanded))))) (setq i (1+ i))))))) (`(eval-when-compile ,expr) (relint--eval-list-iter fun expr (cons 1 path))) (_ ;; Fall back on `relint--eval-list', giving up on ;; element-specific source position. (let ((expr (relint--eval-list form))) (when (consp expr) (dolist (elem expr) (funcall fun elem path nil))))))) (defun relint--get-string (form) "Convert something to a string, or nil." (let ((val (relint--eval-or-nil form))) (and (stringp val) val))) (defun relint--check-re (form name pos path) (let ((re (relint--get-string form))) (when re (relint--check-re-string re name pos path)))) (defun relint--check-list (form name pos path is-file-name) "Check a list of regexps." (let ((check (if is-file-name #'relint--check-file-name-re #'relint--check-re-string))) (relint--eval-list-iter (lambda (elem elem-path _literal) (when (stringp elem) (funcall check elem name pos elem-path))) form path))) (defun relint--check-list-any (form name pos path) "Check a list of regexps or conses whose car is a regexp." (relint--eval-list-iter (lambda (elem elem-path literal) (cond ((stringp elem) (relint--check-re-string elem name pos elem-path)) ((and (consp elem) (stringp (car elem))) (relint--check-re-string (car elem) name pos (if literal (cons 0 elem-path) elem-path))))) form path)) (defun relint--check-alist-any (form name pos path) "Check an alist whose cars or cdrs may be regexps." (relint--eval-list-iter (lambda (elem elem-path literal) (when (consp elem) (when (stringp (car elem)) (relint--check-re-string (car elem) name pos (if literal (cons 0 elem-path) elem-path))) (when (stringp (cdr elem)) (relint--check-re-string (cdr elem) name pos (if literal (cons 1 elem-path) elem-path))))) form path)) (defun relint--check-alist-cdr (form name pos path) "Check an alist whose cdrs are regexps." (relint--eval-list-iter (lambda (elem elem-path literal) (when (and (consp elem) (stringp (cdr elem))) (relint--check-re-string (cdr elem) name pos (if literal (cons 1 elem-path) elem-path)))) form path)) (defun relint--check-font-lock-defaults (form name pos path) "Check a value for `font-lock-defaults'." (let ((val (relint--eval-or-nil form))) (when (consp val) (cond ((symbolp (car val)) (unless (memq (car val) relint--checked-variables) (relint--check-font-lock-keywords (car val) name pos path))) ((consp (car val)) (let ((keywords (car val))) (while keywords (when (and (symbolp (car keywords)) (not (memq (car keywords) relint--checked-variables))) (relint--check-font-lock-keywords (car keywords) name pos path)) (setq keywords (cdr keywords))))))))) (defun relint--check-font-lock-keywords (form name pos path) "Check a font-lock-keywords list. A regexp can be found in an element, or in the car of an element." (relint--eval-list-iter (lambda (elem elem-path literal) (cond ((stringp elem) (relint--check-re-string elem name pos elem-path)) ((and (consp elem) (stringp (car elem))) (let* ((tag (and (symbolp (cdr elem)) (cdr elem))) (ident (if tag (list 'at name tag) name)) (p (if literal (cons 0 elem-path) elem-path))) (relint--check-re-string (car elem) ident pos p))))) form path)) (defun relint--check-treesit-query (form name pos path literal) "Recursively check tree-sitter :match regexps in the value FORM. If LITERAL, then PATH is exact." (pcase form (`(,_ (:match ,(and (pred stringp) re) . ,_)) (relint--check-re-string re name pos (if literal (cons 1 (cons 1 path)) path))) (`(,_ ,(pred symbolp) (:match ,(and (pred stringp) re) . ,_)) (relint--check-re-string re name pos (if literal (cons 1 (cons 2 path)) path))) ((pred consp) (let ((i 0)) (while (consp form) (relint--check-treesit-query (car form) name pos (if literal (cons i path) path) literal) (setq form (cdr form)) (setq i (1+ i))))) ((pred vectorp) (dotimes (i (length form)) (relint--check-treesit-query (aref form i) name pos path nil))))) (defun relint--check-treesit-queries (form name pos path) "Evaluate and validate FORM as a list of tree-sitter queries." (relint--eval-list-iter (lambda (elem elem-path literal) (relint--check-treesit-query elem name pos elem-path literal)) form path)) (defun relint--check-treesit-font-lock-rules (form name pos path) "Check tree-sitter font lock queries. Evaluate and validate FORM as an arglist for `treesit-font-lock-rules'." (relint--eval-list-iter (let (skip-next) (lambda (elem elem-path literal) (let ((skip skip-next)) ;; Skip leading plists. (setq skip-next (keywordp elem)) (unless (or skip skip-next) (relint--check-treesit-query elem name pos elem-path literal))))) form path)) (defun relint--check-treesit-indent-rule (form name pos path literal) "Check regexps in tree-sitter indentation matcher FORM." (pcase form ;; Optional arguments. (`(match . ,items) (dotimes (i 3) (when (stringp (car-safe items)) (relint--check-re-string (car items) name pos (if literal (cons (1+ i) path) path))) (setq items (cdr-safe items)))) ;; Required arguments. (`(n-p-gp ,_ ,_ ,_ . ,_) (let ((items (cdr form))) (dotimes (i 3) (when (stringp (car items)) (relint--check-re-string (car items) name pos (if literal (cons (1+ i) path) path))) (setq items (cdr items))))) (`(,(or 'parent-is 'node-is 'field-is) ,(and (pred stringp) re) . ,_) (relint--check-re-string re name pos (if literal (cons 1 path) path))))) (defun relint--check-treesit-indent-rules (form name pos path) "Check tree-sitter indentation rules. Evaluate and validate FORM as a value for `treesit-simple-indent-rules'." (relint--eval-list-iter (lambda (lang lang-path literal) (let ((lang-name (if (consp lang) (list 'at name (car lang)) name)) (rules (cdr-safe lang)) (i 1)) (while (consp rules) (let ((matcher (car-safe (car rules)))) (when matcher (let ((matcher-path (if literal (cons 0 (cons i lang-path)) lang-path))) (relint--check-treesit-indent-rule matcher lang-name pos matcher-path literal)))) (setq rules (cdr rules)) (setq i (1+ i))))) form path)) (defun relint--check-imenu-generic-expression (form name pos path) (relint--eval-list-iter (lambda (elem elem-path literal) (when (and (consp elem) (consp (cdr elem)) (stringp (cadr elem))) (relint--check-re-string (cadr elem) name pos (if literal (cons 1 elem-path) elem-path)))) form path)) (defun relint--check-compilation-error-regexp-alist-alist (form name pos path) (relint--eval-list-iter (lambda (elem elem-path literal) (when (cadr elem) (relint--check-re-string (cadr elem) (list 'at name (car elem)) pos (if literal (cons 1 elem-path) elem-path)))) form path)) (defun relint--check-file-name-re (form name pos path) (let ((re (relint--get-string form))) (when re (relint--check-file-re-string re name pos path)))) (defun relint--check-auto-mode-alist-expr (form name pos path) "Check a single element added to `auto-mode-alist'." (pcase form (`(quote (,(and (pred stringp) str) . ,_)) (relint--check-file-re-string str name pos (cons 0 (cons 1 path)))) (_ (let ((val (relint--eval-or-nil form))) (when (and (consp val) (stringp (car val))) (relint--check-file-re-string (car val) name pos path)))))) (defun relint--check-auto-mode-alist (form name pos path) (relint--eval-list-iter (lambda (elem elem-path literal) (relint--check-file-name-re (car elem) name pos (if literal (cons 0 elem-path) elem-path))) form path)) (defun relint--check-rules-list (form name pos path) "Check a variable on `align-mode-rules-list' format" (relint--eval-list-iter (lambda (rule rule-path literal) (when (and (consp rule) (symbolp (car rule))) (let ((rule-name (car rule)) (i 1)) (dolist (clause (cdr rule)) (when (and (consp clause) (eq (car clause) 'regexp) (stringp (cdr clause))) (relint--check-re-string (cdr clause) (list 'at name rule-name) pos (if literal (cons 1 (cons i rule-path)) rule-path))) (setq i (1+ i)))))) form path)) (defconst relint--known-regexp-variables '( page-delimiter paragraph-separate paragraph-start sentence-end comment-start-skip comment-end-skip treesit-sentence-type-regexp treesit-sexp-type-regexp) "List of known (global or buffer-local) regexp variables.") (defconst relint--known-regexp-returning-functions '(regexp-quote regexp-opt regexp-opt-charset rx rx-to-string wildcard-to-regexp read-regexp char-fold-to-regexp find-tag-default-as-regexp find-tag-default-as-symbol-regexp sentence-end word-search-regexp) "List of functions known to return a regexp.") (defconst relint--known-imenu-variables '(imenu-generic-expression treesit-simple-imenu-settings) "List of known buffer-local Imenu index-defining variables.") ;; List of functions believed to return a regexp. (defvar relint--regexp-returning-functions) (defun relint--regexp-generators (expr expanded) "List of regexp-generating functions and variables used in EXPR. EXPANDED is a list of expanded functions, to prevent recursion." (cond ((symbolp expr) (and (not (memq expr '(nil t))) ;; Check both variable contents and name. (or (let ((def (assq expr relint--variables))) (and def (eq (cadr def) 'expr) (relint--regexp-generators (caddr def) expanded))) (and (or (memq expr relint--known-regexp-variables) ;; This is guesswork, but effective. (string-match-p (rx (or (seq bos (or "regexp" "regex")) (or "-regexp" "-regex" "-re")) eos) (symbol-name expr))) (list expr))))) ((atom expr) nil) ((memq (car expr) relint--regexp-returning-functions) (list (car expr))) ((memq (car expr) ;; These forms never produce regexps at all, but are listed here ;; to prevent false positives since their bodies often do. '(while looking-at re-search-forward re-search-backward string-match string-match-p looking-back looking-at-p replace-regexp query-replace-regexp posix-looking-at posix-search-backward posix-search-forward posix-string-match search-forward-regexp search-backward-regexp kill-matching-buffers keep-lines flush-lines how-many delete-matching-lines delete-non-matching-lines count-matches s-matches? s-matches-p s-matched-positions-all s-count-matches s-count-matches-all)) nil) ((null (cdr (last expr))) (let* ((head (car expr)) (args (if (memq head ;; These forms may generate regexps but the provenance ;; of their first argument is irrelevant. ;; This list, too, could be expanded vastly. '(if when unless replace-regexp-in-string s-match-strings-all s-match s-slice-at s-split s-split-up-to)) (cddr expr) (cdr expr))) (alias (assq head relint--alias-defs))) (if alias (relint--regexp-generators (cons (cdr alias) (cdr expr)) expanded) (append (mapcan (lambda (x) (relint--regexp-generators x expanded)) args) (let ((fun (assq head relint--function-defs))) (and fun (not (memq head expanded)) (mapcan (lambda (x) (relint--regexp-generators x (cons head expanded))) (caddr fun)))))))))) (defun relint--check-non-regexp-provenance (skip-function form pos path) (let ((reg-gen (relint--regexp-generators form nil))) (when reg-gen (relint--warn pos path (format-message "`%s' cannot be used for arguments to `%s'" (car reg-gen) skip-function))))) (defun relint--check-format-mixup (template args pos path) "Look for a format expression that suggests insertion of a regexp into a character alternative: [%s] where the corresponding format parameter is regexp-generating." (let ((nargs (length args)) (index 0) (start 0)) (while (and (< index nargs) (string-match (rx "%" (opt (1+ digit) "$") (0+ (any "+ #" ?-)) (0+ digit) (opt "." (0+ digit)) (group (any "%sdioxXefgcS"))) template start)) (let ((percent (match-beginning 0)) (type (string-to-char (match-string 1 template))) (next (match-end 0))) (when (and (eq type ?s) ;; Find preceding `[' before %s (string-match-p (rx bos (* (or (not (any "\\" "[")) (seq "\\" anything))) "[" (* (not (any "]"))) eos) (substring template start percent))) (let ((reg-gen (relint--regexp-generators (nth index args) nil))) (when reg-gen (relint--warn pos (cons (+ index 2) path) (format-message "Value from `%s' cannot be spliced into `[...]'" (car reg-gen)))))) (unless (eq type ?%) (setq index (1+ index))) (setq start next))))) (defun relint--check-concat-mixup (args pos path) "Look for concat args that suggest insertion of a regexp into a character alternative: `[' followed by a regexp-generating expression." (let ((index 1)) (while (consp args) (let ((arg (car args))) (when (and (stringp arg) (cdr args) (string-match-p (rx (or bos (not (any "\\"))) (0+ "\\\\") "[" (0+ (not (any "]"))) eos) arg)) (let ((reg-gen (relint--regexp-generators (cadr args) nil))) (when reg-gen (relint--warn pos (cons (1+ index) path) (format-message "Value from `%s' cannot be spliced into `[...]'" (car reg-gen))))))) (setq index (1+ index)) (setq args (cdr args))))) (defun relint--pretty-range (from to) (relint--escape-string (if (eq from to) (char-to-string from) (format "%c-%c" from to)) nil)) (defun relint--intersecting-range (from to ranges) "Return a range in RANGES intersecting [FROM,TO], or nil if none. RANGES is a list of (X . Y) representing the interval [X,Y]." (while (and ranges (let ((range (car ranges))) (not (and (<= from (cdr range)) (<= (car range) to))))) (setq ranges (cdr ranges))) (car ranges)) (defun relint--expand-rx-args (items) "Expand unquotes and `eval' and `literal' forms in ITEMS recursively." (if (atom items) items (let ((acc nil) (tail items)) (while (consp tail) (let* ((item (car tail)) (head (car-safe item))) ;; Evaluate unquote and unquote-splicing forms as if inside a ;; (single) backquote. (cond ((memq head '(eval \,)) (let ((val (relint--eval-or-nil (cadr item)))) (pop tail) (if val (push val tail) (push item acc)))) ((eq head 'literal) (let ((val (relint--eval-or-nil (cadr item)))) (pop tail) (push (if (stringp val) (regexp-quote val) item) acc))) ((eq head '\,@) (let ((vals (relint--eval-or-nil (cadr item)))) (pop tail) (if vals (setq tail (append vals tail)) (push item acc)))) ((eq item '\,) ; (... . ,TAIL) = (... , TAIL) (let ((vals (relint--eval-or-nil (cadr tail)))) (pop tail) (if vals (setq tail vals) (push item acc)))) ((and (consp item) (listp (cdr item))) (push (relint--expand-rx-args item) acc) (pop tail)) (t (push item acc) (pop tail))))) (setq acc (nreverse acc)) (if (equal acc items) items acc)))) (defun relint--check-rx (item pos path exact-path) "Check the `rx' expression ITEM. EXACT-PATH indicates whether PATH leads to ITEM exactly, rather than just to a surrounding or producing expression." (let ((expanded (relint--expand-rx-args item))) (relint--check-rx-1 expanded pos path (and exact-path (eq expanded item))))) (defun relint--check-rx-1 (item pos path exact-path) (pcase item (`(,(or 'rx ': 'seq 'sequence 'and 'or '| ; pretend that `rx' is `seq' 'not 'intersection 'repeat '= '>= '** 'zero-or-more '0+ '* '*? 'one-or-more '1+ '+ '+? 'zero-or-one 'opt 'optional '\? ?\s '\?? ?? 'minimal-match 'maximal-match 'group 'submatch 'group-n 'submatch-n) . ,args) (when (memq (car item) '(| or)) ;; Check or-patterns for duplicates, because if rx runs `regexp-opt' ;; on them then they are effectively deduplicated and we'd never ;; know about it. (let ((i 1) (tail args)) (while (consp tail) (when (member (car tail) (cdr tail)) (relint--warn pos (if exact-path (cons i path) path) (format-message "Duplicated rx form in or-pattern: %s" (replace-regexp-in-string (rx (+ (in " \n"))) " " (string-trim (xr-pp-rx-to-str (car tail))) t t)))) (setq i (1+ i)) (setq tail (cdr tail))))) ;; Form with subforms: recurse. (let ((i 1)) (dolist (arg args) (relint--check-rx-1 arg pos (if exact-path (cons i path) path) exact-path) (setq i (1+ i))))) (`(,(or 'any 'in 'char 'not-char) . ,args) ;; We don't bother checking for outright errors like "b-a", but ;; look for mistakes that rx itself doesn't complain about. We ;; assume a hand-written rx expression; machine-generated code ;; can break these rules. (let ((i 1) (classes nil) (ranges nil)) (dolist (arg args) (cond ((characterp arg) (let ((overlap (relint--intersecting-range arg arg ranges))) (when overlap (relint--warn pos (if exact-path (cons i path) path) (if (eq (car overlap) (cdr overlap)) (format-message "Duplicated character `%s'" (relint--pretty-range arg arg)) (format-message "Character `%s' included in range `%s'" (relint--pretty-range arg arg) (relint--pretty-range (car overlap) (cdr overlap))))))) (push (cons arg arg) ranges)) ((stringp arg) (let* ((s (string-to-multibyte arg)) (j 0) (len (length s))) (while (< j len) (let ((from (aref s j))) (if (and (< (+ j 2) len) (eq (aref s (1+ j)) ?-)) ;; Range. (let ((to (aref s (+ j 2)))) (cond ;; When people write "+-X" or "X-+" for some ;; X, they rarely mean a range. ((or (eq from ?+) (eq to ?+)) (relint--warn pos (if exact-path (cons i path) path) (format-message "Suspect range `%s'" (relint--pretty-range from to)) s j (+ j 2))) ((= to from) (relint--warn pos (if exact-path (cons i path) path) (format-message "Single-character range `%s'" (relint--escape-string (format "%c-%c" from to) nil)) s j j)) ((= to (1+ from)) (relint--warn pos (if exact-path (cons i path) path) (format-message "Two-character range `%s'" (relint--pretty-range from to)) s j (+ j 2)))) ;; Take care to split ASCII-raw ranges; they do not ;; include anything in-between. (let* ((split (and (<= from #x7f) (>= to #x3fff80))) (overlap (if split (or (relint--intersecting-range from #x7f ranges) (relint--intersecting-range #x3fff80 to ranges)) (relint--intersecting-range from to ranges)))) (when overlap (relint--warn pos (if exact-path (cons i path) path) (format-message "Range `%s' overlaps previous `%s'" (relint--pretty-range from to) (relint--pretty-range (car overlap) (cdr overlap))) s j (+ j 2))) (if split (progn (push (cons from #x7f) ranges) (push (cons #x3fff80 to) ranges)) (push (cons from to) ranges))) (setq j (+ j 3))) ;; Single character. (when (and (eq from ?-) (< 0 j (1- len))) (relint--warn pos (if exact-path (cons i path) path) (format-message "Literal `-' not first or last") s j j)) (let ((overlap (relint--intersecting-range from from ranges))) (when overlap (relint--warn pos (if exact-path (cons i path) path) (if (eq (car overlap) (cdr overlap)) (format-message "Duplicated character `%s'" (relint--pretty-range from from)) (format-message "Character `%s' included in range `%s'" (relint--pretty-range from from) (relint--pretty-range (car overlap) (cdr overlap)))) s j j))) (push (cons from from) ranges) (setq j (1+ j))))))) ((consp arg) (let ((from (car arg)) (to (cdr arg))) (when (and (characterp from) (characterp to) (<= from to)) (let ((overlap (relint--intersecting-range from to ranges))) (when overlap (relint--warn pos (if exact-path (cons i path) path) (format-message "Range `%s' overlaps previous `%s'" (relint--pretty-range from to) (relint--pretty-range (car overlap) (cdr overlap)))))) (push (cons from to) ranges)))) ((symbolp arg) (when (memq arg classes) (relint--warn pos (if exact-path (cons i path) path) (format-message "Duplicated class `%s'" arg))) (push arg classes))) (setq i (1+ i))))) (`(,(or 'regexp 'regex) ,expr) (relint--check-re expr (format-message "rx `%s' form" (car item)) pos (if exact-path (cons 1 path) path))))) (defun relint--regexp-args-from-doc (doc-string) "Extract regexp arguments (as a list of symbols) from DOC-STRING." (let ((start 0) (found nil)) (while (string-match (rx (any "rR") (or (seq "egex" (opt "p")) (seq "egular" (+ (any " \n\t")) "expression")) (+ (any " \n\t")) (group (+ (any "A-Z" ?-)))) doc-string start) (push (intern (downcase (match-string 1 doc-string))) found) (setq start (match-end 0))) found)) (defun relint--check-form-recursively-1 (form pos path) (pcase form (`(,(or 'defun 'defmacro 'defsubst) ,name ,args . ,body) (when (symbolp name) (let ((doc-args nil)) (when (string-match-p (rx (or "-regexp" "-regex" "-re") eos) (symbol-name name)) (push name relint--regexp-returning-functions)) ;; Examine doc string if any. (when (stringp (car body)) (setq doc-args (relint--regexp-args-from-doc (car body))) (when (and (not (memq name relint--regexp-returning-functions)) (let ((case-fold-search t)) (string-match-p (rx (or bos (seq (or "return" "generate" "make") (opt "s") (+ (any " \n\t")))) (opt (or "a" "the") (+ (any " \n\t"))) (or "regex" (seq "regular" (+ (any " \n\t")) "expression"))) (car body)))) (push name relint--regexp-returning-functions)) (setq body (cdr body))) ;; Skip declarations. (while (and (consp (car body)) (memq (caar body) '(interactive declare))) (setq body (cdr body))) ;; Save the function or macro for possible use. (push (list name args body) (if (eq (car form) 'defmacro) relint--macro-defs relint--function-defs)) ;; If any argument looks like a regexp, remember it so that it can be ;; checked in calls. (when (consp args) (let ((indices nil) (index 0)) (while args (let ((arg (car args))) (when (symbolp arg) (cond ((eq arg '&optional)) ; Treat optional args as regular. ((eq arg '&rest) (setq args nil)) ; Ignore &rest args. (t (when (or (memq arg doc-args) (string-match-p (rx (or (or "regexp" "regex" "-re" "pattern") (seq bos "re")) eos) (symbol-name arg))) (push index indices)) (setq index (1+ index))))) (setq args (cdr args)))) (when indices (push (cons name (reverse indices)) relint--regexp-functions))))))) (`(defalias ,name-arg ,(and def-arg `(,(or 'quote 'function) ,(pred symbolp))) . ,_) ;; Only store and follow aliases on the form (quote SYMBOL) or ;; (function SYMBOL), to avoid infinite recursion. (let ((name (relint--eval-or-nil name-arg)) (def (relint--eval-or-nil def-arg))) (when (and name def ;; Prevent alias loops. (not (eq name def)) (not (assq def relint--alias-defs))) (push (cons name def) relint--alias-defs)))) (_ (let ((index 0)) (while (consp form) (when (consp (car form)) (relint--check-form-recursively-1 (car form) pos (cons index path))) (setq form (cdr form)) (setq index (1+ index))))))) (defun relint--check-defcustom-type (type name pos path) (pcase type (`(,(or 'const 'string 'regexp) . ,rest) (while (consp rest) (cond ((eq (car rest) :value) (relint--check-re (cadr rest) name pos path)) ((not (cdr rest)) (relint--check-re (car rest) name pos path))) (setq rest (cddr rest)))) (`(,(or 'choice 'radio) . ,choices) (dolist (choice choices) (relint--check-defcustom-type choice name pos path))))) (defun relint--check-defcustom-re (form name pos path) (let ((args (nthcdr 4 form)) (index 5)) (while (consp args) (pcase args (`(:type ,type) (relint--check-defcustom-type (relint--eval-or-nil type) name pos (cons index path))) (`(:options ,options) (relint--check-list options name pos (cons index path) nil))) (setq index (+ index 2)) (setq args (cddr args))))) (defun relint--defcustom-type-regexp-p (type) "Whether the defcustom type TYPE indicates a regexp." (pcase type ('regexp t) (`(regexp . ,_) t) (`(string :tag ,tag . ,_) (let ((case-fold-search t)) (string-match-p (rx bos (opt (or "the" "a") " ") (or "regex" "regular expression")) tag))) (`(,(or 'choice 'radio) . ,rest) (cl-some #'relint--defcustom-type-regexp-p rest)))) (defun relint--check-and-eval-let-binding (binding mutables pos path) "Check the let-binding BINDING, which is probably (NAME EXPR) or NAME, and evaluate EXPR. On success return (NAME VALUE); if evaluation failed, return (NAME); on syntax error, return nil." (cond ((symbolp binding) (cons binding (list nil))) ((and (consp binding) (symbolp (car binding)) (consp (cdr binding))) (when (consp (cadr binding)) (relint--check-form-recursively-2 (cadr binding) mutables pos (cons 1 path))) (let ((val (catch 'relint-eval (list (relint--eval (cadr binding)))))) (when (and (consp val) (stringp (car val)) (memq (car binding) relint--known-regexp-variables)) ;; Setting a special buffer-local regexp. (relint--check-re (car val) (car binding) pos (cons 1 path))) (cons (car binding) (if (eq val 'no-value) nil val)))))) (defun relint--check-form-recursively-2 (form mutables pos path) "Check FORM (at POS, PATH) recursively. MUTABLES is a list of lexical variables in a scope which FORM may mutate directly." (let ((head (car form)) (args (cdr form))) (cond ((not (relint--proper-list-p args)) ;; Dotted list: just recurse. (let ((index 0)) (while (consp form) (when (consp (car form)) (relint--check-form-recursively-2 (car form) nil pos (cons index path))) (setq form (cdr form)) (setq index (1+ index))))) ;; now we can assume that `args' is a proper list ((memq head '(let let*)) (let ((bindings (car args))) (when (listp bindings) (let* ((relint--locals relint--locals) (new-bindings nil) (let* (eq head 'let*)) (i 0) (bindings-path (cons 1 path)) (body-mutables mutables)) (while (consp bindings) (let ((b (relint--check-and-eval-let-binding (car bindings) mutables pos (cons i bindings-path)))) (when b (push b (if let* relint--locals new-bindings)) (push (car b) body-mutables)) (setq i (1+ i)) (setq bindings (cdr bindings)))) (when new-bindings (setq relint--locals (append (nreverse new-bindings) relint--locals))) (let ((index 2) (body (cdr args))) (while body (when (consp (car body)) (relint--check-form-recursively-2 (car body) body-mutables pos (cons index path))) (setq body (cdr body)) (setq index (1+ index)))))))) ((memq head '(setq setq-local)) ;; Only mutate lexical variables in the mutation list, which means ;; that this form will be executed exactly once during their remaining ;; lifetime. Other lexical vars will just be invalidated since we ;; don't know anything about the control flow. (let ((i 2)) (while (and (cdr args) (symbolp (car args))) (let ((name (car args)) (expr (cadr args))) (when (consp expr) (relint--check-form-recursively-2 expr mutables pos (cons i path))) (cond ((memq name relint--known-regexp-variables) (relint--check-re expr name pos (cons i path))) ((string-match-p (rx "font-lock-keywords") (symbol-name name)) (relint--check-font-lock-keywords expr name pos (cons i path))) ((eq name 'font-lock-defaults) (relint--check-font-lock-defaults expr name pos (cons i path))) ((memq name relint--known-imenu-variables) (relint--check-imenu-generic-expression expr name pos (cons i path))) ((eq name 'auto-mode-alist) (pcase expr (`(cons ,item auto-mode-alist) (relint--check-auto-mode-alist-expr item name pos (cons 1 (cons i path)))) (`(append ,items auto-mode-alist) (relint--check-auto-mode-alist items name pos (cons 1 (cons i path)))))) ((eq name 'treesit-simple-indent-rules) (relint--check-treesit-indent-rules expr name pos (cons i path))) ((eq name 'treesit-defun-type-regexp) (let* ((val (relint--eval-or-nil expr)) (re (or (car-safe val) val))) (when (stringp re) (relint--check-re-string re name pos (cons i path))))) (t ;; Invalidate the variable if it was local; otherwise, ignore. (let ((local (assq name relint--locals))) (when local (setcdr local (and (memq name mutables) (let ((val (catch 'relint-eval (list (relint--eval expr))))) (and (not (eq val 'no-value)) val))))))))) (setq args (cddr args)) (setq i (+ i 2))))) ((eq head 'push) (let ((expr (nth 0 args)) (name (nth 1 args))) (when (consp expr) (relint--check-form-recursively-2 expr mutables pos (cons 1 path))) (when (symbolp name) ;; Treat (push EXPR NAME) as (setq NAME (cons EXPR NAME)). (when (eq name 'auto-mode-alist) (relint--check-auto-mode-alist-expr expr name pos (cons 1 path))) (let ((local (assq name relint--locals))) (when local (setcdr local (let ((old-val (cdr local))) (and old-val (memq name mutables) (let ((val (catch 'relint-eval (list (cons (relint--eval expr) (car old-val)))))) (and (consp val) val)))))))))) ((eq head 'pop) (let ((name (car args))) (when (symbolp name) ;; Treat (pop NAME) as (setq NAME (cdr NAME)). (let ((local (assq name relint--locals))) (when (and local (memq name mutables)) (let ((old-val (cadr local))) (when (consp old-val) (setcdr local (list (cdr old-val)))))))))) ((memq head '(if and or when unless)) (let ((arg1 (car args)) (rest (cdr args))) (when (consp arg1) ;; Only first arg is executed unconditionally. ;; FIXME: A conditional in the tail position of its environment binding ;; has the exactly-once property wrt its body; use it! (relint--check-form-recursively-2 arg1 mutables pos (cons 1 path))) (let ((i 2)) (while rest (when (consp (car rest)) (relint--check-form-recursively-2 (car rest) nil pos (cons i path))) (setq rest (cdr rest)) (setq i (1+ i)))))) ((memq head '( defun defsubst defmacro)) (let ((arglist (cadr args)) (body (cddr args))) (when (listp arglist) ;; Create local bindings for formal arguments (with unknown values). (let* ((argnames (mapcan (lambda (arg) (and (symbolp arg) (not (memq arg '(&optional &rest))) (list arg))) arglist)) (relint--locals (append (mapcar #'list argnames) relint--locals))) (let ((i 3)) (while body (when (consp (car body)) (relint--check-form-recursively-2 (car body) argnames pos (cons i path))) (setq body (cdr body)) (setq i (1+ i)))))))) ((eq head 'lambda) (let ((arglist (car args)) (body (cdr args))) (when (listp arglist) ;; Create local bindings for formal arguments (with unknown values). (let* ((argnames (mapcan (lambda (arg) (and (symbolp arg) (not (memq arg '(&optional &rest))) (list arg))) arglist)) (relint--locals (append (mapcar #'list argnames) relint--locals))) (let ((i 2)) (while body (when (consp (car body)) (relint--check-form-recursively-2 (car body) argnames pos (cons i path))) (setq body (cdr body)) (setq i (1+ i)))))))) (t (cond ((memq head '( looking-at re-search-forward re-search-backward string-match string-match-p looking-back looking-at-p replace-regexp-in-string replace-regexp query-replace-regexp posix-looking-at posix-search-backward posix-search-forward posix-string-match search-forward-regexp search-backward-regexp kill-matching-buffers keep-lines flush-lines how-many delete-matching-lines delete-non-matching-lines count-matches ;; From s.el s-matches? s-matches-p s-match-strings-all s-matched-positions-all s-match s-slice-at s-count-matches s-count-matches-all s-split s-split-up-to)) (let ((re-arg (car args))) (unless (and (symbolp re-arg) (memq re-arg relint--checked-variables)) (relint--check-re re-arg (cons 'call-to (car form)) pos (cons 1 path))))) ((eq head 'load-history-filename-element) (let ((re-arg (car args))) (relint--check-file-name-re re-arg (cons 'call-to (car form)) pos (cons 1 path)))) ((eq head 'directory-files-recursively) (let ((re-arg (cadr args))) (relint--check-file-name-re re-arg (cons 'call-to (car form)) pos (cons 2 path)))) ((memq head '( split-string split-string-and-unquote string-trim-left string-trim-right string-trim treesit-induce-sparse-tree treesit-search-forward treesit-search-forward-goto treesit-search-subtree)) (let ((re-arg (cadr args)) (rest (cddr args))) (unless (and (symbolp re-arg) (memq re-arg relint--checked-variables)) (relint--check-re re-arg (cons 'call-to (car form)) pos (cons 2 path))) ;; string-trim has another regexp argument (trim-right, arg 3) (when (and (eq (car form) 'string-trim) (car rest)) (let ((right (car rest))) (unless (and (symbolp right) (memq right relint--checked-variables)) (relint--check-re right (cons 'call-to (car form)) pos (cons 3 path))))) ;; split-string has another regexp argument (trim, arg 4) (when (and (eq (car form) 'split-string) (cadr rest)) (let ((trim (cadr rest))) (unless (and (symbolp trim) (memq trim relint--checked-variables)) (relint--check-re trim (cons 'call-to (car form)) pos (cons 4 path))))))) ((memq head '(directory-files directory-files-and-attributes)) (let ((re-arg (caddr args))) (relint--check-file-name-re re-arg (cons 'call-to (car form)) pos (cons 3 path)))) ((eq head 'sort-regexp-fields) (let ((record-arg (cadr args)) (key-arg (caddr args)) (name (cons 'call-to (car form)))) (relint--check-re record-arg name pos (cons 2 path)) (let ((key-re (relint--eval-or-nil key-arg))) (when (and (stringp key-re) (not (equal key-re "\\&"))) (relint--check-re key-re name pos (cons 3 path)))))) ((memq head '(skip-chars-forward skip-chars-backward)) (let* ((skip-arg (car args)) (str (relint--get-string skip-arg))) (when str (relint--check-skip-set str (cons 'call-to (car form)) pos (cons 1 path))) (relint--check-non-regexp-provenance (car form) skip-arg pos (cons 1 path)))) ((memq head '(skip-syntax-forward skip-syntax-backward)) (let* ((arg (car args)) (str (relint--get-string arg))) (when str (relint--check-syntax-string str (cons 'call-to (car form)) pos (cons 1 path))) (relint--check-non-regexp-provenance (car form) arg pos (cons 1 path)))) ((eq head 'concat) (relint--check-concat-mixup args pos path)) ((eq head 'format) (let* ((template-arg (car args)) (template (relint--get-string template-arg))) (when template (relint--check-format-mixup template (cdr args) pos path)))) ((memq head '( defvar defconst defcustom)) (let* ((name (car args)) (re-arg (cadr args)) (rest (cddr args)) (type (and (eq (car form) 'defcustom) (relint--eval-or-nil (plist-get (cdr rest) :type))))) (when (and (symbolp name) (cdr args)) (cond ((or (relint--defcustom-type-regexp-p type) (string-match-p (rx (or "-regexp" "-regex" "-re" "-pattern") eos) (symbol-name name))) (relint--check-re re-arg name pos (cons 2 path)) (when (eq (car form) 'defcustom) (relint--check-defcustom-re form name pos path)) (push name relint--checked-variables)) ((and (consp type) (eq (car type) 'alist) (relint--defcustom-type-regexp-p (plist-get (cdr type) :key-type))) (relint--check-list-any re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((and (consp type) (eq (car type) 'alist) (relint--defcustom-type-regexp-p (plist-get (cdr type) :value-type))) (relint--check-alist-cdr re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((or (and (consp type) (eq (car type) 'repeat) (relint--defcustom-type-regexp-p (cadr type))) (string-match-p (rx (or (or "-regexps" "-regexes") (seq (or "-regexp" "-regex" "-re") "-list")) eos) (symbol-name name))) (relint--check-list re-arg name pos (cons 2 path) nil) (push name relint--checked-variables)) ((string-match-p (rx "font-lock-keywords") (symbol-name name)) (relint--check-font-lock-keywords re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((eq name 'compilation-error-regexp-alist-alist) (relint--check-compilation-error-regexp-alist-alist re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((eq name 'auto-mode-alist) (relint--check-auto-mode-alist re-arg name pos (cons 2 path))) ((string-match-p (rx (or "-regexp" "-regex" "-re" "-pattern") "-alist" eos) (symbol-name name)) (relint--check-alist-any re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((string-suffix-p "-mode-alist" (symbol-name name)) (relint--check-list-any re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((string-suffix-p "-rules-list" (symbol-name name)) (relint--check-rules-list re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ((string-match-p (rx (or (seq (or bos ?-) "treesit") (seq "-ts" (opt "-mode") (opt ?-))) "-font-lock-" (or "rules" "settings") eos) (symbol-name name)) (relint--check-treesit-font-lock-rules re-arg name pos (cons 2 path)) (push name relint--checked-variables)) ;; Doc string starting with "regexp" etc. ((and (stringp (car rest)) (let ((case-fold-search t)) (string-match-p (rx bos (opt (or "when" "if") (* " ") (or "not" "non") (* (any "- ")) "nil" (* (any " ,"))) (opt (or "specify" "specifies") " ") (opt (or "a" "the" "this") " ") (or "regex" "regular expression")) (car rest)))) (relint--check-re re-arg name pos (cons 2 path)) (when (eq (car form) 'defcustom) (relint--check-defcustom-re form name pos path)) (push name relint--checked-variables)) ) (let* ((old (assq name relint--variables)) (new (or (and old ;; Redefinition of the same variable: eagerly ;; evaluate the new expression in case it uses ;; the old value. (let ((val (catch 'relint-eval (list (relint--eval re-arg))))) (and (consp val) (cons 'val val)))) (list 'expr re-arg)))) (push (cons name new) relint--variables))))) ((eq head 'rx) (relint--check-rx form pos path t)) ((eq head 'rx-to-string) (when (memq (car-safe (car-safe args)) '(quote \`)) (let ((arg (cadar args))) (relint--check-rx arg pos (cons 1 (cons 1 path)) t)))) ((eq head 'font-lock-add-keywords) (let ((keywords (cadr args))) (relint--check-font-lock-keywords keywords (car form) pos (cons 2 path)))) ((memq head '(treesit-font-lock-rules treesit-range-rules)) (let ((items args) (i 1)) (while items (if (not (and (keywordp (car items)) (cdr items))) (relint--check-treesit-queries (car items) (cons 'call-to (car form)) pos (cons i path)) ;; Skip leading plists. (setq items (cdr items)) (setq i (1+ i))) (setq items (cdr items)) (setq i (1+ i))))) ((eq head 'treesit-query-expand) (let ((query (car args))) (relint--check-treesit-queries query (cons 'call-to (car form)) pos (cons 1 path)))) ((memq head '( treesit-node-top-level treesit-query-capture treesit-query-compile treesit-query-range treesit-query-string)) (let ((query (cadr args))) (relint--check-treesit-queries query (cons 'call-to (car form)) pos (cons 2 path)))) ((eq head 'set) (pcase args (`((make-local-variable ',(and (pred symbolp) name)) ,expr . ,_) (cond ((memq name relint--known-regexp-variables) (relint--check-re expr name pos (cons 2 path))) ((eq name 'font-lock-defaults) (relint--check-font-lock-defaults expr name pos (cons 2 path))) ((string-match-p (rx "font-lock-keywords") (symbol-name name)) (relint--check-font-lock-keywords expr name pos (cons 2 path))) ((memq name relint--known-imenu-variables) (relint--check-imenu-generic-expression expr name pos (cons 2 path))))))) ((eq head 'define-generic-mode) (let* ((name (nth 0 args)) (font-lock-list (nth 3 args)) (auto-mode-list (nth 4 args)) (origin (format "define-generic-mode %s" name))) (relint--check-font-lock-keywords font-lock-list origin pos (cons 4 path)) (relint--check-list auto-mode-list origin pos (cons 5 path) t))) ((memq head '( syntax-propertize-rules syntax-propertize-precompile-rules)) (let ((rules args) (name (cons 'call-to (car form))) (index 1)) (dolist (item rules) (when (consp item) (relint--check-re (car item) name pos (cons 0 (cons index path)))) (setq index (1+ index))))) ((eq head 'add-to-list) (when (equal (car args) ''auto-mode-alist) (let ((elem (cadr args))) (relint--check-auto-mode-alist-expr elem (car form) pos (cons 2 path))))) ((eq head 'modify-coding-system-alist) (let ((type (nth 0 args)) (re-arg (nth 1 args))) (funcall (if (eq (relint--eval-or-nil type) 'file) #'relint--check-file-name-re #'relint--check-re) re-arg (cons 'call-to (car form)) pos (cons 2 path)))) (t (let ((alias (assq head relint--alias-defs))) (when alias (relint--check-form-recursively-2 (cons (cdr alias) args) mutables pos path))) ;; Check calls to remembered functions with regexp arguments. (let ((indices (cdr (assq head relint--regexp-functions)))) (when indices (let ((index 0)) (while (and indices args) (when (= index (car indices)) (unless (and (symbolp (car args)) (memq (car args) relint--checked-variables)) (relint--check-re (car args) (cons 'call-to (car form)) pos (cons (1+ index) path))) (setq indices (cdr indices))) (setq args (cdr args)) (setq index (1+ index))))))) ) ;; FIXME: All function applications, and some macros / special forms ;; (prog{1,2,n}, save-excursion...) could be scanned with full ;; mutables since all args are evaluated once. (let ((index 0)) (while form (cond ((consp (car form)) ;; Check subforms with the assumption that nothing can be mutated, ;; since we don't really know what is evaluated when. (relint--check-form-recursively-2 (car form) nil pos (cons index path))) ((and (memq (car form) '(:regexp :regex)) (cdr form)) (relint--check-re (cadr form) (cons 'parameter (car form)) pos (cons (1+ index) path)))) (setq form (cdr form)) (setq index (1+ index)))))))) (defun relint--show-errors (error-buffer quiet) (unless (or noninteractive quiet (not error-buffer)) (let ((pop-up-windows t)) (display-buffer error-buffer) (sit-for 0)))) (defun relint--read-buffer () "Read top-level forms from the current buffer. Return a list of (FORM . STARTING-POSITION)." (goto-char (point-min)) (let ((pos nil) (keep-going t) (read-circle nil) (forms nil)) (while keep-going (setq pos (point)) (let ((form nil)) (condition-case err (setq form (read (current-buffer))) (end-of-file (setq keep-going nil)) (invalid-read-syntax (cond ((let ((errstr (cadr err))) (and (stringp errstr) (string-prefix-p "#" errstr))) (goto-char pos) (forward-sexp 1)) (t (relint--err-at-pos (point) (prin1-to-string err)) (setq keep-going nil)))) (error (relint--err-at-pos (point) (prin1-to-string err)) (setq keep-going nil))) (when (consp form) (push (cons form pos) forms)))) (nreverse forms))) (defun relint--in-doc-string-p (pos) "Whether the string literal starting at POS is a doc string." (save-excursion (goto-char pos) ;; Go back to start of containing sexp, counting the steps. (let ((steps 0)) (while (and (not (bobp)) (ignore-errors (forward-sexp -1) t) (not (and (= steps 0) (looking-at (rx ":documentation" symbol-end))))) (setq steps (1+ steps))) (or (and (= steps 0) (looking-at (rx ":documentation"))) (and (= steps 3) (looking-at (rx (or "defun" "defmacro" "defsubst" "defalias" "defconst" "defvar" "defcustom" "autoload" "cl-defun" "cl-defmacro" "cl-defmethod" "ert-deftest" ;; Specific to cc-mode "c-lang-defvar" ;; Specific to gnus "defvoo")))) (and (= steps 2) (looking-at (rx (or "define-major-mode" "define-minor-mode" ;; Specific to cc-mode "c-lang-defconst")))) (and (= steps 4) (looking-at (rx "define-derived-mode"))))))) (defun relint--suspicious-backslash (string-start) "With point at an ineffective backslash, emit an warning unless filtered out. STRING-START is the start of the string literal (first double quote)." (let ((c (char-after (1+ (point))))) ;; Filter out escaped round and square brackets and apostrophes ;; inside doc strings, as well as anything in the leftmost column: ;; common for historical reasons and less likely to be mistakes. (unless (or (bolp) (and (memq c '(?\( ?\) ?\[ ?\] ?\')) (relint--in-doc-string-p string-start))) (relint--warn-at-pos (point) (1+ (point)) (if (eq c ?x) (format-message "Character escape `\\x' not followed by hex digit") (format-message "Ineffective string escape `\\%s'" (relint--escape-string (char-to-string c) nil))))))) (defun relint--check-for-misplaced-backslashes () "Check for misplaced backslashes in the current buffer." (goto-char (point-min)) (while (not (eobp)) (while (looking-at (rx (or (+ (not (any ?\" ?\; ?? ?\\))) (seq ";" (0+ nonl)) (seq "?" (or (seq ?\\ anything) (not (any ?\\)))) (seq "\\" anything)))) (goto-char (match-end 0))) (when (eq (following-char) ?\") (let ((string-start (point))) (forward-char) (while (not (memq (char-after) '(?\" nil))) (when (looking-at (rx (1+ (or (seq ?\\ (or (any "0-7" "uUN" "abfnrtv" "des" "^" " " ?\\ ?\n ?\" "CM") (seq "x" xdigit))) (not (any ?\\ ?\")))))) (goto-char (match-end 0))) (when (eq (following-char) ?\\) (relint--suspicious-backslash string-start) (forward-char 2))) (unless (eobp) (forward-char 1)))))) (defalias 'relint--sort-with-key (if (>= emacs-major-version 30) (lambda (key-fun list) (with-suppressed-warnings ((callargs sort)) ; hush emacs <30 (sort list :key key-fun :in-place t))) (lambda (key-fun list) (mapcar #'cdr (sort (mapcar (lambda (x) (cons (funcall key-fun x) x)) list) #'car-less-than-car)))) "Sort LIST using KEY-FUN for each element as the key. The keys are sorted numerically, in ascending order.") (defun relint--scan-current-buffer () (let* ((relint--suppression-count 0) (relint--complaints nil) (forms (relint--read-buffer)) (relint--variables nil) (relint--checked-variables nil) (relint--regexp-functions nil) (relint--regexp-returning-functions relint--known-regexp-returning-functions) (relint--function-defs nil) (relint--macro-defs nil) (relint--alias-defs nil) (relint--locals nil) (case-fold-search nil)) (dolist (form forms) (relint--check-form-recursively-1 (car form) (cdr form) nil)) (dolist (form forms) (relint--check-form-recursively-2 (car form) nil (cdr form) nil)) (relint--check-for-misplaced-backslashes) (let ((complaints (nreverse relint--complaints))) (cons (relint--sort-with-key (lambda (g) (relint-diag-beg-pos (car g))) complaints) relint--suppression-count)))) (defvar relint-last-target nil "The last file, directory or buffer on which relint was run.") (defun relint--prepare-error-buffer (target base-dir error-buffer quiet) (when error-buffer (with-current-buffer error-buffer (unless quiet (let ((inhibit-read-only t)) (insert (format "Relint results for %s\n" target))) (relint--show-errors error-buffer quiet)) (setq relint-last-target target) (setq default-directory base-dir)))) (defun relint--finish (errors suppressed error-buffer quiet) (let* ((problems (- errors suppressed)) (msg (format "%d problem%s%s" problems (if (= problems 1) "" "s") (if (zerop suppressed) "" (format " (%s suppressed)" suppressed))))) (unless (or quiet (and noninteractive (zerop errors))) (unless noninteractive ;; Only set `next-error-last-buffer' when there is an actual ;; diagnostic to jump to, to avoid unnecessarily losing track ;; of the previous next-error buffer if any. (when (and error-buffer (> errors suppressed)) (setq next-error-last-buffer error-buffer)) (relint--add-to-error-buffer error-buffer (format "\nFinished -- %s.\n" msg))) (message "relint: %s." msg)))) (defun relint-again () "Re-run relint on the same file, directory or buffer as last time." (interactive) (cond ((bufferp relint-last-target) (with-current-buffer relint-last-target (relint-current-buffer))) ((file-directory-p relint-last-target) (relint-directory relint-last-target)) ((file-readable-p relint-last-target) (relint-file relint-last-target)) (t (error "No target")))) (defvar relint-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map compilation-minor-mode-map) (define-key map "n" #'next-error-no-select) (define-key map "p" #'previous-error-no-select) (define-key map "g" #'relint-again) map) "Keymap for relint buffers.") (define-compilation-mode relint-mode "Relint" "Mode for relint output." (setq-local relint-last-target nil)) (defun relint--scan-files (files target base-dir error-buffer) "Scan FILES in BASE-DIR; return (ERRORS . SUPPRESSED). TARGET is the file or directory to use for a repeated run." (relint--prepare-error-buffer target base-dir error-buffer nil) (let ((total-errors 0) (total-suppressed 0) (nfiles (length files)) (count 0)) (dolist (file files) (when (and (not noninteractive) (zerop (% count 50))) (message "Scanned %d/%d file%s..." count nfiles (if (= nfiles 1) "" "s")) (sit-for 0)) (setq count (1+ count)) (with-temp-buffer (emacs-lisp-mode) (insert-file-contents file) ;; Call file-relative-name lazily -- it is surprisingly expensive ;; on macOS, and the result only used for diagnostics output. (let* ((results (relint--scan-current-buffer)) (complaints (car results)) (suppressed (cdr results))) (when complaints (relint--output-complaints (current-buffer) (file-relative-name file base-dir) complaints error-buffer)) (setq total-errors (+ total-errors (length complaints) suppressed)) (setq total-suppressed (+ total-suppressed suppressed)) (when complaints (relint--show-errors error-buffer nil))))) (relint--finish total-errors total-suppressed error-buffer nil) (cons total-errors total-suppressed))) (defun relint--tree-files (dir) (let ((re (rx bos (not (any ".")) (* anything) ".el" eos))) (directory-files-recursively dir re nil ;; Save time by not pointlessly descending into huge .git directories. (lambda (s) (not (string-suffix-p "/.git" s)))))) (defun relint--scan-buffer (buffer) "Scan BUFFER; return (COMPLAINTS . SUPPRESSED) where COMPLAINTS is a list of (unsuppressed) diagnostics each on the form (MESSAGE BEG-POS END-POS STRING BEG-IDX END-IDX SEVERITY) and SUPPRESSED is the number of suppressed diagnostics." (with-current-buffer buffer (unless (derived-mode-p 'emacs-lisp-mode) (error "Relint: can only scan elisp code (use emacs-lisp-mode)")) (save-excursion (relint--scan-current-buffer)))) (defun relint--buffer (buffer error-buffer quiet) ;; FIXME: With 0 errors, maybe don't pop up the error buffer at all? (let* ((results (relint--scan-buffer buffer)) (complaints (car results)) (suppressed (cdr results)) (errors (+ (length complaints) suppressed))) (relint--prepare-error-buffer buffer default-directory error-buffer quiet) (when complaints (relint--output-complaints buffer (buffer-name buffer) complaints error-buffer)) (relint--finish errors suppressed error-buffer quiet))) ;;;###autoload (defun relint-file (file) "Scan FILE, an elisp file, for regexp-related errors." (interactive "fRelint elisp file: ") (relint--scan-files (list file) file (file-name-directory file) (relint--get-error-buffer))) ;;;###autoload (defun relint-directory (dir) "Scan all *.el files in DIR for regexp-related errors." (interactive "DRelint directory: ") (message "Finding .el files in %s..." dir) (sit-for 0) (let ((files (relint--tree-files dir))) (if files (relint--scan-files files dir dir (relint--get-error-buffer)) (message "No .el files found.")))) ;;;###autoload (defun relint-current-buffer () "Scan the current buffer for regexp errors. The buffer must be in emacs-lisp-mode." (interactive) (relint--buffer (current-buffer) (relint--get-error-buffer) nil)) ;;;###autoload (defun relint-buffer (buffer) "Scan BUFFER for regexp mistakes. Return list of diagnostics. Each element in the returned list is an object with the slots message the message string beg-pos starting position in the buffer end-pos ending position the buffer (inclusive), or nil pos-type if `string', then the buffer at BEG-POS..END-POS is inside a string literal corresponding to STRING at BEG-IDX..END-IDX; otherwise BEG-POS..END-POS just point to code string the string the message is about, or nil beg-idx starting offset in STRING, or nil end-idx ending offset in STRING (inclusive), or nil severity `error', `warning' or `info' Accessors are prefixed by `relint-diag-': eg, (relint-diag-message D) returns the message of object D. BEG-POS..END-POS is the range of interest in the buffer, and may correspond to the range BEG-IDX..END-IDX in STRING but not necessarily so." (let ((groups (car (relint--scan-buffer buffer)))) (apply #'append groups))) ; flatten groups (defun relint-batch () "Scan elisp source files for regexp-related errors. Call this function in batch mode with files and directories as command-line arguments. Files are scanned; directories are searched recursively for *.el files to scan. When done, Emacs terminates with a nonzero status if anything worth complaining about was found, zero otherwise. The output appearance is controlled by the variable `relint-batch-highlight'." (unless noninteractive (error "`relint-batch' is only for use with -batch")) (let* ((err-supp (relint--scan-files (mapcan (lambda (arg) (if (file-directory-p arg) (relint--tree-files arg) (list arg))) command-line-args-left) nil default-directory nil)) (errors (car err-supp)) (suppressed (cdr err-supp))) (setq command-line-args-left nil) (kill-emacs (if (> errors suppressed) 1 0)))) (provide 'relint) ;;; relint.el ends here