[elpa] externals/relint 9259a5c 03/21: Check some :value parameters in defcustom :type clauses
branch: externals/relint commit 9259a5c5e82f75aa4efd20746c6d0887acd64e0f Author: Mattias Engdegård Commit: Mattias Engdegård Check some :value parameters in defcustom :type clauses For example, this catches :type '(string :value "some bad regexp") --- relint.el | 13 +++-- test/1.elisp| 13 + test/1.expected | 30 +- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/relint.el b/relint.el index bc4a9af..d484116 100644 --- a/relint.el +++ b/relint.el @@ -1551,12 +1551,13 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." (defun relint--check-defcustom-type (type name file pos path) (pcase type -(`(const . ,rest) - ;; Skip keywords. - (while (and rest (symbolp (car rest))) - (setq rest (cddr rest))) - (when rest - (relint--check-re (car rest) name file pos path))) +(`(,(or 'const 'string 'regexp) . ,rest) + (while (consp rest) + (cond ((eq (car rest) :value) + (relint--check-re (cadr rest) name file pos path)) + ((not (cdr rest)) + (relint--check-re (car rest) name file pos path))) + (setq rest (cddr rest (`(,(or 'choice 'radio) . ,choices) (dolist (choice choices) (relint--check-defcustom-type choice name file pos path) diff --git a/test/1.elisp b/test/1.elisp index 865d21a..c00681a 100644 --- a/test/1.elisp +++ b/test/1.elisp @@ -75,6 +75,19 @@ :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) diff --git a/test/1.expected b/test/1.expected index 8d1e997..4014a72 100644 --- a/test/1.expected +++ b/test/1.expected @@ -138,30 +138,42 @@ 1.elisp:73:35: In bad-custom-7: Duplicated `a' inside character alternative (pos 2) "[aa]" ..^ -1.elisp:80:11: In compilation-error-regexp-alist-alist (aa): Unescaped literal `^' (pos 1) +1.elisp:80:9: In bad-custom-8: Duplicated `1' inside character alternative (pos 2) + "[11]" + ..^ +1.elisp:80:9: In bad-custom-8: Duplicated `2' inside character alternative (pos 2) + "[22]" + ..^ +1.elisp:85:9: In bad-custom-9-regexp: Duplicated `3' inside character alternative (pos 2) + "[33]" + ..^ +1.elisp:89:9: In bad-custom-10: Duplicated `4' inside character alternative (pos 2) + "[44]" + ..^ +1.elisp:93:11: In compilation-error-regexp-alist-alist (aa): Unescaped literal `^' (pos 1) "a^a" .^ -1.elisp:81:11: In compilation-error-regexp-alist-alist (bb): Unescaped literal `$' (pos 1) +1.elisp:94:11: In compilation-error-regexp-alist-alist (bb): Unescaped literal `$' (pos 1) "b$b" .^ -1.elisp:86:8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) +1.elisp:99:8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) "1^" .^ -1.elisp:87:8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) +1.elisp:100:8: In define-generic-mode my-mode: Unescaped literal `^' (pos 1) "2^" .^ -1.elisp:88:12: In define-generic-mode my-mode: Repetition of repetition (pos 2) +1.elisp:101:12: In define-generic-mode my-mode: Repetition of repetition (pos 2) "b++" ..^ -1.elisp:94:6: In call to syntax-propertize-rules: Unescaped literal `$' (pos 0) +1.elisp:107:6: In call to syntax-propertize-rules: Unescaped literal `$' (pos 0) "$1$" ^ -1.elisp:95:8: In call to syntax-propertize-rules: Unescaped literal `^' (pos 2) +1.elisp:108:8: In call to syntax-propertize-rules: Unescaped literal `^' (pos 2) "^2^" ..^ -1.elisp:100:6: In call to syntax-propertize-precompile-rules: Unescaped literal `$' (pos 0) +1.elisp:113:6: In call to syntax-propertize-precompile-rules: Unescaped literal `$' (pos 0) "$3$" ^ -1.elisp:101:8: In call to syntax-propertize-precompile-rules: Unescaped literal `^' (pos 2) +1.elisp:114:8: In call to syntax-propertize-precompile-rules: Unescaped literal `^' (pos 2) "^4^" ..^
[elpa] externals/relint 09ef3df 05/21: Describe the new xr wrapped subsumption warning
branch: externals/relint commit 09ef3dfa9c31317bf508ab18eb0d8f1e31c0b568 Author: Mattias Engdegård Commit: Mattias Engdegård Describe the new xr wrapped subsumption warning --- README | 11 +++ 1 file changed, 11 insertions(+) diff --git a/README b/README index 944e665..3ca9499 100644 --- a/README +++ b/README @@ -122,6 +122,17 @@ skip-syntax-backward. 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. + - Uncounted repetition The construct A\{,\} repeats A zero or more times which was
[elpa] externals/relint 5d3f78d 19/21: Update xr messages ("repetition" changed to "option")
branch: externals/relint commit 5d3f78da32b03b8926076c10426366e07fd0f318 Author: Mattias Engdegård Commit: Mattias Engdegård Update xr messages ("repetition" changed to "option") --- README | 14 +++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README b/README index 34a7b5b..7d25c5e 100644 --- a/README +++ b/README @@ -71,11 +71,17 @@ skip-syntax-backward. 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*+ (? counts as repetition here). Such -expressions can be written with a single repetition and often -indicate a different mistake, such as missing backslashes. +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 @@ -186,12 +192,14 @@ skip-syntax-backward. 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
[elpa] externals/relint f6d0fed 15/21: Describe the new file-specific warnings
branch: externals/relint commit f6d0fedd9bd876c741de17b7bb3db29983c338ab Author: Mattias Engdegård Commit: Mattias Engdegård Describe the new file-specific warnings --- README | 16 1 file changed, 16 insertions(+) diff --git a/README b/README index f816349..34a7b5b 100644 --- a/README +++ b/README @@ -147,6 +147,22 @@ skip-syntax-backward. 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
[elpa] externals/relint 96e26a5 02/21: Check keyword arguments :regexp and :regex
branch: externals/relint commit 96e26a5f8f3967cf5b1e181831e94860f2be6c74 Author: Mattias Engdegård Commit: Mattias Engdegård Check keyword arguments :regexp and :regex --- relint.el | 8 +++- test/2.elisp| 4 test/2.expected | 6 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/relint.el b/relint.el index 567315c..bc4a9af 100644 --- a/relint.el +++ b/relint.el @@ -1965,11 +1965,17 @@ directly." ;; mutables since all args are evaluated once. (let ((index 0)) (while (consp form) - (when (consp (car 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 file pos (cons index path))) + ((and (memq (car form) '(:regexp :regex)) +(consp (cdr form))) + (relint--check-re (cadr form) + (format "%s parameter" (car form)) + file pos (cons (1+ index) path (setq form (cdr form)) (setq index (1+ index))) diff --git a/test/2.elisp b/test/2.elisp index 1a2c4ea..a96cef0 100644 --- a/test/2.elisp +++ b/test/2.elisp @@ -50,3 +50,7 @@ (defun f6 () (f5 "[aa]" "[bb]" "[cc]" "[dd]" "[ee]")) + +(defun f7 () + (alpha beta :regexp "[11]") + (gamma :regex "[22]" delta)) diff --git a/test/2.expected b/test/2.expected index 0bde370..0dbcfc8 100644 --- a/test/2.expected +++ b/test/2.expected @@ -133,3 +133,9 @@ 2.elisp:52:31: In call to f5: Duplicated `d' inside character alternative (pos 2) "[dd]" ..^ +2.elisp:55:26: In :regexp parameter: Duplicated `1' inside character alternative (pos 2) + "[11]" + ..^ +2.elisp:56:20: In :regex parameter: Duplicated `2' inside character alternative (pos 2) + "[22]" + ..^
[elpa] externals/relint a001a05 21/21: Increment version to 1.16
branch: externals/relint commit a001a05a1d692be22f7fac9233e8cc0102aebd30 Author: Mattias Engdegård Commit: Mattias Engdegård Increment version to 1.16 Require xr 1.19 --- relint.el | 10 -- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/relint.el b/relint.el index cbdd340..0651d3b 100644 --- a/relint.el +++ b/relint.el @@ -3,8 +3,8 @@ ;; Copyright (C) 2019-2020 Free Software Foundation, Inc. ;; Author: Mattias Engdegård -;; Version: 1.15 -;; Package-Requires: ((xr "1.17") (emacs "26.1")) +;; Version: 1.16 +;; Package-Requires: ((xr "1.19") (emacs "26.1")) ;; URL: https://github.com/mattiase/relint ;; Keywords: lisp, regexps @@ -29,6 +29,12 @@ ;;; News: +;; 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
[elpa] externals/relint eb178d5 06/21: Check assignments to imenu-generic-expression
branch: externals/relint commit eb178d5f9d529ccc29785e277683faa661e73be4 Author: Mattias Engdegård Commit: Mattias Engdegård Check assignments to imenu-generic-expression --- relint.el | 16 +++- test/9.elisp| 5 + test/9.expected | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/relint.el b/relint.el index 0c1653d..31f1887 100644 --- a/relint.el +++ b/relint.el @@ -1152,6 +1152,14 @@ or in the car of an element." (relint--check-re-string (car elem) ident file pos p) form path)) +(defun relint--check-imenu-generic-expression (form name file 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 file pos (if literal (cons 1 elem-path) elem-path + form path)) + (defun relint--check-compilation-error-regexp-alist-alist (form name file pos path) (relint--eval-list-iter @@ -1701,6 +1709,9 @@ directly." ((memq name '(font-lock-defaults font-lock-keywords)) (relint--check-font-lock-keywords expr name file pos (cons i path))) +((eq name 'imenu-generic-expression) + (relint--check-imenu-generic-expression + expr name file pos (cons i path))) (t ;; Invalidate the variable if it was local; otherwise, ignore. (let ((local (assq name relint--locals))) @@ -1948,7 +1959,10 @@ directly." (relint--check-re expr name file pos (cons 2 path))) ((memq name '(font-lock-defaults font-lock-keywords)) (relint--check-font-lock-keywords expr name - file pos (cons 2 path) + file pos (cons 2 path))) + ((eq name 'imenu-generic-expression) + (relint--check-imenu-generic-expression +expr name file pos (cons 2 path) (`(define-generic-mode ,name ,_ ,_ ,font-lock-list ,auto-mode-list . ,_) (let ((origin (format "define-generic-mode %s" name))) (relint--check-font-lock-keywords font-lock-list origin diff --git a/test/9.elisp b/test/9.elisp index 322334f..4a37174 100644 --- a/test/9.elisp +++ b/test/9.elisp @@ -23,3 +23,8 @@ (setq-local font-lock-defaults '(("[mm]" . tag))) (setq font-lock-defaults '(("[nn]" . tag))) (set (make-local-variable 'font-lock-defaults) '(("[oo]" . tag + +(defun test-9-ge () + (setq-local imenu-generic-expression + '((nil "oh" 0) +("*more*" "+a+" 0 diff --git a/test/9.expected b/test/9.expected index ab3f06e..7bab898 100644 --- a/test/9.expected +++ b/test/9.expected @@ -43,3 +43,6 @@ 9.elisp:25:56: In font-lock-defaults (tag): Duplicated `o' inside character alternative (pos 2) "[oo]" ..^ +9.elisp:30:28: In imenu-generic-expression: Unescaped literal `+' (pos 0) + "+a+" + ^
[elpa] externals/relint cf2a2ae 14/21: Do file-specific checks on arguments to known functions
branch: externals/relint commit cf2a2ae075f132aab97a32fc4d0c8921068217bb Author: Mattias Engdegård Commit: Mattias Engdegård Do file-specific checks on arguments to known functions This includes directory-files, directory-files-and-attributes, directory-files-recursively, load-history-filename-element, and modify-coding-system-alist (with type = file). --- relint.el| 20 +++- test/12.elisp| 9 + test/12.expected | 15 +++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/relint.el b/relint.el index 03a9421..1adcaee 100644 --- a/relint.el +++ b/relint.el @@ -1887,7 +1887,6 @@ directly." 'posix-looking-at 'posix-search-backward 'posix-search-forward 'posix-string-match 'search-forward-regexp 'search-backward-regexp - 'load-history-filename-element 'kill-matching-buffers 'keep-lines 'flush-lines 'how-many) ,re-arg . ,_) @@ -1895,9 +1894,14 @@ directly." (memq re-arg relint--checked-variables)) (relint--check-re re-arg (format "call to %s" (car form)) file pos (cons 1 path + (`(load-history-filename-element ,re-arg) +(relint--check-file-name-re re-arg (format "call to %s" (car form)) +file pos (cons 1 path))) + (`(directory-files-recursively ,_ ,re-arg . ,_) +(relint--check-file-name-re re-arg (format "call to %s" (car form)) +file pos (cons 2 path))) (`(,(or 'split-string 'split-string-and-unquote - 'string-trim-left 'string-trim-right 'string-trim - 'directory-files-recursively) + 'string-trim-left 'string-trim-right 'string-trim) ,_ ,re-arg . ,rest) (unless (and (symbolp re-arg) (memq re-arg relint--checked-variables)) @@ -1921,8 +1925,8 @@ directly." file pos (cons 4 path)) (`(,(or 'directory-files 'directory-files-and-attributes) ,_ ,_ ,re-arg . ,_) -(relint--check-re re-arg (format "call to %s" (car form)) - file pos (cons 3 path))) +(relint--check-file-name-re re-arg (format "call to %s" (car form)) +file pos (cons 3 path))) (`(,(or 'skip-chars-forward 'skip-chars-backward) ,skip-arg . ,_) (let ((str (relint--get-string skip-arg))) @@ -2078,6 +2082,12 @@ directly." (`(add-to-list 'auto-mode-alist ,elem . ,_) (relint--check-auto-mode-alist-expr elem (car form) file pos (cons 2 path))) + (`(modify-coding-system-alist ,type ,re-arg ,_) +(funcall + (if (eq (relint--eval-or-nil type) 'file) + #'relint--check-file-name-re + #'relint--check-re) + re-arg (format "call to %s" (car form)) file pos (cons 2 path))) (`(,name . ,args) (let ((alias (assq name relint--alias-defs))) (when alias diff --git a/test/12.elisp b/test/12.elisp index ea04551..04ea09c 100644 --- a/test/12.elisp +++ b/test/12.elisp @@ -14,3 +14,12 @@ (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)) diff --git a/test/12.expected b/test/12.expected index 6abea93..ecef078 100644 --- a/test/12.expected +++ b/test/12.expected @@ -22,3 +22,18 @@ 12.elisp:15:35: Possibly unescaped `.' in file-matching regexp (pos 0) ".ii\\'" ^ +12.elisp:21:27: Possibly unescaped `.' in file-matching regexp (pos 0) + ".txt\\'" + ^ +12.elisp:22:48: Use \' instead of $ in file-matching regexp (pos 5) + "\\.pas$" + ..^ +12.elisp:23:35: Use \` instead of ^ in file-matching regexp (pos 0) + "^abc" + ^ +12.elisp:24:43: Use \' instead of $ in file-matching regexp (pos 4) + "\\.ml$" + .^ +12.elisp:25:41: In call to modify-coding-system-alist: Unescaped literal `+' (pos 0) + "+xx$" + ^
[elpa] externals/relint a50ed0b 20/21: Don't escape printable chars in rx warnings
branch: externals/relint commit a50ed0b3c15339ae3979d3aa2ac61bf8bdf7a160 Author: Mattias Engdegård Commit: Mattias Engdegård Don't escape printable chars in rx warnings --- relint.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relint.el b/relint.el index b775b8a..cbdd340 100644 --- a/relint.el +++ b/relint.el @@ -1355,7 +1355,7 @@ character alternative: `[' followed by a regexp-generating expression." (if (eq from to) (char-to-string from) (format "%c-%c" from to)) - t)) + nil)) (defun relint--intersecting-range (from to ranges) "Return a range in RANGES intersecting [FROM,TO], or nil if none. @@ -1438,7 +1438,7 @@ than just to a surrounding or producing expression." file pos (if exact-path (cons i path) path) (format-message "Single-character range `%s'" - (relint--escape-string (format "%c-%c" from to) t)) + (relint--escape-string (format "%c-%c" from to) nil)) s j)) ((= to (1+ from)) (relint--warn
[elpa] externals/relint 326cfe2 11/21: Check calls to directory-files(-and-attributes)
branch: externals/relint commit 326cfe24cd110e4cf7686b4a154c7bdb55a727e8 Author: Mattias Engdegård Commit: Mattias Engdegård Check calls to directory-files(-and-attributes) --- relint.el | 4 test/2.elisp| 5 - test/2.expected | 49 + 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/relint.el b/relint.el index b17d451..6c81221 100644 --- a/relint.el +++ b/relint.el @@ -1844,6 +1844,10 @@ directly." (memq trim relint--checked-variables)) (relint--check-re trim (format "call to %s" (car form)) file pos (cons 4 path)) + (`(,(or 'directory-files 'directory-files-and-attributes) + ,_ ,_ ,re-arg . ,_) +(relint--check-re re-arg (format "call to %s" (car form)) + file pos (cons 3 path))) (`(,(or 'skip-chars-forward 'skip-chars-backward) ,skip-arg . ,_) (let ((str (relint--get-string skip-arg))) diff --git a/test/2.elisp b/test/2.elisp index a96cef0..f069a33 100644 --- a/test/2.elisp +++ b/test/2.elisp @@ -26,7 +26,10 @@ (split-string-and-unquote s "[vv]") (string-trim-left s "[ww]") (string-trim-right s "[xx]") - (string-trim s "[yy]" "[zz]")) + (string-trim s "[yy]" "[zz]") + (directory-files s nil "+1") + (directory-files-and-attributes s nil "+2") + (directory-files-recursively s "+3")) ;; Test argument names as means of detecting regexps. (defun f2 (x1 my-regexp x2 my-regex x3 my-re x4 my-pattern x5 re) diff --git a/test/2.expected b/test/2.expected index 0dbcfc8..347f31d 100644 --- a/test/2.expected +++ b/test/2.expected @@ -79,63 +79,72 @@ 2.elisp:29:28: In call to string-trim: Duplicated `z' inside character alternative (pos 2) "[zz]" ..^ -2.elisp:42:17: In call to f2: Duplicated `B' inside character alternative (pos 2) +2.elisp:30:27: In call to directory-files: Unescaped literal `+' (pos 0) + "+1" + ^ +2.elisp:31:42: In call to directory-files-and-attributes: Unescaped literal `+' (pos 0) + "+2" + ^ +2.elisp:32:35: In call to directory-files-recursively: Unescaped literal `+' (pos 0) + "+3" + ^ +2.elisp:45:17: In call to f2: Duplicated `B' inside character alternative (pos 2) "[BB]" ..^ -2.elisp:42:31: In call to f2: Duplicated `D' inside character alternative (pos 2) +2.elisp:45:31: In call to f2: Duplicated `D' inside character alternative (pos 2) "[DD]" ..^ -2.elisp:42:45: In call to f2: Duplicated `F' inside character alternative (pos 2) +2.elisp:45:45: In call to f2: Duplicated `F' inside character alternative (pos 2) "[FF]" ..^ -2.elisp:42:59: In call to f2: Duplicated `H' inside character alternative (pos 2) +2.elisp:45:59: In call to f2: Duplicated `H' inside character alternative (pos 2) "[HH]" ..^ -2.elisp:42:73: In call to f2: Duplicated `J' inside character alternative (pos 2) +2.elisp:45:73: In call to f2: Duplicated `J' inside character alternative (pos 2) "[JJ]" ..^ -2.elisp:43:17: In call to s2: Duplicated `B' inside character alternative (pos 2) +2.elisp:46:17: In call to s2: Duplicated `B' inside character alternative (pos 2) "[BB]" ..^ -2.elisp:43:31: In call to s2: Duplicated `D' inside character alternative (pos 2) +2.elisp:46:31: In call to s2: Duplicated `D' inside character alternative (pos 2) "[DD]" ..^ -2.elisp:43:45: In call to s2: Duplicated `F' inside character alternative (pos 2) +2.elisp:46:45: In call to s2: Duplicated `F' inside character alternative (pos 2) "[FF]" ..^ -2.elisp:43:59: In call to s2: Duplicated `H' inside character alternative (pos 2) +2.elisp:46:59: In call to s2: Duplicated `H' inside character alternative (pos 2) "[HH]" ..^ -2.elisp:43:73: In call to s2: Duplicated `J' inside character alternative (pos 2) +2.elisp:46:73: In call to s2: Duplicated `J' inside character alternative (pos 2) "[JJ]" ..^ -2.elisp:44:17: In call to m2: Duplicated `B' inside character alternative (pos 2) +2.elisp:47:17: In call to m2: Duplicated `B' inside character alternative (pos 2) "[BB]" ..^ -2.elisp:44:31: In call to m2: Duplicated `D' inside character alternative (pos 2) +2.elisp:47:31: In call to m2: Duplicated `D' inside character alternative (pos 2) "[DD]" ..^ -2.elisp:44:45: In call to m2: Duplicated `F' inside character alternative (pos 2) +2.elisp:47:45: In call to m2: Duplicated `F' inside character alternative (pos 2) "[FF]" ..^ -2.elisp:44:59: In call to m2: Duplicated `H' inside character alternative (pos 2) +2.elisp:47:59: In call to m2: Duplicated `H' inside character alternative (pos 2) "[HH]" ..^ -2.elisp:44:73: In call to m2: Duplicated `J' inside character alternative (pos 2) +2.elisp:47:73: In call to m2: Duplicated `J' inside character alternative (pos 2) "[JJ]" ..^ -2.elisp:52:17: In call to f5: Duplicated `b' inside character alternative
[elpa] externals/relint b694c09 07/21: Check split ASCII-raw ranges in rx correctly
branch: externals/relint commit b694c091dcb36ed4fd3a1f0d8f4a14cf9d5f9b74 Author: Mattias Engdegård Commit: Mattias Engdegård Check split ASCII-raw ranges in rx correctly Also make sure not to confuse raw bytes and U+0080-U+00FF. --- relint.el| 44 ++-- test/11.elisp| 7 ++- test/11.expected | 6 ++ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/relint.el b/relint.el index 31f1887..d5b329e 100644 --- a/relint.el +++ b/relint.el @@ -1383,13 +1383,15 @@ than just to a surrounding or producing expression." (push (cons arg arg) ranges)) ((stringp arg) - (let ((j 0) - (len (length arg))) + (let* ((s (string-to-multibyte arg)) + (j 0) + (len (length s))) (while (< j len) - (let ((from (aref arg j))) + (let ((from (aref s j))) (if (and (< (+ j 2) len) - (eq (aref arg (1+ j)) ?-)) - (let ((to (aref arg (+ j 2 + (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. @@ -1399,22 +1401,30 @@ than just to a surrounding or producing expression." file pos (if exact-path (cons i path) path) (format-message "Suspect range `%s'" (relint--pretty-range from to)) - arg j)) + s j)) ((= to from) (relint--warn file pos (if exact-path (cons i path) path) (format-message "Single-character range `%s'" (relint--escape-string (format "%c-%c" from to) t)) - arg j)) + s j)) ((= to (1+ from)) (relint--warn file pos (if exact-path (cons i path) path) (format-message "Two-character range `%s'" (relint--pretty-range from to)) - arg j))) - (let ((overlap - (relint--intersecting-range from to ranges))) + s j))) + ;; 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 file pos (if exact-path (cons i path) path) @@ -1422,15 +1432,21 @@ than just to a surrounding or producing expression." (relint--pretty-range from to) (relint--pretty-range (car overlap) (cdr overlap))) -arg j))) - (push (cons from to) ranges) +s j)) + (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 file pos (if exact-path (cons i path) path) (format-message "Literal `-' not first or last") - arg j)) + s j)) (let ((overlap (relint--intersecting-range from from ranges))) (when overlap @@ -1443,7 +1459,7 @@ than just to a surrounding or producing expression." "Character `%s' included in range `%s'" (relint--pretty-range from from) (relint--pretty-range (car overlap) (cdr overlap -arg j))) +
[elpa] externals/relint 12a2b0f 08/21: Use regexp in suppression comments
branch: externals/relint commit 12a2b0fafdba26526e2b166a322a6171fac19cc4 Author: Mattias Engdegård Commit: Mattias Engdegård Use regexp in suppression comments This adds flexibility, in particular for coping with different quoting styles. --- README | 6 +++--- relint.el| 10 +- test/6.elisp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README b/README index 3ca9499..7a355c4 100644 --- a/README +++ b/README @@ -265,10 +265,10 @@ skip-syntax-backward. To suppress such diagnostics, add a comment on the form -;; relint suppression: MESSAGE +;; relint suppression: REGEXP - on the line before the code where the error occurred. MESSAGE is a - substring of the message to be suppressed. Multiple suppression + 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. diff --git a/relint.el b/relint.el index d5b329e..b17d451 100644 --- a/relint.el +++ b/relint.el @@ -209,9 +209,9 @@ or nil if no position could be determined." (save-excursion ;; On a preceding line, look for a comment on the form ;; -;; relint suppression: SUBSTRING +;; relint suppression: REGEXP ;; -;; where SUBSTRING is a substring of MESSAGE. There can be +;; where REGEXP matches MESSAGE. There can be ;; multiple suppression lines preceding a line of code with ;; several errors. (goto-char pos) @@ -221,11 +221,11 @@ or nil if no position could be determined." (not (setq matched (and (looking-at (rx (0+ blank) (1+ ";") (0+ blank) - "relint suppression:" (0+ blank) + "relint suppression:" (1+ blank) (group (0+ nonl) (not (any "\n" blank) - (let ((substr (match-string 1))) -(string-match-p (regexp-quote substr) message) + (let ((regexp (match-string 1))) +(string-match-p regexp message) (looking-at (rx bol (0+ blank) (opt ";" (0+ nonl)) eol)) diff --git a/test/6.elisp b/test/6.elisp index ac07fc1..cbbdf4b 100644 --- a/test/6.elisp +++ b/test/6.elisp @@ -48,8 +48,8 @@ ;; Test suppression (defun test-suppression () - ;; relint suppression: Unescaped literal `$' - ;; relint suppression: Duplicated `a' + ;; relint suppression: Unescaped literal .\$ + ;; relint suppression: Duplicated .a (looking-at "$[aa]")) ;; Test user-defined regexp-generating functions
[elpa] externals/relint ac75b62 04/21: Check rx-to-string, and the 'regexp' and 'eval' subforms
branch: externals/relint commit ac75b6211e1a9ee0e173641981310550d5193dd3 Author: Mattias Engdegård Commit: Mattias Engdegård Check rx-to-string, and the 'regexp' and 'eval' subforms This includes special treatment for backquoted forms. --- relint.el| 54 -- test/11.elisp| 11 ++- test/11.expected | 21 + 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/relint.el b/relint.el index d484116..0c1653d 100644 --- a/relint.el +++ b/relint.el @@ -1328,8 +1328,10 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." (setq ranges (cdr ranges))) (car ranges)) -(defun relint--check-rx (item file pos path) - "Check the `rx' expression ITEM." +(defun relint--check-rx (item file 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." (pcase item (`(,(or ': 'seq 'sequence 'and 'or '| 'not 'intersection 'repeat '= '>= '** @@ -1343,7 +1345,9 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." ;; Form with subforms: recurse. (let ((i 1)) (dolist (arg args) - (relint--check-rx arg file pos (cons i path)) + (relint--check-rx arg file pos + (if exact-path (cons i path) path) + exact-path) (setq i (1+ i) (`(,(or 'any 'in 'char 'not-char) . ,args) @@ -1360,7 +1364,7 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." (let ((overlap (relint--intersecting-range arg arg ranges))) (when overlap (relint--warn -file pos (cons i path) +file pos (if exact-path (cons i path) path) (if (eq (car overlap) (cdr overlap)) (format-message "Duplicated character `%s'" (relint--pretty-range arg arg)) @@ -1384,20 +1388,20 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." ((or (eq from ?+) (eq to ?+)) (relint--warn - file pos (cons i path) + file pos (if exact-path (cons i path) path) (format-message "Suspect range `%s'" (relint--pretty-range from to)) arg j)) ((= to from) (relint--warn - file pos (cons i path) + file pos (if exact-path (cons i path) path) (format-message "Single-character range `%s'" (relint--escape-string (format "%c-%c" from to) t)) arg j)) ((= to (1+ from)) (relint--warn - file pos (cons i path) + file pos (if exact-path (cons i path) path) (format-message "Two-character range `%s'" (relint--pretty-range from to)) arg j))) @@ -1405,7 +1409,7 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." (relint--intersecting-range from to ranges))) (when overlap (relint--warn -file pos (cons i path) +file pos (if exact-path (cons i path) path) (format-message "Range `%s' overlaps previous `%s'" (relint--pretty-range from to) (relint--pretty-range @@ -1416,14 +1420,14 @@ RANGES is a list of (X . Y) representing the interval [X,Y]." (when (and (eq from ?-) (< 0 j (1- len))) (relint--warn - file pos (cons i path) + file pos (if exact-path (cons i path) path) (format-message "Literal `-' not first or last") arg j)) (let ((overlap (relint--intersecting-range from from ranges))) (when overlap (relint--warn -file pos (cons i path) +file pos (if exact-path (cons i path) path) (if (eq (car overlap) (cdr overlap)) (format-message "Duplicated character `%s'" (relint--pretty-range from from)) @@ -1444,7 +1448,7 @@ RANGES is a list of (X . Y) representing the interval [X,Y]."
[elpa] externals/relint 4fcc322 16/21: Delay call to file-relative-name until needed
branch: externals/relint commit 4fcc3220975815471242dff222d9316d58920c53 Author: Mattias Engdegård Commit: Mattias Engdegård Delay call to file-relative-name until needed ENCODE_FILE and DECODE_FILE, present in many file and filename manipulation primitives, are quite expensive and allocate a lot, even when names are pure ASCII. We don't actually need to call file-relative-name until there is a diagnostic emitted for that file, so evaluate it lazily. This should really be fixed in Emacs, but meanwhile this mitigation doesn't hurt. --- relint.el | 11 --- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/relint.el b/relint.el index 1adcaee..e2eefcf 100644 --- a/relint.el +++ b/relint.el @@ -91,6 +91,7 @@ (require 'xr) (require 'compile) (require 'cl-lib) +(require 'thunk) (defvar relint--error-buffer) (defvar relint--quiet) @@ -263,7 +264,8 @@ or nil if no position could be determined." (error-pos (and str-idx (relint--string-pos expr-pos str-idx (if (relint--suppression expr-pos message) (setq relint--suppression-count (1+ relint--suppression-count)) - (funcall relint--report-function file expr-pos error-pos message + (funcall relint--report-function + (thunk-force file) expr-pos error-pos message str str-idx severity))) (setq relint--error-count (1+ relint--error-count))) @@ -2191,8 +2193,11 @@ Return a list of (FORM . STARTING-POSITION)." (with-temp-buffer (emacs-lisp-mode) (insert-file-contents file) -(relint--scan-current-buffer (file-relative-name file base-dir - +;; Call file-relative-name lazily -- it is surprisingly expensive +;; on macOS, and the result only used for diagnostics output. +(relint--scan-current-buffer + (thunk-delay (file-relative-name file base-dir) + (defvar relint-last-target nil "The last file, directory or buffer on which relint was run.")
[elpa] externals/relint 2eba4d7 09/21: Describe new bol/eol/eos warnings
branch: externals/relint commit 2eba4d72e8592cd0891ee5652c5bdd323c72f5ea Author: Mattias Engdegård Commit: Mattias Engdegård Describe new bol/eol/eos warnings --- README | 12 1 file changed, 12 insertions(+) diff --git a/README b/README index 7a355c4..777d9d0 100644 --- a/README +++ b/README @@ -133,6 +133,18 @@ skip-syntax-backward. the repeated sequence, resulting in a*\(?:c[ab]+\)* in the example above. + - End-of-line anchor followed by non-newline + - Non-newline followed by line-start anchor + +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. + + - End-of-text anchor followed by non-empty pattern + +A pattern that only matches a non-empty string occurs right after +an end-of-text anchor (\'). This combination can never match. + - Uncounted repetition The construct A\{,\} repeats A zero or more times which was
[elpa] externals/xr 35dbbeb 10/10: Increment version to 1.19
branch: externals/xr commit 35dbbebc86385e2d89668f42f4997a355e17ec04 Author: Mattias Engdegård Commit: Mattias Engdegård Increment version to 1.19 --- xr.el | 7 ++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xr.el b/xr.el index 560cfed..6bbf5e7 100644 --- a/xr.el +++ b/xr.el @@ -3,7 +3,7 @@ ;; Copyright (C) 2019-2020 Free Software Foundation, Inc. ;; Author: Mattias Engdegård -;; Version: 1.18 +;; Version: 1.19 ;; Package-Requires: ((emacs "26.1")) ;; URL: https://github.com/mattiase/xr ;; Keywords: lisp, regexps @@ -29,6 +29,11 @@ ;;; News: +;; Version 1.19: +;; - Added filename-specific checks; new PURPOSE argument to `xr-lint' +;; - Warn about wrapped subsumption, like \(A*C[AB]*\)+ +;; - Improved scope and accuracy of all subsumption checks +;; - Warn about anchors in conflict with other expressions, like \(A$\)B ;; Version 1.18: ;; - Fix test broken in Emacs 26 ;; Version 1.17:
[elpa] externals/relint 1bf7f25 13/21: Check auto-mode-alist with file-specific checks
branch: externals/relint commit 1bf7f25a9fff55e177734ba14b41736ce77f9862 Author: Mattias Engdegård Commit: Mattias Engdegård Check auto-mode-alist with file-specific checks This also includes additions to auto-mode-alist via add-to-list or setq. --- relint.el| 55 ++- test/12.elisp| 16 test/12.expected | 24 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/relint.el b/relint.el index 89c2461..03a9421 100644 --- a/relint.el +++ b/relint.el @@ -1090,13 +1090,16 @@ source." (when re (relint--check-re-string re name file pos path -(defun relint--check-list (form name file pos path) +(defun relint--check-list (form name file pos path is-file-name) "Check a list of regexps." - (relint--eval-list-iter - (lambda (elem elem-path _literal) - (when (stringp elem) - (relint--check-re-string elem name file pos elem-path))) - form path)) + (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 file pos elem-path))) + form path))) (defun relint--check-list-any (form name file pos path) "Check a list of regexps or conses whose car is a regexp." @@ -1215,6 +1218,24 @@ or in the car of an element." (relint--check-re re name file pos path) (relint--extra-file-name-re-checks re file pos path +(defun relint--check-auto-mode-alist-expr (form name file pos path) + "Check a single element added to `auto-mode-alist'." + (pcase form +(`(quote (,(and (pred stringp) str) . ,_)) + (relint--check-file-name-re str name file pos (cons 0 (cons 1 path +(_ + (let ((val (relint--eval-or-nil form))) + (when (and (consp val) (stringp (car val))) + (relint--check-file-name-re (car val) name file pos path)) + +(defun relint--check-auto-mode-alist (form name file pos path) + (relint--eval-list-iter + (lambda (elem elem-path literal) + (relint--check-file-name-re + (car elem) name + file pos (if literal (cons 0 elem-path) elem-path))) + form path)) + (defun relint--check-rules-list (form name file pos path) "Check a variable on `align-mode-rules-list' format" (relint--eval-list-iter @@ -1663,7 +1684,7 @@ than just to a surrounding or producing expression." (relint--check-defcustom-type (relint--eval-or-nil type) name file pos (cons index path))) (`(:options ,options) - (relint--check-list options name file pos (cons index path + (relint--check-list options name file pos (cons index path) nil))) (setq index (+ index 2)) (setq args (cddr args) @@ -1772,6 +1793,14 @@ directly." ((eq name 'imenu-generic-expression) (relint--check-imenu-generic-expression expr name file pos (cons i path))) +((eq name 'auto-mode-alist) + (pcase expr + (`(cons ,item auto-mode-alist) +(relint--check-auto-mode-alist-expr + item name file pos (cons 1 (cons i path + (`(append ,items auto-mode-alist) +(relint--check-auto-mode-alist + items name file pos (cons 1 (cons i path)) (t ;; Invalidate the variable if it was local; otherwise, ignore. (let ((local (assq name relint--locals))) @@ -1787,6 +1816,8 @@ directly." (`(push ,expr ,(and (pred symbolp) name)) ;; Treat (push EXPR NAME) as (setq NAME (cons EXPR NAME)). (relint--check-form-recursively-2 expr mutables file pos (cons 1 path)) + (when (eq name 'auto-mode-alist) + (relint--check-auto-mode-alist-expr expr name file pos (cons 1 path))) (let ((local (assq name relint--locals))) (when local (setcdr local @@ -1949,7 +1980,7 @@ directly." "-list")) eos) (symbol-name name))) - (relint--check-list re-arg name file pos (cons 2 path)) + (relint--check-list re-arg name file pos (cons 2 path) nil) (push name relint--checked-variables)) ((string-match-p (rx "font-lock-keywords") (symbol-name name)) @@ -1960,6 +1991,9 @@ directly." (relint--check-compilation-error-regexp-alist-alist re-arg name file pos (cons 2 path)) (push name relint--checked-variables)) + ((eq name 'auto-mode-alist) + (relint--check-auto-mode-alist + re-arg name file pos (cons 2 path))) ((string-match-p (rx (or "-regexp" "-regex" "-re"
[elpa] externals/relint 008fad0 17/21: Repair relint-current-buffer after thunking file parameter
branch: externals/relint commit 008fad0182055fde788b1df8757db49a299291b8 Author: Mattias Engdegård Commit: Mattias Engdegård Repair relint-current-buffer after thunking file parameter --- relint.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relint.el b/relint.el index e2eefcf..ba10355 100644 --- a/relint.el +++ b/relint.el @@ -2272,7 +2272,7 @@ Diagnostics to ERROR-BUFFER. If QUIET, don't emit messages." (error "Relint: can only scan elisp code (use emacs-lisp-mode)")) (relint--init buffer default-directory error-buffer quiet) (save-excursion - (relint--scan-current-buffer (buffer-name + (relint--scan-current-buffer (thunk-delay (buffer-name) (relint--finish))
[elpa] externals/relint 8f49686 18/21: Move file-specific checks to xr
branch: externals/relint commit 8f49686f268dc10e1074f1c344084e3f2bafc7ee Author: Mattias Engdegård Commit: Mattias Engdegård Move file-specific checks to xr --- relint.el| 48 ++-- test/12.expected | 24 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/relint.el b/relint.el index ba10355..b775b8a 100644 --- a/relint.el +++ b/relint.el @@ -320,6 +320,9 @@ or nil if no position could be determined." (defun relint--check-re-string (re name file pos path) (relint--check-string re #'xr-lint name file pos path)) +(defun relint--check-file-re-string (re name file pos path) + (relint--check-string re (lambda (x) (xr-lint x 'file)) name file pos path)) + (defun relint--check-syntax-string (syntax name file pos path) (relint--check-string syntax #'relint--syntax-string-lint name file pos path)) @@ -1176,59 +1179,20 @@ or in the car of an element." file pos (if literal (cons 1 elem-path) elem-path form path)) -(defun relint--extra-file-name-re-checks (string file pos path) - "Perform extra checks on STRING assuming it matches file names." - - ;; It would be much easier to do these checks (and more) on the rx - ;; representation, but unfortunately xr doesn't return a - ;; location-annotated expression right now. - (let ((len (length string)) -(start 0)) -(while (and (< start len) -;; Skip anything that is NOT one of . ^ $ -(string-match (rx (* (or (not (any "\\.$^[")) - (seq "\\" anything) - (seq "[" (opt "^") (opt "]") - (* (not (any "]"))) - "]" - string start)) - (setq start (match-end 0)) - (let* ((m (string-match (rx (or "^" "$" (seq "." (opt (any "*+?") - string start)) - (end (match-end 0))) -(when (and m (= m start)) - (pcase (match-string 0 string) -("^" (relint--warn - file pos path - "Use \\` instead of ^ in file-matching regexp" - string start)) -("$" (relint--warn - file pos path - "Use \\' instead of $ in file-matching regexp" - string start)) -;; We assume that .* etc are intended. -("." (relint--warn - file pos path - (format-message - "Possibly unescaped `.' in file-matching regexp") - string start))) - (setq start end)) - (defun relint--check-file-name-re (form name file pos path) (let ((re (relint--get-string form))) (when re - (relint--check-re re name file pos path) - (relint--extra-file-name-re-checks re file pos path + (relint--check-file-re-string re name file pos path (defun relint--check-auto-mode-alist-expr (form name file pos path) "Check a single element added to `auto-mode-alist'." (pcase form (`(quote (,(and (pred stringp) str) . ,_)) - (relint--check-file-name-re str name file pos (cons 0 (cons 1 path + (relint--check-file-re-string str name file pos (cons 0 (cons 1 path (_ (let ((val (relint--eval-or-nil form))) (when (and (consp val) (stringp (car val))) - (relint--check-file-name-re (car val) name file pos path)) + (relint--check-file-re-string (car val) name file pos path)) (defun relint--check-auto-mode-alist (form name file pos path) (relint--eval-list-iter diff --git a/test/12.expected b/test/12.expected index ecef078..e0ab05b 100644 --- a/test/12.expected +++ b/test/12.expected @@ -1,37 +1,37 @@ -12.elisp:9:6: Possibly unescaped `.' in file-matching regexp (pos 0) +12.elisp:9:6: In define-generic-mode my-mode: Possibly unescaped `.' in file-matching regexp (pos 0) ".aa\\'" ^ -12.elisp:9:20: Use \' instead of $ in file-matching regexp (pos 4) +12.elisp:9:20: In define-generic-mode my-mode: Use \' instead of $ in file-matching regexp (pos 4) "\\.bb$" .^ -12.elisp:9:24: Use \` instead of ^ in file-matching regexp (pos 0) +12.elisp:9:24: In define-generic-mode my-mode: Use \` instead of ^ in file-matching regexp (pos 0) "^cc.*dd" ^ -12.elisp:11:39: Use \' instead of $ in file-matching regexp (pos 4) +12.elisp:11:39: In add-to-list: Use \' instead of $ in file-matching regexp (pos 4) "\\.ee$" .^ -12.elisp:12:10: Possibly unescaped `.' in file-matching regexp (pos 0) +12.elisp:12:10: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".ff\\'" ^ -12.elisp:13:32: Possibly unescaped `.' in file-matching regexp (pos 0) +12.elisp:13:32: In auto-mode-alist: Possibly unescaped `.' in file-matching regexp (pos 0) ".gg\\'" ^
[elpa] externals/relint cdd65ae 10/21: Add section about how relint works
branch: externals/relint commit cdd65ae90343eec0774a86741d8104a524b1aad8 Author: Mattias Engdegård Commit: Mattias Engdegård Add section about how relint works --- README | 36 +++- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/README b/README index 777d9d0..f816349 100644 --- a/README +++ b/README @@ -36,6 +36,7 @@ skip-syntax-backward. which returns a list of diagnostics. + * Installation From GNU ELPA (https://elpa.gnu.org/packages/relint.html): @@ -45,6 +46,7 @@ skip-syntax-backward. Relint requires the package xr (https://elpa.gnu.org/packages/xr.html); it will be installed automatically. + * What the diagnostics mean - Unescaped literal 'X' @@ -268,6 +270,7 @@ skip-syntax-backward. A string argument to skip-syntax-forward or skip-syntax-backward is empty or "^", neither of which makes sense. + * Suppressing diagnostics While relint has been designed to avoid false positives, there may @@ -284,11 +287,34 @@ skip-syntax-backward. 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 standard functions taking regexps as arguments, +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, 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 recognition of regexps is done by ad-hoc rules; the simplistic - method employed means that many errors will go undetected. + The simplistic method employed means that many errors will go + undetected, but false warnings are usually rare. - Still, if you believe that a flawed regexp could have been - discovered but wasn't, please report it as a bug. Reports of false - positives and crashes are of course equally welcome. + 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.
[elpa] externals/relint 636e172 12/21: Add filename-specific checks (unused so far)
branch: externals/relint commit 636e1725f985c6cf30de04cf1396a413dd89a598 Author: Mattias Engdegård Commit: Mattias Engdegård Add filename-specific checks (unused so far) This includes checks for ^ and $ (use \` and \' instead), and for . (should probably be \.) --- relint.el | 44 1 file changed, 44 insertions(+) diff --git a/relint.el b/relint.el index 6c81221..89c2461 100644 --- a/relint.el +++ b/relint.el @@ -1171,6 +1171,50 @@ or in the car of an element." file pos (if literal (cons 1 elem-path) elem-path form path)) +(defun relint--extra-file-name-re-checks (string file pos path) + "Perform extra checks on STRING assuming it matches file names." + + ;; It would be much easier to do these checks (and more) on the rx + ;; representation, but unfortunately xr doesn't return a + ;; location-annotated expression right now. + (let ((len (length string)) +(start 0)) +(while (and (< start len) +;; Skip anything that is NOT one of . ^ $ +(string-match (rx (* (or (not (any "\\.$^[")) + (seq "\\" anything) + (seq "[" (opt "^") (opt "]") + (* (not (any "]"))) + "]" + string start)) + (setq start (match-end 0)) + (let* ((m (string-match (rx (or "^" "$" (seq "." (opt (any "*+?") + string start)) + (end (match-end 0))) +(when (and m (= m start)) + (pcase (match-string 0 string) +("^" (relint--warn + file pos path + "Use \\` instead of ^ in file-matching regexp" + string start)) +("$" (relint--warn + file pos path + "Use \\' instead of $ in file-matching regexp" + string start)) +;; We assume that .* etc are intended. +("." (relint--warn + file pos path + (format-message + "Possibly unescaped `.' in file-matching regexp") + string start))) + (setq start end)) + +(defun relint--check-file-name-re (form name file pos path) + (let ((re (relint--get-string form))) +(when re + (relint--check-re re name file pos path) + (relint--extra-file-name-re-checks re file pos path + (defun relint--check-rules-list (form name file pos path) "Check a variable on `align-mode-rules-list' format" (relint--eval-list-iter
[elpa] externals/relint updated (83e677d -> a001a05)
mattiase pushed a change to branch externals/relint. from 83e677d Increment version to 1.15 new ba7b747 Display the number of files found in relint-directory new 96e26a5 Check keyword arguments :regexp and :regex new 9259a5c Check some :value parameters in defcustom :type clauses new ac75b62 Check rx-to-string, and the 'regexp' and 'eval' subforms new 09ef3df Describe the new xr wrapped subsumption warning new eb178d5 Check assignments to imenu-generic-expression new b694c09 Check split ASCII-raw ranges in rx correctly new 12a2b0f Use regexp in suppression comments new 2eba4d7 Describe new bol/eol/eos warnings new cdd65ae Add section about how relint works new 326cfe2 Check calls to directory-files(-and-attributes) new 636e172 Add filename-specific checks (unused so far) new 1bf7f25 Check auto-mode-alist with file-specific checks new cf2a2ae Do file-specific checks on arguments to known functions new f6d0fed Describe the new file-specific warnings new 4fcc322 Delay call to file-relative-name until needed new 008fad0 Repair relint-current-buffer after thunking file parameter new 8f49686 Move file-specific checks to xr new 5d3f78d Update xr messages ("repetition" changed to "option") new a50ed0b Don't escape printable chars in rx warnings new a001a05 Increment version to 1.16 Summary of changes: README | 95 +--- relint.el| 264 +-- test/1.elisp | 13 +++ test/1.expected | 30 +-- test/11.elisp| 16 +++- test/11.expected | 27 ++ test/12.elisp| 25 ++ test/12.expected | 39 test/2.elisp | 9 +- test/2.expected | 51 +++ test/6.elisp | 4 +- test/9.elisp | 5 ++ test/9.expected | 3 + 13 files changed, 475 insertions(+), 106 deletions(-) create mode 100644 test/12.elisp create mode 100644 test/12.expected
[elpa] externals/relint ba7b747 01/21: Display the number of files found in relint-directory
branch: externals/relint commit ba7b7474ae08968d5a0341b9ce79757848ef759c Author: Mattias Engdegård Commit: Mattias Engdegård Display the number of files found in relint-directory --- relint.el | 9 ++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/relint.el b/relint.el index a53e2c0..567315c 100644 --- a/relint.el +++ b/relint.el @@ -2125,9 +2125,12 @@ Diagnostics to ERROR-BUFFER. If QUIET, don't emit messages." "Scan all *.el files in DIR for regexp-related errors." (interactive "DRelint directory: ") (message "Finding .el files in %s..." dir) - (let ((files (relint--tree-files dir))) -(message "Scanning files...") -(relint--scan-files files dir dir (relint--get-error-buffer + (let* ((files (relint--tree-files dir)) + (n (length files))) +(if (not files) +(message "No .el files found.") + (message "Scanning %d file%s..." n (if (= n 1) "" "s")) + (relint--scan-files files dir dir (relint--get-error-buffer) ;;;###autoload (defun relint-current-buffer ()
[elpa] externals/xr d752aab 09/10: Use "option" instead of "repetition" in diagnostics for ? and ??
branch: externals/xr commit d752aabeae1c5f29fff810f43a3f96f34366603f Author: Mattias Engdegård Commit: Mattias Engdegård Use "option" instead of "repetition" in diagnostics for ? and ?? --- xr-test.el | 17 xr.el | 94 +- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/xr-test.el b/xr-test.el index 175df15..95927ca 100644 --- a/xr-test.el +++ b/xr-test.el @@ -362,16 +362,21 @@ '((0 . "Escaped non-special character `}'") (4 . "Escaped non-special character `a'") (8 . "Escaped non-special character `%'" -(should (equal (xr-lint "a?+b+?\\(?:c?\\)*d\\{3\\}+e*?\\{2,5\\}") - '((2 . "Repetition of repetition") +(should (equal (xr-lint "a?+b+?\\(?:c*\\)*d\\{3\\}+e*?\\{2,5\\}") + '((2 . "Repetition of option") (14 . "Repetition of repetition") (25 . "Repetition of repetition" (should (equal (xr-lint "\\(a*\\)*\\(b+\\)*\\(c*\\)?\\(d+\\)?") '((6 . "Repetition of repetition") (13 . "Repetition of repetition") - (20 . "Repetition of repetition" + (20 . "Optional repetition" +(should (equal (xr-lint "\\(a?\\)+\\(b?\\)?") + '((6 . "Repetition of option") + (13 . "Optional option" (should (equal (xr-lint "\\(e*\\)\\{3\\}") '((6 . "Repetition of repetition" +(should (equal (xr-lint "\\(a?\\)\\{4,7\\}") + '((6 . "Repetition of option" (should (equal (xr-lint "[]-Qa-fz-t]") '((1 . "Reversed range `]-Q' matches nothing") (7 . "Reversed range `z-t' matches nothing" @@ -406,7 +411,7 @@ "Literal `-' not first or last in character alternative" (should (equal (xr-lint "\\'*\\
[elpa] externals/xr d0b09e1 08/10: Add filename-specific checks
branch: externals/xr commit d0b09e1b49bc47641580604c34369d4fb0875a38 Author: Mattias Engdegård Commit: Mattias Engdegård Add filename-specific checks These were moved from relint since they arguably belong here. xr-lint now takes an optional PURPOSE argument. --- xr-test.el | 9 + xr.el | 54 +- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/xr-test.el b/xr-test.el index c0c428d..175df15 100644 --- a/xr-test.el +++ b/xr-test.el @@ -651,6 +651,15 @@ nil)) )) +(ert-deftest xr-lint-file () + (let ((text-quoting-style 'grave)) +(should (equal (xr-lint "a.b\\.c.*d.?e.+f." 'file) + '((1 . "Possibly unescaped `.' in file-matching regexp") + (15 . "Possibly unescaped `.' in file-matching regexp" +(should (equal (xr-lint "^abc$" 'file) + '((0 . "Use \\` instead of ^ in file-matching regexp") + (4 . "Use \\' instead of $ in file-matching regexp")) + (ert-deftest xr-skip-set () (should (equal (xr-skip-set "0-9a-fA-F+*") '(any "0-9a-fA-F" "+*"))) diff --git a/xr.el b/xr.el index a10be63..f50c4f2 100644 --- a/xr.el +++ b/xr.el @@ -521,7 +521,7 @@ like (* (* X) ... (* X))." "First item in repetition subsumes last item (wrapped)" "Last item in repetition subsumes first item (wrapped)" -(defun xr--parse-seq (warnings) +(defun xr--parse-seq (warnings purpose) (let ((sequence nil)) ; reversed (while (not (looking-at (rx (or "\\|" "\\)" eos (let ((item-start (point))) @@ -530,8 +530,12 @@ like (* (* X) ... (* X))." ((looking-at (rx "^")) (forward-char 1) (if (null sequence) - (push 'bol sequence) -(xr--report warnings (match-beginning 0) + (progn +(when (eq purpose 'file) + (xr--report warnings item-start + "Use \\` instead of ^ in file-matching regexp")) +(push 'bol sequence)) +(xr--report warnings item-start (format-message "Unescaped literal `^'")) (push "^" sequence))) @@ -539,8 +543,13 @@ like (* (* X) ... (* X))." ((looking-at (rx "$")) (forward-char 1) (if (looking-at (rx (or "\\|" "\\)" eos))) - (push 'eol sequence) -(xr--report warnings (match-beginning 0) + (progn +(when (eq purpose 'file) + (xr--report warnings item-start + "Use \\' instead of $ in file-matching regexp")) + +(push 'eol sequence)) +(xr--report warnings item-start (format-message "Unescaped literal `$'")) (push "$" sequence))) @@ -682,7 +691,7 @@ like (* (* X) ... (* X))." (when (and question (not colon)) (error "Invalid \\(? syntax")) (goto-char (match-end 0)) -(let* ((group (xr--parse-alt warnings)) +(let* ((group (xr--parse-alt warnings purpose)) ;; simplify - group has an implicit seq (operand (if (and (listp group) (eq (car group) 'seq)) (cdr group) @@ -706,14 +715,23 @@ like (* (* X) ... (* X))." (push (list 'backref (string-to-number (match-string 1))) sequence)) + ;; not-newline + ((looking-at (rx ".")) + (goto-char (match-end 0)) + ;; Assume that .* etc is intended. + (when (and (eq purpose 'file) + (not (looking-at (rx (any "?*+") +(xr--report warnings (match-beginning 0) +"Possibly unescaped `.' in file-matching regexp")) + (push 'nonl sequence)) + ;; various simple substitutions - ((looking-at (rx (or "." "\\w" "\\W" "\\`" "\\'" "\\=" + ((looking-at (rx (or "\\w" "\\W" "\\`" "\\'" "\\=" "\\b" "\\B" "\\<" "\\>"))) (goto-char (match-end 0)) (let ((sym (cdr (assoc (match-string 0) - '(("." . nonl) - ("\\w" . wordchar) ("\\W" . not-wordchar) + '(("\\w" . wordchar) ("\\W" . not-wordchar) ("\\`" . bos) ("\\'" . eos) ("\\=" . point) ("\\b" . word-boundary) ("\\B" . not-word-boundary) @@ -1356,13 +1374,13 @@ A-SETS and B-SETS are arguments to `any'." (_ (equal a b)) -(defun xr--parse-alt (warnings) +(defun xr--parse-alt (warnings purpose) (let ((alternatives nil)) ; reversed -(push (xr--parse-seq warnings) alternatives) +(push
[elpa] externals/xr f3b61ef 05/10: Fix false negative in empty string repetition check
branch: externals/xr commit f3b61ef7450afd7726e509509f5b3042af23a051 Author: Mattias Engdegård Commit: Mattias Engdegård Fix false negative in empty string repetition check We forgot to check for non-greedy inner repetitions. --- xr-test.el | 14 ++ xr.el | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/xr-test.el b/xr-test.el index 88e68f7..2724e0d 100644 --- a/xr-test.el +++ b/xr-test.el @@ -404,16 +404,22 @@ (xr-lint "[-A-Z][A-Z-][A-Z-a][^-A-Z][]-a][A-Z---.]") '((16 . "Literal `-' not first or last in character alternative" -(should (equal - (xr-lint "\\(?:a*b?\\)*\\(c\\|d\\|\\)+\\(^\\|e\\)*\\(?:\\)*") - '((10 . "Repetition of expression matching an empty string") - (21 . "Repetition of expression matching an empty string" (should (equal (xr-lint "\\'*\\
[elpa] externals/xr 7160235 02/10: Refactor repetition subsumption check to avoid code duplication
branch: externals/xr commit 7160235fe06fdbd8814d9384f4239c9a6e8ad809 Author: Mattias Engdegård Commit: Mattias Engdegård Refactor repetition subsumption check to avoid code duplication --- xr-test.el | 2 +- xr.el | 52 +--- 2 files changed, 10 insertions(+), 44 deletions(-) diff --git a/xr-test.el b/xr-test.el index 2001a39..a46cf60 100644 --- a/xr-test.el +++ b/xr-test.el @@ -602,7 +602,7 @@ '((13 . "First item in repetition subsumes last item (wrapped)" (should (equal (xr-lint "\\(?3:a*xa*\\)\\{7\\}") - '((14 . "First item in repetition subsumes last item (wrapped)" + '((14 . "Last item in repetition subsumes first item (wrapped)" )) (ert-deftest xr-skip-set () diff --git a/xr.el b/xr.el index 985f4d8..3e09ac0 100644 --- a/xr.el +++ b/xr.el @@ -490,11 +490,11 @@ like (* (* X) ... (* X))." (when (cddr operands) (let* ((first (car operands)) (last (car (last operands))) - (subsumption (xr--adjacent-subsumption first last))) + (subsumption (xr--adjacent-subsumption last first))) (when subsumption (xr--report warnings pos - (if (eq subsumption 'a-subsumes-b) + (if (eq subsumption 'b-subsumes-a) "First item in repetition subsumes last item (wrapped)" "Last item in repetition subsumes first item (wrapped)" @@ -743,48 +743,14 @@ like (* (* X) ... (* X))." (t (error "Backslash at end of regexp"))) (when (and warnings (cdr sequence)) - ;; Check for subsuming repetitions in sequence: (Rx X) (Ry Y) - ;; where Rx and Ry are repetition operators, and X and Y are operands. - ;; We conclude that (Rx X) subsumes (Ry Y), in the sense that the - ;; sequence is equivalent to just (Rx X), if: - ;; X matches a superset of Y - ;; and Rx can match infinitely many times - ;; and Ry can match zero times - ;; and Ry is non-greedy if Rx is non-greedy. - ;; Example: [ab]+a? (let* ((item (car sequence)) - (expr (and (consp item) -(memq (car item) - '(zero-or-more one-or-more opt *? +? ??)) -(xr--make-seq (cdr item) -(when expr - (let* ((prev-item (cadr sequence)) - (prev-expr - (and (consp prev-item) - (memq (car prev-item) - '(zero-or-more one-or-more opt *? +? ??)) - (xr--make-seq (cdr prev-item) -(when prev-expr - (let ((op (car item)) -(prev-op (car prev-item))) -;; Test the same condition twice, but mirrored. -(cond - ((and (memq op '(zero-or-more opt *? ??)) - (memq prev-op '(zero-or-more one-or-more *? +?)) - (not (and (memq prev-op '(*? +?)) - (memq op '(zero-or-more opt - (xr--superset-p prev-expr expr)) - (xr--report - warnings item-start - "Repetition subsumed by preceding repetition")) - ((and (memq prev-op '(zero-or-more opt *? ??)) - (memq op '(zero-or-more one-or-more *? +?)) - (not (and (memq op '(*? +?)) - (memq prev-op '(zero-or-more opt - (xr--superset-p expr prev-expr)) - (xr--report - warnings item-start - "Repetition subsumes preceding repetition"))) + (prev-item (cadr sequence)) + (subsumption (xr--adjacent-subsumption prev-item item))) +(when subsumption + (xr--report warnings item-start + (if (eq subsumption 'a-subsumes-b) + "Repetition subsumed by preceding repetition" +"Repetition subsumes preceding repetition"))) (let ((item-seq (xr--rev-join-seq sequence))) (cond ((null item-seq)
[elpa] externals/xr c98bb7b 03/10: Handle whitespace and word syntax subsumption in one place
branch: externals/xr commit c98bb7bcf319bca7c42038c51a92ed78f5e439d7 Author: Mattias Engdegård Commit: Mattias Engdegård Handle whitespace and word syntax subsumption in one place Use the more elaborate code already in place for charsets. --- xr.el | 33 + 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/xr.el b/xr.el index 3e09ac0..fa89862 100644 --- a/xr.el +++ b/xr.el @@ -1042,22 +1042,23 @@ A-SETS and B-SETS are arguments to `any'." (defun xr--syntax-superset-of-rx-p (syntax negated rx) "Whether SYNTAX, possibly NEGATED, is a superset of RX." - ;; Syntax tables vary, but we make a (quite conservative) guess. - (let* ((always-set - ;; Characters we think always will be in the syntax set. - '((whitespace " \t") - (word "A-Za-z0-9") - (open-parenthesis "([") - (close-parenthesis "])"))) - (never-set - ;; Characters we think never will be in the syntax set. - '((whitespace "!-~") - (punctuation "A-Za-z0-9") - (open-parenthesis "\x00- A-Za-z0-9") - (close-parenthesis "\x00- A-Za-z0-9"))) - (set (assq syntax (if negated never-set always-set -(and set - (xr--char-superset-of-rx-p (cdr set) nil rx + (cond + ((eq syntax 'whitespace) (xr--char-superset-of-rx-p '(space) negated rx)) + ((eq syntax 'word) (xr--char-superset-of-rx-p '(word) negated rx)) + (t +;; Syntax tables vary, but we make a fairly conservative guess. +(let* ((always-set +;; Characters we think always will be in the syntax set. +'((open-parenthesis "([") + (close-parenthesis "])"))) + (never-set +;; Characters we think never will be in the syntax set. +'((punctuation "A-Za-z0-9") ; NOT the same as [:punct:]! + (open-parenthesis "\000-\037A-Za-z0-9" " \177") + (close-parenthesis "\000-\037A-Za-z0-9" " \177"))) + (set (assq syntax (if negated never-set always-set + (and set + (xr--char-superset-of-rx-p (cdr set) nil rx)) (defun xr--expand-strings (rx) "Expand strings to characters or seqs of characters.
[elpa] externals/xr c6e12b7 04/10: Handle \w and \W in subsumption checks
branch: externals/xr commit c6e12b71234c1009395d8b0c013db77d63834269 Author: Mattias Engdegård Commit: Mattias Engdegård Handle \w and \W in subsumption checks --- xr-test.el | 8 xr.el | 6 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/xr-test.el b/xr-test.el index a46cf60..88e68f7 100644 --- a/xr-test.el +++ b/xr-test.el @@ -509,6 +509,14 @@ (should (equal (xr-lint "\\Ca\\|ü") '((5 . "Branch matches subset of a previous branch" +(should (equal (xr-lint "\\w\\|[^z-a]") + '((4 . "Branch matches superset of a previous branch" +(should (equal (xr-lint "\\W\\|[^z-a]") + '((4 . "Branch matches superset of a previous branch" +(should (equal (xr-lint "\\w\\|a") + '((4 . "Branch matches subset of a previous branch" +(should (equal (xr-lint "\\W\\|\f") + '((4 . "Branch matches subset of a previous branch" )) (ert-deftest xr-lint-subsumed-repetition () diff --git a/xr.el b/xr.el index fa89862..03b5f0a 100644 --- a/xr.el +++ b/xr.el @@ -1033,7 +1033,8 @@ A-SETS and B-SETS are arguments to `any'." (or (memq rx '(nonl anything ascii alnum alpha blank cntrl digit graph lower multibyte nonascii print punct space - unibyte upper word xdigit)) + unibyte upper word xdigit + wordchar not-wordchar)) (characterp rx) (and (consp rx) (or (memq (car rx) '(any category syntax)) @@ -1160,6 +1161,9 @@ A-SETS and B-SETS are arguments to `any'." (`(not (syntax ,syn)) (or (equal a b) (xr--syntax-superset-of-rx-p syn t b))) + ('wordchar (or (equal a b) (xr--syntax-superset-of-rx-p 'word nil b))) + ('not-wordchar (or (equal a b) (xr--syntax-superset-of-rx-p 'word t b))) + ((or `(category ,_) `(not (category ,_))) (or (equal a b) (and (characterp b)
[elpa] externals/xr 21eab3c 06/10: Check for bol, eol and eos in conflict with other expressions
branch: externals/xr commit 21eab3c0f6593754b03f5d2a2a7c4f44c6744a75 Author: Mattias Engdegård Commit: Mattias Engdegård Check for bol, eol and eos in conflict with other expressions --- xr-test.el | 12 +++ xr.el | 118 +++-- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/xr-test.el b/xr-test.el index 2724e0d..b426d30 100644 --- a/xr-test.el +++ b/xr-test.el @@ -619,6 +619,18 @@ '((14 . "Last item in repetition subsumes first item (wrapped)" )) +(ert-deftest xr-lint-bad-anchor () + (let ((text-quoting-style 'grave)) +(should (equal (xr-lint "a\\(?:^b$\\)c") + '((1 . "Non-newline followed by line-start anchor") + (10 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint ".\\(?:^$\\).") + '((1 . "Non-newline followed by line-start anchor") + (9 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint "\\'b") + '((2 . "End-of-text anchor followed by non-empty pattern" +)) + (ert-deftest xr-skip-set () (should (equal (xr-skip-set "0-9a-fA-F+*") '(any "0-9a-fA-F" "+*"))) diff --git a/xr.el b/xr.el index eccbf3f..6467624 100644 --- a/xr.el +++ b/xr.el @@ -439,6 +439,29 @@ UPPER may be nil, meaning infinity." (cl-every #'xr--matches-empty-p body)) ("" t))) +(defun xr--matches-nonempty-only-p (rx) + "Whether RX matches non-empty strings only." + (pcase rx +((pred stringp) (> (length rx) 0)) +(`(,(or 'seq 'one-or-more '+? 'group) . ,body) + (cl-some #'xr--matches-nonempty-only-p body)) +(`(or . ,body) + (cl-every #'xr--matches-nonempty-only-p body)) +(`(group-n ,_ . ,body) + (cl-some #'xr--matches-nonempty-only-p body)) +(`(repeat ,from ,_ . ,body) + (and (> from 0) + (cl-some #'xr--matches-nonempty-only-p body))) +(`(,(or '= '>=) ,n . ,body) + (and (> n 0) + (cl-some #'xr--matches-nonempty-only-p body))) +(`(,(or 'any 'not 'intersection) . ,_) t) +((or 'ascii 'alnum 'alpha 'blank 'cntrl 'digit 'graph + 'lower 'multibyte 'nonascii 'print 'punct 'space + 'unibyte 'upper 'word 'xdigit + 'nonl 'anything) + t))) + (defun xr--adjacent-subsumption (a b) "Check if A subsumes B, or vice versa, or not, assuming they are adjacent. Return `a-subsumes-b', `b-subsumes-a' or nil." @@ -742,7 +765,8 @@ like (* (* X) ... (* X))." (t (error "Backslash at end of regexp"))) -(when (and warnings (cdr sequence)) +(when (and warnings (cdr sequence) + (not (looking-at (rx (or (any "?*+") "\\{") (let* ((item (car sequence)) (prev-item (cadr sequence)) (subsumption (xr--adjacent-subsumption prev-item item))) @@ -750,7 +774,27 @@ like (* (* X) ... (* X))." (xr--report warnings item-start (if (eq subsumption 'a-subsumes-b) "Repetition subsumed by preceding repetition" -"Repetition subsumes preceding repetition"))) +"Repetition subsumes preceding repetition"))) + +;; Check for anchors conflicting with previous/next character. +(cond + ((and (xr--may-end-in-eol-p prev-item) + (not (xr--may-start-in-nl-p item))) + (xr--report warnings item-start + "End-of-line anchor followed by non-newline")) + ((and (xr--may-start-in-bol-p item) + (not (xr--may-end-in-nl-p prev-item))) + (xr--report warnings item-start + "Non-newline followed by line-start anchor")) + ((and (xr--may-end-in-eos-p prev-item) + (xr--matches-nonempty-only-p item)) + (xr--report warnings item-start + "End-of-text anchor followed by non-empty pattern")) + ;; FIXME: We don't complain about non-empty followed by + ;; bos because it may be the start of unmatchable. + ;; We should really do these checks in a later pass, + ;; and maintain location information. + ) (let ((item-seq (xr--rev-join-seq sequence))) (cond ((null item-seq) @@ -760,6 +804,76 @@ like (* (* X) ... (* X))." (t (cons 'seq item-seq)) +(defun xr--may-start-in-bol-p (item) + (pcase item +('bol t) +(`(,(or 'seq 'opt 'zero-or-more 'one-or-more ?? '*? '+? 'group) ,first . ,_) + (xr--may-start-in-bol-p first)) +(`(group-n ,_ ,first . ,_) + (xr--may-start-in-bol-p first)) +(`(or . ,items) (cl-some #'xr--may-start-in-bol-p items + +(defun xr--may-end-in-eol-p (item) + (pcase item +('eol t) +(`(,(or 'seq 'opt
[elpa] externals/xr updated (434b300 -> 35dbbeb)
mattiase pushed a change to branch externals/xr. from 434b300 Increment version to 1.18 new e5b51bf Add wrapped subsumption in repeated forms new 7160235 Refactor repetition subsumption check to avoid code duplication new c98bb7b Handle whitespace and word syntax subsumption in one place new c6e12b7 Handle \w and \W in subsumption checks new f3b61ef Fix false negative in empty string repetition check new 21eab3c Check for bol, eol and eos in conflict with other expressions new c7e7557 Broaden anchor check to check more paths new d0b09e1 Add filename-specific checks new d752aab Use "option" instead of "repetition" in diagnostics for ? and ?? new 35dbbeb Increment version to 1.19 Summary of changes: xr-test.el | 91 ++- xr.el | 502 +++-- 2 files changed, 469 insertions(+), 124 deletions(-)
[elpa] externals/xr c7e7557 07/10: Broaden anchor check to check more paths
branch: externals/xr commit c7e7557db435cd6553c81592394de0358225f079 Author: Mattias Engdegård Commit: Mattias Engdegård Broaden anchor check to check more paths Check both AB, A?B and AB? (but not A?B?) where A and B are an anchor and conflicting expression, in some order. --- xr-test.el | 26 +++- xr.el | 209 + 2 files changed, 163 insertions(+), 72 deletions(-) diff --git a/xr-test.el b/xr-test.el index b426d30..c0c428d 100644 --- a/xr-test.el +++ b/xr-test.el @@ -621,14 +621,34 @@ (ert-deftest xr-lint-bad-anchor () (let ((text-quoting-style 'grave)) -(should (equal (xr-lint "a\\(?:^b$\\)c") - '((1 . "Non-newline followed by line-start anchor") - (10 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint "a\\(?:^\\)") + '((1 . "Non-newline followed by line-start anchor" +(should (equal (xr-lint "a?\\(?:^\\)") + '((2 . "Non-newline followed by line-start anchor" +(should (equal (xr-lint "a\\(?:^\\|b\\)") + '((1 . "Non-newline followed by line-start anchor" +(should (equal (xr-lint "a?\\(?:^\\|b\\)") + nil)) +(should (equal (xr-lint "\\(?:$\\)a") + '((7 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint "\\(?:$\\)\\(\n\\|a\\)") + '((7 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint "\\(?:$\\|b\\)a") + '((10 . "End-of-line anchor followed by non-newline" +(should (equal (xr-lint "\\(?:$\\|b\\)\\(\n\\|a\\)") + nil)) (should (equal (xr-lint ".\\(?:^$\\).") '((1 . "Non-newline followed by line-start anchor") (9 . "End-of-line anchor followed by non-newline" (should (equal (xr-lint "\\'b") '((2 . "End-of-text anchor followed by non-empty pattern" +(should (equal (xr-lint "\\'b?") + '((3 . "End-of-text anchor followed by non-empty pattern" +(should (equal (xr-lint "\\(?:a\\|\\'\\)b") + '((11 . + "End-of-text anchor followed by non-empty pattern" +(should (equal (xr-lint "\\(?:a\\|\\'\\)b?") + nil)) )) (ert-deftest xr-skip-set () diff --git a/xr.el b/xr.el index 6467624..a10be63 100644 --- a/xr.el +++ b/xr.el @@ -777,24 +777,41 @@ like (* (* X) ... (* X))." "Repetition subsumes preceding repetition"))) ;; Check for anchors conflicting with previous/next character. -(cond - ((and (xr--may-end-in-eol-p prev-item) - (not (xr--may-start-in-nl-p item))) - (xr--report warnings item-start - "End-of-line anchor followed by non-newline")) - ((and (xr--may-start-in-bol-p item) - (not (xr--may-end-in-nl-p prev-item))) - (xr--report warnings item-start - "Non-newline followed by line-start anchor")) - ((and (xr--may-end-in-eos-p prev-item) - (xr--matches-nonempty-only-p item)) - (xr--report warnings item-start - "End-of-text anchor followed by non-empty pattern")) - ;; FIXME: We don't complain about non-empty followed by - ;; bos because it may be the start of unmatchable. - ;; We should really do these checks in a later pass, - ;; and maintain location information. - ) +;; To avoid false positives, we require that at least one +;; of the items is present in all paths. +(let ((prev-eol (xr--ends-with-sym 'eol prev-item))) + (when prev-eol +(let ((this-nonl (xr--starts-with-nonl item))) + (when (and this-nonl + (or (eq prev-eol 'always) + (eq this-nonl 'always))) +(xr--report + warnings item-start + "End-of-line anchor followed by non-newline") +(let ((this-bol (xr--starts-with-sym 'bol item))) + (when this-bol +(let ((prev-nonl (xr--ends-with-nonl prev-item))) + (when (and prev-nonl + (or (eq prev-nonl 'always) + (eq this-bol 'always))) +(xr--report + warnings item-start + "Non-newline followed by line-start anchor") +(let ((prev-eos (xr--ends-with-sym 'eos prev-item))) + (when prev-eos +(let ((this-nonempty (xr--matches-nonempty item))) + (when (and this-nonempty +
[elpa] externals/xr e5b51bf 01/10: Add wrapped subsumption in repeated forms
branch: externals/xr commit e5b51bf5608720dddac10495950258a9cd07a178 Author: Mattias Engdegård Commit: Mattias Engdegård Add wrapped subsumption in repeated forms This check finds regexps like "\\(?:a*c[ab]*\\)+", where the first and last item in a repeated sequence are considered adjacent. --- xr-test.el | 13 xr.el | 71 +- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/xr-test.el b/xr-test.el index 432b8af..2001a39 100644 --- a/xr-test.el +++ b/xr-test.el @@ -592,6 +592,19 @@ (46 . "Repetition subsumed by preceding repetition" )) +(ert-deftest xr-lint-wrapped-subsumption () + (let ((text-quoting-style 'grave)) +(should (equal + (xr-lint "\\(?:a*x[ab]+\\)*") + '((14 . "Last item in repetition subsumes first item (wrapped)" +(should (equal + (xr-lint "\\([ab]*xya?\\)+") + '((13 . "First item in repetition subsumes last item (wrapped)" +(should (equal + (xr-lint "\\(?3:a*xa*\\)\\{7\\}") + '((14 . "First item in repetition subsumes last item (wrapped)" +)) + (ert-deftest xr-skip-set () (should (equal (xr-skip-set "0-9a-fA-F+*") '(any "0-9a-fA-F" "+*"))) diff --git a/xr.el b/xr.el index 5e7a11f..985f4d8 100644 --- a/xr.el +++ b/xr.el @@ -439,6 +439,65 @@ UPPER may be nil, meaning infinity." (cl-every #'xr--matches-empty-p body)) ("" t))) +(defun xr--adjacent-subsumption (a b) + "Check if A subsumes B, or vice versa, or not, assuming they are adjacent. +Return `a-subsumes-b', `b-subsumes-a' or nil." + ;; Check for subsuming repetitions in sequence: (Ra A) (Rb B) + ;; where Ra and Rb are repetition operators, and A and B are operands. + ;; We conclude that (Ra A) subsumes (Rb B), in the sense that the + ;; sequence is equivalent to just (Ra A), if: + ;; A matches a superset of B + ;; and Ra can match infinitely many times + ;; and Rb can match zero times + ;; and Rb is non-greedy if Ra is non-greedy. + ;; Example: [cd]+c? + (let ((a-expr (and (consp a) + (memq (car a) + '(zero-or-more one-or-more opt *? +? ??)) + (xr--make-seq (cdr a) +(when a-expr + (let ((b-expr (and (consp b) + (memq (car b) + '(zero-or-more one-or-more opt *? +? ??)) + (xr--make-seq (cdr b) +(when b-expr + (let ((a-op (car a)) +(b-op (car b))) +;; Test the same condition twice, but mirrored. +(cond + ((and (memq b-op '(zero-or-more opt *? ??)) + (memq a-op '(zero-or-more one-or-more *? +?)) + (not (and (memq a-op '(*? +?)) + (memq b-op '(zero-or-more opt + (xr--superset-p a-expr b-expr)) + 'a-subsumes-b) + ((and (memq a-op '(zero-or-more opt *? ??)) + (memq b-op '(zero-or-more one-or-more *? +?)) + (not (and (memq b-op '(*? +?)) + (memq a-op '(zero-or-more opt + (xr--superset-p b-expr a-expr)) + 'b-subsumes-a + +(defun xr--check-wrap-around-repetition (operand pos warnings) + "Whether OPERAND has a wrap-around repetition subsumption case, +like (* (* X) ... (* X))." + (when (and (consp operand) + (memq (car operand) '(seq group group-n))) +(let* ((operands +(if (eq (car operand) 'group-n) +(cddr operand) + (cdr operand + (when (cddr operands) +(let* ((first (car operands)) + (last (car (last operands))) + (subsumption (xr--adjacent-subsumption first last))) + (when subsumption +(xr--report + warnings pos + (if (eq subsumption 'a-subsumes-b) + "First item in repetition subsumes last item (wrapped)" + "Last item in repetition subsumes first item (wrapped)" + (defun xr--parse-seq (warnings) (let ((sequence nil)) ; reversed (while (not (looking-at (rx (or "\\|" "\\)" eos @@ -502,7 +561,11 @@ UPPER may be nil, meaning infinity." (not (equal operand ""))) (xr--report warnings (match-beginning 0) - "Repetition of expression matching an empty string" + "Repetition of expression matching an empty string"))) + ;; (* (* X) ... (* X)) etc: wrap-around subsumption + (when (member operator '("*" "+" "*?" "+?")) +(xr--check-wrap-around-repetition + operand (match-beginning 0) warnings)))