aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoam Postavsky2018-03-10 18:12:55 -0500
committerNoam Postavsky2019-11-28 18:10:07 -0500
commitb2790db049da98b541d07bac21ca7d7c220d3be0 (patch)
tree61aba17be4946bf84dc0dd57b1d7157b88725c83
parent85f586f3ce5c6d9598d345440fd57e0fc9b8d98b (diff)
downloademacs-b2790db049da98b541d07bac21ca7d7c220d3be0.tar.gz
emacs-b2790db049da98b541d07bac21ca7d7c220d3be0.zip
Improve errors & warnings due to fancy quoted vars (Bug#32939)
Add some hints to the message for byte compiler free & unused variable warnings, and 'void-variable' errors where the variable has confusable quote characters in it. * lisp/help.el (uni-confusables), uni-confusables-regexp): New constants. (help-command-error-confusable-suggestions): New function, added to `command-error-function'. (help-uni-confusable-suggestions): New function. * lisp/emacs-lisp/bytecomp.el (byte-compile-variable-ref): * lisp/emacs-lisp/cconv.el (cconv--analyze-use): Use it. * lisp/emacs-lisp/lisp-mode.el (lisp--match-confusable-symbol-character): New function. (lisp-fdefs): Use it to fontify confusable characters with font-lock-warning-face when they occur in symbol names. * doc/lispref/modes.texi (Faces for Font Lock): * doc/lispref/objects.texi (Basic Char Syntax): Recommend backslash escaping of confusable characters, and mention new fontification. * etc/NEWS: Announce the new fontification behavior. * test/lisp/emacs-lisp/lisp-mode-tests.el (lisp-fontify-confusables): New test.
-rw-r--r--doc/lispref/modes.texi3
-rw-r--r--doc/lispref/objects.texi6
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/emacs-lisp/bytecomp.el10
-rw-r--r--lisp/emacs-lisp/cconv.el6
-rw-r--r--lisp/emacs-lisp/lisp-mode.el18
-rw-r--r--lisp/help.el49
-rw-r--r--test/lisp/emacs-lisp/lisp-mode-tests.el26
8 files changed, 116 insertions, 7 deletions
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 7283930507f..c554ccdf4a0 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -3287,7 +3287,8 @@ assigned using the ordering as a guide.
3287@table @code 3287@table @code
3288@item font-lock-warning-face 3288@item font-lock-warning-face
3289@vindex font-lock-warning-face 3289@vindex font-lock-warning-face
3290for a construct that is peculiar, or that greatly changes the meaning of 3290for a construct that is peculiar (e.g., an unescaped confusable quote
3291in an Emacs Lisp symbol like @samp{‘foo}), or that greatly changes the meaning of
3291other text, like @samp{;;;###autoload} in Emacs Lisp and @samp{#error} 3292other text, like @samp{;;;###autoload} in Emacs Lisp and @samp{#error}
3292in C. 3293in C.
3293 3294
diff --git a/doc/lispref/objects.texi b/doc/lispref/objects.texi
index d9971f6839e..e948814d1f7 100644
--- a/doc/lispref/objects.texi
+++ b/doc/lispref/objects.texi
@@ -424,7 +424,11 @@ without a special escape meaning; thus, @samp{?\+} is equivalent to
424characters. However, you must add a backslash before any of the 424characters. However, you must add a backslash before any of the
425characters @samp{()[]\;"}, and you should add a backslash before any 425characters @samp{()[]\;"}, and you should add a backslash before any
426of the characters @samp{|'`#.,} to avoid confusing the Emacs commands 426of the characters @samp{|'`#.,} to avoid confusing the Emacs commands
427for editing Lisp code. You can also add a backslash before whitespace 427for editing Lisp code. You should also add a backslash before Unicode
428characters which resemble the previously mentioned @acronym{ASCII}
429ones, to avoid confusing people reading your code. Emacs will
430highlight some non-escaped commonly confused characters such as
431@samp{‘} to encourage this. You can also add a backslash before whitespace
428characters such as space, tab, newline and formfeed. However, it is 432characters such as space, tab, newline and formfeed. However, it is
429cleaner to use one of the easily readable escape sequences, such as 433cleaner to use one of the easily readable escape sequences, such as
430@samp{\t} or @samp{\s}, instead of an actual whitespace character such 434@samp{\t} or @samp{\s}, instead of an actual whitespace character such
diff --git a/etc/NEWS b/etc/NEWS
index 3a2efcfc2e0..688b3f85cbb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2973,6 +2973,11 @@ and if the new behavior breaks your code please email
2973integers, they now support the '+' and space flags. 2973integers, they now support the '+' and space flags.
2974 2974
2975+++ 2975+++
2976** In Emacs Lisp mode, symbols with confusable quotes are highlighted.
2977For example, the first character in '‘foo' would be highlighted in
2978'font-lock-warning-face'.
2979
2980+++
2976** Omitting variables after '&optional' and '&rest' is now allowed. 2981** Omitting variables after '&optional' and '&rest' is now allowed.
2977For example '(defun foo (&optional))' is no longer an error. This is 2982For example '(defun foo (&optional))' is no longer an error. This is
2978sometimes convenient when writing macros. See the ChangeLog entry 2983sometimes convenient when writing macros. See the ChangeLog entry
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 905d99a5971..118356ec26a 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -3428,7 +3428,10 @@ for symbols generated by the byte compiler itself."
3428 (boundp var) 3428 (boundp var)
3429 (memq var byte-compile-bound-variables) 3429 (memq var byte-compile-bound-variables)
3430 (memq var byte-compile-free-references)) 3430 (memq var byte-compile-free-references))
3431 (byte-compile-warn "reference to free variable `%S'" var) 3431 (let* ((varname (prin1-to-string var))
3432 (suggestions (help-uni-confusable-suggestions varname)))
3433 (byte-compile-warn "reference to free variable `%s'%s" varname
3434 (if suggestions (concat "\n " suggestions) "")))
3432 (push var byte-compile-free-references)) 3435 (push var byte-compile-free-references))
3433 (byte-compile-dynamic-variable-op 'byte-varref var)))) 3436 (byte-compile-dynamic-variable-op 'byte-varref var))))
3434 3437
@@ -3444,7 +3447,10 @@ for symbols generated by the byte compiler itself."
3444 (boundp var) 3447 (boundp var)
3445 (memq var byte-compile-bound-variables) 3448 (memq var byte-compile-bound-variables)
3446 (memq var byte-compile-free-assignments)) 3449 (memq var byte-compile-free-assignments))
3447 (byte-compile-warn "assignment to free variable `%s'" var) 3450 (let* ((varname (prin1-to-string var))
3451 (suggestions (help-uni-confusable-suggestions varname)))
3452 (byte-compile-warn "assignment to free variable `%s'%s" varname
3453 (if suggestions (concat "\n " suggestions) "")))
3448 (push var byte-compile-free-assignments)) 3454 (push var byte-compile-free-assignments))
3449 (byte-compile-dynamic-variable-op 'byte-varset var)))) 3455 (byte-compile-dynamic-variable-op 'byte-varset var))))
3450 3456
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el
index 58ca9d5f57e..09af7cb9104 100644
--- a/lisp/emacs-lisp/cconv.el
+++ b/lisp/emacs-lisp/cconv.el
@@ -591,8 +591,10 @@ FORM is the parent form that binds this var."
591 (eq ?_ (aref (symbol-name var) 0)) 591 (eq ?_ (aref (symbol-name var) 0))
592 ;; As a special exception, ignore "ignore". 592 ;; As a special exception, ignore "ignore".
593 (eq var 'ignored)) 593 (eq var 'ignored))
594 (byte-compile-warn "Unused lexical %s `%S'" 594 (let ((suggestions (help-uni-confusable-suggestions (symbol-name var))))
595 varkind var))) 595 (byte-compile-warn "Unused lexical %s `%S'%s"
596 varkind var
597 (if suggestions (concat "\n " suggestions) "")))))
596 ;; If it's unused, there's no point converting it into a cons-cell, even if 598 ;; If it's unused, there's no point converting it into a cons-cell, even if
597 ;; it's captured and mutated. 599 ;; it's captured and mutated.
598 (`(,binder ,_ t t ,_) 600 (`(,binder ,_ t t ,_)
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 5df52ebc98f..56f8ef63682 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -280,6 +280,19 @@ This will generate compile-time constants from BINDINGS."
280 `(face ,font-lock-warning-face 280 `(face ,font-lock-warning-face
281 help-echo "This \\ has no effect")))) 281 help-echo "This \\ has no effect"))))
282 282
283(defun lisp--match-confusable-symbol-character (limit)
284 ;; Match a confusable character within a Lisp symbol.
285 (catch 'matched
286 (while t
287 (if (re-search-forward uni-confusables-regexp limit t)
288 ;; Skip confusables which are backslash escaped, or inside
289 ;; strings or comments.
290 (save-match-data
291 (unless (or (eq (char-before (match-beginning 0)) ?\\)
292 (nth 8 (syntax-ppss)))
293 (throw 'matched t)))
294 (throw 'matched nil)))))
295
283(let-when-compile 296(let-when-compile
284 ((lisp-fdefs '("defmacro" "defun")) 297 ((lisp-fdefs '("defmacro" "defun"))
285 (lisp-vdefs '("defvar")) 298 (lisp-vdefs '("defvar"))
@@ -463,7 +476,10 @@ This will generate compile-time constants from BINDINGS."
463 (3 'font-lock-regexp-grouping-construct prepend)) 476 (3 'font-lock-regexp-grouping-construct prepend))
464 (lisp--match-hidden-arg 477 (lisp--match-hidden-arg
465 (0 '(face font-lock-warning-face 478 (0 '(face font-lock-warning-face
466 help-echo "Hidden behind deeper element; move to another line?"))) 479 help-echo "Hidden behind deeper element; move to another line?")))
480 (lisp--match-confusable-symbol-character
481 0 '(face font-lock-warning-face
482 help-echo "Confusable character"))
467 )) 483 ))
468 "Gaudy level highlighting for Emacs Lisp mode.") 484 "Gaudy level highlighting for Emacs Lisp mode.")
469 485
diff --git a/lisp/help.el b/lisp/help.el
index 604a365957c..8bb942abf4f 100644
--- a/lisp/help.el
+++ b/lisp/help.el
@@ -1508,6 +1508,55 @@ the same names as used in the original source code, when possible."
1508 (help--docstring-quote (format "%S" (help--make-usage fn arglist))))) 1508 (help--docstring-quote (format "%S" (help--make-usage fn arglist)))))
1509 1509
1510 1510
1511
1512;; Just some quote-like characters for now. TODO: generate this stuff
1513;; from official Unicode data.
1514(defconst uni-confusables
1515 '((#x2018 . "'") ;; LEFT SINGLE QUOTATION MARK
1516 (#x2019 . "'") ;; RIGHT SINGLE QUOTATION MARK
1517 (#x201B . "'") ;; SINGLE HIGH-REVERSED-9 QUOTATION MARK
1518 (#x201C . "\"") ;; LEFT DOUBLE QUOTATION MARK
1519 (#x201D . "\"") ;; RIGHT DOUBLE QUOTATION MARK
1520 (#x201F . "\"") ;; DOUBLE HIGH-REVERSED-9 QUOTATION MARK
1521 (#x301E . "\"") ;; DOUBLE PRIME QUOTATION MARK
1522 (#xFF02 . "'") ;; FULLWIDTH QUOTATION MARK
1523 (#xFF07 . "'") ;; FULLWIDTH APOSTROPHE
1524 ))
1525
1526(defconst uni-confusables-regexp
1527 (concat "[" (mapcar #'car uni-confusables) "]"))
1528
1529(defun help-uni-confusable-suggestions (string)
1530 "Return a message describing confusables in STRING."
1531 (let ((i 0)
1532 (confusables nil))
1533 (while (setq i (string-match uni-confusables-regexp string i))
1534 (let ((replacement (alist-get (aref string i) uni-confusables)))
1535 (push (aref string i) confusables)
1536 (setq string (replace-match replacement t t string))
1537 (setq i (+ i (length replacement)))))
1538 (when confusables
1539 (format-message
1540 (if (> (length confusables) 1)
1541 "Found confusable characters: %s; perhaps you meant: `%s'?"
1542 "Found confusable character: %s, perhaps you meant: `%s'?")
1543 (mapconcat (lambda (c) (format-message "`%c'" c))
1544 confusables ", ")
1545 string))))
1546
1547(defun help-command-error-confusable-suggestions (data _context _signal)
1548 (pcase data
1549 (`(void-variable ,var)
1550 (let ((suggestions (help-uni-confusable-suggestions
1551 (symbol-name var))))
1552 (when suggestions
1553 (princ (concat "\n " suggestions) t))))
1554 (_ nil)))
1555
1556(add-function :after command-error-function
1557 #'help-command-error-confusable-suggestions)
1558
1559
1511(provide 'help) 1560(provide 'help)
1512 1561
1513;;; help.el ends here 1562;;; help.el ends here
diff --git a/test/lisp/emacs-lisp/lisp-mode-tests.el b/test/lisp/emacs-lisp/lisp-mode-tests.el
index e4ba929ecbd..c0dd68c0a0b 100644
--- a/test/lisp/emacs-lisp/lisp-mode-tests.el
+++ b/test/lisp/emacs-lisp/lisp-mode-tests.el
@@ -20,6 +20,10 @@
20(require 'ert) 20(require 'ert)
21(require 'cl-lib) 21(require 'cl-lib)
22(require 'lisp-mode) 22(require 'lisp-mode)
23(require 'faceup)
24
25
26;;; Indentation
23 27
24(defconst lisp-mode-tests--correctly-indented-sexp "\ 28(defconst lisp-mode-tests--correctly-indented-sexp "\
25\(a 29\(a
@@ -290,5 +294,27 @@ Expected initialization file: `%s'\"
290 (insert "\"\n") 294 (insert "\"\n")
291 (lisp-indent-region (point-min) (point-max)))) 295 (lisp-indent-region (point-min) (point-max))))
292 296
297
298;;; Fontification
299
300(ert-deftest lisp-fontify-confusables ()
301 "Unescaped 'smart quotes' should be fontified in `font-lock-warning-face'."
302 (with-temp-buffer
303 (dolist (ch
304 '(#x2018 ;; LEFT SINGLE QUOTATION MARK
305 #x2019 ;; RIGHT SINGLE QUOTATION MARK
306 #x201B ;; SINGLE HIGH-REVERSED-9 QUOTATION MARK
307 #x201C ;; LEFT DOUBLE QUOTATION MARK
308 #x201D ;; RIGHT DOUBLE QUOTATION MARK
309 #x201F ;; DOUBLE HIGH-REVERSED-9 QUOTATION MARK
310 #x301E ;; DOUBLE PRIME QUOTATION MARK
311 #xFF02 ;; FULLWIDTH QUOTATION MARK
312 #xFF07 ;; FULLWIDTH APOSTROPHE
313 ))
314 (insert (format "«w:%c»foo \\%cfoo\n" ch ch)))
315 (let ((faceup (buffer-string)))
316 (faceup-clean-buffer)
317 (should (faceup-test-font-lock-buffer 'emacs-lisp-mode faceup)))))
318
293(provide 'lisp-mode-tests) 319(provide 'lisp-mode-tests)
294;;; lisp-mode-tests.el ends here 320;;; lisp-mode-tests.el ends here