summaryrefslogtreecommitdiff
path: root/lisp/progmodes/cwarn.el
blob: 7fd592fb2e1985cdb9158a154e6f2b1c312f308d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
;;; cwarn.el --- highlight suspicious C and C++ constructions  -*- lexical-binding: t -*-

;; Copyright (C) 1999-2021 Free Software Foundation, Inc.

;; Author: Anders Lindgren
;; Keywords: c, languages, faces
;; Old-Version: 1.3.1

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;{{{ Documentation

;; Description:
;;
;; CWarn is a package that highlights suspicious C and C++ constructions.
;;
;; For example, take a look at the following piece of C code:
;;
;;     if (x = 0);
;;       foo();
;;
;; The code contains two, possibly fatal, bugs.  The first is that the
;; assignment operator "=" is used as part of the test; the user
;; probably meant to use the comparison operator "==".
;;
;; The second problem is that an extra semicolon is placed after
;; closing parenthesis of the test expression.  This makes the body of
;; the if statement to be an empty statement, not the call to the
;; function "foo", as the user probably intended.
;;
;; This package is capable of highlighting the following C and C++
;; constructions:
;;
;; * Assignments inside expressions, including variations like "+=".
;; * Semicolon following immediately after `if', `for', and `while'
;;   (except, of course, after a `do .. while' statement).
;; * C++ functions with reference parameters.
;;
;; Note that none of the constructions highlighted (especially not C++
;; reference parameters) are considered errors by the language
;; definitions.

;; Usage:
;;
;; CWarn is implemented as two minor modes: `cwarn-mode' and
;; `global-cwarn-mode'.  The former can be applied to individual buffers
;; and the latter to all buffers.
;;
;; Activate this package by Customize, or by placing the following line
;; into the appropriate init file:
;;
;;    (global-cwarn-mode 1)
;;
;; Also, `font-lock-mode' or `global-font-lock-mode' must be enabled.

;; Afterthought:
;;
;; After using this package for several weeks it feels as though I
;; find stupid typo-style bugs while editing rather than at compile-
;; or run-time, if I ever find them.
;;
;; On the other hand, I find myself using assignments inside
;; expressions much more often than I used to do.  The reason is that
;; there is no risk of interpreting an assignment operator as a
;; comparison ("hey, the assignment operator is red, duh!").

;; Reporting bugs:
;;
;;     Out of the last ten bugs you found, how many did you report?
;;
;; When reporting a bug, please:
;;
;; * Send a mail the maintainer of the package, or to the author
;;   if no maintainer exists.
;; * Include the name of the package in the title of the mail, to
;;   simplify for the recipient.
;; * State exactly what you did, what happened, and what you expected
;;   to see when you found the bug.
;; * If the bug cause an error, set the variable `debug-on-error' to t,
;;   repeat the operations that triggered the error and include
;;   the backtrace in the letter.
;; * If possible, include an example that activates the bug.
;; * Should you speculate about the cause of the problem, please
;;   state explicitly that you are guessing.

;;}}}

;;; Code:

;;{{{ Dependencies

(require 'cc-mode)

;;}}}
;;{{{ Variables

(defgroup cwarn nil
  "Highlight suspicious C and C++ constructions."
  :version "21.1"
  :group 'faces)

(defcustom cwarn-configuration
  '((c-mode (not reference))
    (c++-mode t))
  "List of items each describing which features are enable for a mode.
Each item is on the form (mode featurelist), where featurelist can be
on one of three forms:

* A list of enabled features.
* A list starting with the atom `not' followed by the features
  which are not enabled.
* The atom t, that represent that all features are enabled.

See variable `cwarn-font-lock-feature-keywords-alist' for available
features."
  :type '(repeat sexp))

(defcustom cwarn-font-lock-feature-keywords-alist
  '((assign    . cwarn-font-lock-assignment-keywords)
    (semicolon . cwarn-font-lock-semicolon-keywords)
    (reference . cwarn-font-lock-reference-keywords))
  "An alist mapping a CWarn feature to font-lock keywords.
The keywords could either a font-lock keyword list or a symbol.
If it is a symbol it is assumed to be a variable containing a font-lock
keyword list."
  :type '(alist :key-type (choice (const assign)
				  (const semicolon)
				  (const reference))
                :value-type (sexp :tag "Value")))

(defcustom cwarn-verbose t
  "When nil, CWarn mode will not generate any messages.

Currently, messages are generated when the mode is activated and
deactivated."
  :type 'boolean)

(defcustom cwarn-mode-text " CWarn"
  "String to display in the mode line when CWarn mode is active.

\(When the string is not empty, make sure that it has a leading space.)"
  :tag "CWarn mode text"                ; To separate it from `global-...'
  :type 'string)

(defcustom cwarn-load-hook nil
  "Functions to run when CWarn mode is first loaded."
  :tag "Load Hook"
  :type 'hook)
(make-obsolete-variable 'cwarn-load-hook
                        "use `with-eval-after-load' instead." "28.1")

;;}}}
;;{{{ The modes

;;;###autoload
(define-minor-mode cwarn-mode
  "Minor mode that highlights suspicious C and C++ constructions.

Suspicious constructs are highlighted using `font-lock-warning-face'.

Note, in addition to enabling this minor mode, the major mode must
be included in the variable `cwarn-configuration'.  By default C and
C++ modes are included."
  :group 'cwarn :lighter cwarn-mode-text
  (cwarn-font-lock-keywords cwarn-mode)
  (font-lock-flush))

;;;###autoload
(define-obsolete-function-alias 'turn-on-cwarn-mode 'cwarn-mode "24.1")

;;}}}
;;{{{ Help functions

(defun cwarn-is-enabled (mode &optional feature)
  "Non-nil if CWarn FEATURE is enabled for MODE.
FEATURE is an atom representing one construction to highlight.

Check if any feature is enabled for MODE if no feature is specified.

The valid features are described by the variable
`cwarn-font-lock-feature-keywords-alist'."
  (let ((mode-configuration (assq mode cwarn-configuration)))
    (and mode-configuration
	 (or (null feature)
	     (let ((list-or-t (nth 1 mode-configuration)))
	       (or (eq list-or-t t)
		   (if (eq (car-safe list-or-t) 'not)
		       (not (memq feature (cdr list-or-t)))
		     (memq feature list-or-t))))))))

(defun cwarn-inside-macro ()
  "True if point is inside a C macro definition."
  (save-excursion
    (beginning-of-line)
    (while (eq (char-before (1- (point))) ?\\)
      (forward-line -1))
    (back-to-indentation)
    (eq (char-after) ?#)))

(defun cwarn-font-lock-keywords (addp)
  "Install/remove keywords into current buffer.
If ADDP is non-nil, install else remove."
  (dolist (pair cwarn-font-lock-feature-keywords-alist)
    (let ((feature (car pair))
	  (keywords (cdr pair)))
      (if (not (listp keywords))
	  (setq keywords (symbol-value keywords)))
      (if (cwarn-is-enabled major-mode feature)
	  (funcall (if addp 'font-lock-add-keywords 'font-lock-remove-keywords)
		   nil keywords)))))

;;}}}
;;{{{ Font-lock keywords and match functions

;; This section contains font-lock keywords.  A font lock keyword can
;; either contain a regular expression or a match function.  All
;; keywords defined here use match functions since the C and C++
;; constructions highlighted by CWarn are too complex to be matched by
;; regular expressions.
;;
;; A match function should act like a normal forward search.  They
;; should return non-nil if they found a candidate and the match data
;; should correspond to the highlight part of the font-lock keyword.
;; The functions should not generate errors, in that case font-lock
;; will fail to highlight the buffer.  A match function takes one
;; argument, LIMIT, that represent the end of area to be searched.
;;
;; The variable `cwarn-font-lock-feature-keywords-alist' contains a
;; mapping from CWarn features to the font-lock keywords defined
;; below.

(defmacro cwarn-font-lock-match (re &rest body)
  "Match RE but only if BODY holds."
  `(let ((res nil))
     (while
	 (progn
	   (setq res (re-search-forward ,re limit t))
	   (and res
		(save-excursion
		  (when (match-beginning 1) (goto-char (match-beginning 1)))
		  (condition-case nil	; In case something barfs.
		      (not (save-match-data
			     ,@body))
		    (error t))))))
     res))

;;{{{ Assignment in expressions

(defconst cwarn-font-lock-assignment-keywords
  '((cwarn-font-lock-match-assignment-in-expression
     (1 font-lock-warning-face))))

(defun cwarn-font-lock-match-assignment-in-expression (limit)
  "Match assignments inside expressions."
  (cwarn-font-lock-match
   "[^!<>=]\\(\\([-+*/%&^|]\\|<<\\|>>\\)?=\\)[^=]"
   (backward-up-list 1)
   (and (memq (following-char) '(?\( ?\[))
	(not (progn
	       (skip-chars-backward " ")
	       (skip-chars-backward "a-zA-Z0-9_")
	       (or
		;; Default parameter of function.
		(c-at-toplevel-p)
		(looking-at "for\\>")))))))

;;}}}
;;{{{ Semicolon

(defconst cwarn-font-lock-semicolon-keywords
  '((cwarn-font-lock-match-dangerous-semicolon (0 font-lock-warning-face))))

(defun cwarn-font-lock-match-dangerous-semicolon (limit)
  "Match semicolons directly after `for', `while', and `if'.
The semicolon after a `do { ... } while (x);' construction is not matched."
  (cwarn-font-lock-match
   ";"
   (backward-sexp 2)			; Expression and keyword.
   (or (looking-at "\\(for\\|if\\)\\>")
       (and (looking-at "while\\>")
	    (condition-case nil
		(progn
		  (backward-sexp 2)	; Body and "do".
		  (not (looking-at "do\\>")))
	      (error t))))))

;;}}}
;;{{{ Reference

(defconst cwarn-font-lock-reference-keywords
  '((cwarn-font-lock-match-reference (1 font-lock-warning-face))))

(defun cwarn-font-lock-match-reference (limit)
  "Font-lock matcher for C++ reference parameters."
  (cwarn-font-lock-match
   "[^&]\\(&\\)[^&=]"
   (backward-up-list 1)
   (and (eq (following-char) ?\()
	(not (cwarn-inside-macro))
	(c-at-toplevel-p))))

;;}}}

;;}}}
;;{{{ The end

(defun turn-on-cwarn-mode-if-enabled ()
  "Turn on CWarn mode in the current buffer if applicable.
The mode is turned if some feature is enabled for the current
`major-mode' in `cwarn-configuration'."
  (when (cwarn-is-enabled major-mode) (cwarn-mode 1)))

;;;###autoload
(define-globalized-minor-mode global-cwarn-mode
  cwarn-mode turn-on-cwarn-mode-if-enabled)

(provide 'cwarn)

(run-hooks 'cwarn-load-hook)

;;}}}

;;; cwarn.el ends here