diff options
| author | Noam Postavsky | 2018-03-10 18:12:55 -0500 |
|---|---|---|
| committer | Noam Postavsky | 2019-11-28 18:10:07 -0500 |
| commit | b2790db049da98b541d07bac21ca7d7c220d3be0 (patch) | |
| tree | 61aba17be4946bf84dc0dd57b1d7157b88725c83 | |
| parent | 85f586f3ce5c6d9598d345440fd57e0fc9b8d98b (diff) | |
| download | emacs-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.texi | 3 | ||||
| -rw-r--r-- | doc/lispref/objects.texi | 6 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 10 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cconv.el | 6 | ||||
| -rw-r--r-- | lisp/emacs-lisp/lisp-mode.el | 18 | ||||
| -rw-r--r-- | lisp/help.el | 49 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/lisp-mode-tests.el | 26 |
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 |
| 3290 | for a construct that is peculiar, or that greatly changes the meaning of | 3290 | for a construct that is peculiar (e.g., an unescaped confusable quote |
| 3291 | in an Emacs Lisp symbol like @samp{‘foo}), or that greatly changes the meaning of | ||
| 3291 | other text, like @samp{;;;###autoload} in Emacs Lisp and @samp{#error} | 3292 | other text, like @samp{;;;###autoload} in Emacs Lisp and @samp{#error} |
| 3292 | in C. | 3293 | in 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 | |||
| 424 | characters. However, you must add a backslash before any of the | 424 | characters. However, you must add a backslash before any of the |
| 425 | characters @samp{()[]\;"}, and you should add a backslash before any | 425 | characters @samp{()[]\;"}, and you should add a backslash before any |
| 426 | of the characters @samp{|'`#.,} to avoid confusing the Emacs commands | 426 | of the characters @samp{|'`#.,} to avoid confusing the Emacs commands |
| 427 | for editing Lisp code. You can also add a backslash before whitespace | 427 | for editing Lisp code. You should also add a backslash before Unicode |
| 428 | characters which resemble the previously mentioned @acronym{ASCII} | ||
| 429 | ones, to avoid confusing people reading your code. Emacs will | ||
| 430 | highlight some non-escaped commonly confused characters such as | ||
| 431 | @samp{‘} to encourage this. You can also add a backslash before whitespace | ||
| 428 | characters such as space, tab, newline and formfeed. However, it is | 432 | characters such as space, tab, newline and formfeed. However, it is |
| 429 | cleaner to use one of the easily readable escape sequences, such as | 433 | cleaner 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 |
| @@ -2973,6 +2973,11 @@ and if the new behavior breaks your code please email | |||
| 2973 | integers, they now support the '+' and space flags. | 2973 | integers, they now support the '+' and space flags. |
| 2974 | 2974 | ||
| 2975 | +++ | 2975 | +++ |
| 2976 | ** In Emacs Lisp mode, symbols with confusable quotes are highlighted. | ||
| 2977 | For 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. |
| 2977 | For example '(defun foo (&optional))' is no longer an error. This is | 2982 | For example '(defun foo (&optional))' is no longer an error. This is |
| 2978 | sometimes convenient when writing macros. See the ChangeLog entry | 2983 | sometimes 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 |