]> code.delx.au - gnu-emacs/blob - lisp/progmodes/cwarn.el
(executable-set-magic): If
[gnu-emacs] / lisp / progmodes / cwarn.el
1 ;;; cwarn.el --- highlight suspicious C and C++ constructions
2
3 ;; Copyright (C) 1999, 2000 Free Software Foundation, Inc.
4
5 ;; Author: Anders Lindgren <andersl@andersl.com>
6 ;; Keywords: c, languages, faces
7 ;; X-Url: http://www.andersl.com/emacs
8 ;; Version: 1.3.1 1999-12-13
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28
29 ;;{{{ Documentation
30
31 ;; Description:
32 ;;
33 ;; CWarn is a package that highlights suspicious C and C++ constructions.
34 ;;
35 ;; For example, take a look at the following piece of C code:
36 ;;
37 ;; if (x = 0);
38 ;; foo();
39 ;;
40 ;; The code contains two, possibly fatal, bugs. The first is that the
41 ;; assignment operator "=" is used as part of the test; the user
42 ;; probably ment to use the comparison operator "==".
43 ;;
44 ;; The second problem is that an extra semicolon is placed after
45 ;; closing parenthesis of the test expression. This makes the body of
46 ;; the if statement to be an empty statement, not the call to the
47 ;; function "foo", as the user probably intended.
48 ;;
49 ;; This package is capable of highlighting the following C and C++
50 ;; constructions:
51 ;;
52 ;; * Assignments inside expressions, including variations like "+=".
53 ;; * Semicolon following immediately after `if', `for', and `while'
54 ;; (except, of course, after a `do .. while' statement).
55 ;; * C++ functions with reference parameters.
56 ;;
57 ;; Note that none of the constructions highlighted (especially not C++
58 ;; reference parameters) are considered errors by the langauage
59 ;; definitions.
60
61 ;; Usage:
62 ;;
63 ;; CWarn is implemented as two minor modes: `cwarn-mode' and
64 ;; `global-cwarn-mode'. The former can be applied to individual buffers
65 ;; and the latter to all buffers.
66 ;;
67 ;; Activate this package by Customize, or by placing the following line
68 ;; into the appropriate init file:
69 ;;
70 ;; (global-cwarn-mode 1)
71 ;;
72 ;; Also, `font-lock-mode' or `global-font-lock-mode' must be enabled.
73
74 ;; Afterthought:
75 ;;
76 ;; After using this package for several weeks it feels as though I
77 ;; find stupid typo-style bugs while editing rather than at compile-
78 ;; or run-time, if I ever find them.
79 ;;
80 ;; On the other hand, I find myself using assignments inside
81 ;; expressions much more often than I used to do. The reason is that
82 ;; there is no risk of interpreting an assignment operator as a
83 ;; comparison ("hey, the assignment operator is red, duh!").
84
85 ;; Reporting bugs:
86 ;;
87 ;; Out of the last ten bugs you found, how many did you report?
88 ;;
89 ;; When reporting a bug, please:
90 ;;
91 ;; * Send a mail the maintainer of the package, or to the author
92 ;; if no maintainer exists.
93 ;; * Include the name of the package in the title of the mail, to
94 ;; simplify for the recipient.
95 ;; * State exactly what you did, what happened, and what you expected
96 ;; to see when you found the bug.
97 ;; * If the bug cause an error, set the variable `debug-on-error' to t,
98 ;; repreat the operations that triggered the error and include
99 ;; the backtrace in the letter.
100 ;; * If possible, include an example that activates the bug.
101 ;; * Should you speculate about the cause of the problem, please
102 ;; state explicitly that you are guessing.
103
104 ;;}}}
105
106 ;;; Code:
107
108 ;;{{{ Dependencies
109
110 (eval-when-compile (require 'cl))
111
112 (require 'custom)
113 (require 'font-lock)
114 (require 'cc-mode)
115
116 ;;}}}
117 ;;{{{ Variables
118
119 (defgroup cwarn nil
120 "Highlight suspicious C and C++ constructions."
121 :version "21.1"
122 :link '(url-link "http://www.andersl.com/emacs")
123 :group 'faces)
124
125 (defvar cwarn-mode nil
126 "*Non-nil when Cwarn mode is active.
127
128 Never set this variable directly, use the command `cwarn-mode'
129 instead.")
130
131 (defcustom global-cwarn-mode nil
132 "When on, suspicious C and C++ constructions are highlighted.
133
134 Set this variable using \\[customize] or use the command
135 `global-cwarn-mode'."
136 :group 'cwarn
137 :initialize 'custom-initialize-default
138 :set (lambda (symbol value)
139 (global-cwarn-mode (or value 0)))
140 :type 'boolean
141 :require 'cwarn)
142
143 (defcustom cwarn-configuration
144 '((c-mode (not reference))
145 (c++-mode t))
146 "*List of items each describing which features are enable for a mode.
147 Each item is on the form (mode featurelist), where featurelist can be
148 on one of three forms:
149
150 * A list of enabled features.
151 * A list starting with the atom `not' followed by the features
152 which are not enabled.
153 * The atom t, that represent that all features are enabled.
154
155 See variable `cwarn-font-lock-feature-keywords-alist' for available
156 features."
157 :type '(repeat sexp)
158 :group 'cwarn)
159
160 (defcustom cwarn-font-lock-feature-keywords-alist
161 '((assign . cwarn-font-lock-assignment-keywords)
162 (semicolon . cwarn-font-lock-semicolon-keywords)
163 (reference . cwarn-font-lock-reference-keywords))
164 "An alist mapping a CWarn feature to font-lock keywords.
165 The keywords could either a font-lock keyword list or a symbol.
166 If it is a symbol it is assumed to be a variable containing a font-lock
167 keyword list."
168 :type '(alist :key-type (choice (const assign)
169 (const semicolon)
170 (const reference))
171 :value-type face)
172 :group 'cwarn)
173
174 (defcustom cwarn-verbose t
175 "*When nil, CWarn mode will not generate any messages.
176
177 Currently, messages are generated when the mode is activated and
178 deactivated."
179 :group 'cwarn
180 :type 'boolean)
181
182 (defcustom cwarn-mode-text " CWarn"
183 "*String to display in the mode line when CWarn mode is active.
184
185 \(When the string is not empty, make sure that it has a leading space.)"
186 :tag "CWarn mode text" ; To separate it from `global-...'
187 :group 'cwarn
188 :type 'string)
189
190 (defcustom cwarn-mode-hook nil
191 "*Functions to run when CWarn mode is activated."
192 :tag "CWarn mode hook" ; To separate it from `global-...'
193 :group 'cwarn
194 :type 'hook)
195
196 (defcustom global-cwarn-mode-text ""
197 "*String to display when Global CWarn mode is active.
198
199 The default is nothing since when this mode is active this text doesn't
200 vary over time, or between buffers. Hence mode line text
201 would only waste precious space."
202 :group 'cwarn
203 :type 'string)
204
205 (defcustom global-cwarn-mode-hook nil
206 "*Hook called when Global CWarn mode is activated."
207 :group 'cwarn
208 :type 'hook)
209
210 (defcustom cwarn-load-hook nil
211 "*Functions to run when CWarn mode is first loaded."
212 :tag "Load Hook"
213 :group 'cwarn
214 :type 'hook)
215
216 ;;}}}
217 ;;{{{ The modes
218
219 ;;;###autoload
220 (defun cwarn-mode (&optional arg)
221 "Minor mode that highlights suspicious C and C++ constructions.
222
223 Note, in addition to enabling this minor mode, the major mode must
224 be included in the variable `cwarn-configuration'. By default C and
225 C++ modes are included.
226
227 With ARG, turn CWarn mode on if and only if arg is positive."
228 (interactive "P")
229 (make-local-variable 'cwarn-mode)
230 (setq cwarn-mode
231 (if (null arg)
232 (not cwarn-mode)
233 (> (prefix-numeric-value arg) 0)))
234 (if (and cwarn-verbose
235 (interactive-p))
236 (message "Cwarn mode is now %s."
237 (if cwarn-mode "on" "off")))
238 (if (not global-cwarn-mode)
239 (if cwarn-mode
240 (cwarn-font-lock-add-keywords)
241 (cwarn-font-lock-remove-keywords)))
242 (font-lock-fontify-buffer)
243 (if cwarn-mode
244 (run-hooks 'cwarn-mode-hook)))
245
246 ;;;###autoload
247 (defun turn-on-cwarn-mode ()
248 "Turn on CWarn mode.
249
250 This function is designed to be added to hooks, for example:
251 (add-hook 'c-mode-hook 'turn-on-cwarn-mode)"
252 (cwarn-mode 1))
253
254 ;;;###autoload
255 (defun global-cwarn-mode (&optional arg)
256 "Hightlight suspicious C and C++ constructions in all buffers.
257
258 With ARG, turn CWarn mode on globally if and only if arg is positive."
259 (interactive "P")
260 (let ((old-global-cwarn-mode global-cwarn-mode))
261 (setq global-cwarn-mode
262 (if (null arg)
263 (not global-cwarn-mode)
264 (> (prefix-numeric-value arg) 0)))
265 (if (and cwarn-verbose
266 (interactive-p))
267 (message "Global CWarn mode is now %s."
268 (if global-cwarn-mode "on" "off")))
269 (when (not (eq global-cwarn-mode old-global-cwarn-mode))
270 ;; Update for all future buffers.
271 (dolist (conf cwarn-configuration)
272 (if global-cwarn-mode
273 (cwarn-font-lock-add-keywords (car conf))
274 (cwarn-font-lock-remove-keywords (car conf))))
275 ;; Update all existing buffers.
276 (save-excursion
277 (dolist (buffer (buffer-list))
278 (set-buffer buffer)
279 ;; Update keywords in alive buffers.
280 (when (and font-lock-mode
281 (not cwarn-mode)
282 (cwarn-is-enabled major-mode))
283 (if global-cwarn-mode
284 (cwarn-font-lock-add-keywords)
285 (cwarn-font-lock-remove-keywords))
286 (font-lock-fontify-buffer))))))
287 ;; Kills all added keywords :-(
288 ;; (font-lock-mode 0)
289 ;; (makunbound 'font-lock-keywords)
290 ;; (font-lock-mode 1))))
291 (when global-cwarn-mode
292 (run-hooks 'global-cwarn-mode-hook)))
293
294 ;;}}}
295 ;;{{{ Help functions
296
297 (defun cwarn-is-enabled (mode &optional feature)
298 "Non-nil if CWarn FEATURE is enabled for MODE.
299 feature is an atom representing one construction to highlight.
300
301 Check if any feature is enabled for MODE if no feature is specified.
302
303 The valid features are described by the variable
304 `cwarn-font-lock-feature-keywords-alist'."
305 (let ((mode-configuraion (assq mode cwarn-configuration)))
306 (and mode-configuraion
307 (or (null feature)
308 (let ((list-or-t (nth 1 mode-configuraion)))
309 (or (eq list-or-t t)
310 (if (eq (car-safe list-or-t) 'not)
311 (not (memq feature (cdr list-or-t)))
312 (memq feature list-or-t))))))))
313
314 (defun cwarn-inside-macro ()
315 "True if point is inside a C macro definition."
316 (save-excursion
317 (beginning-of-line)
318 (while (eq (char-before (1- (point))) ?\\)
319 (forward-line -1))
320 (back-to-indentation)
321 (eq (char-after) ?#)))
322
323 (defun cwarn-font-lock-add-keywords (&optional mode)
324 "Install keywords into major MODE, or into current buffer if nil."
325 (dolist (pair cwarn-font-lock-feature-keywords-alist)
326 (let ((feature (car pair))
327 (keywords (cdr pair)))
328 (if (not (listp keywords))
329 (setq keywords (symbol-value keywords)))
330 (if (cwarn-is-enabled (or mode major-mode) feature)
331 (font-lock-add-keywords mode keywords)))))
332
333 (defun cwarn-font-lock-remove-keywords (&optional mode)
334 "Remove keywords from major MODE, or from current buffer if nil."
335 (dolist (pair cwarn-font-lock-feature-keywords-alist)
336 (let ((feature (car pair))
337 (keywords (cdr pair)))
338 (if (not (listp keywords))
339 (setq keywords (symbol-value keywords)))
340 (if (cwarn-is-enabled (or mode major-mode) feature)
341 (font-lock-remove-keywords mode keywords)))))
342
343 ;;}}}
344 ;;{{{ Backward compatibility
345
346 ;; This piece of code will be part of CC mode as of Emacs 20.4.
347 (if (not (fboundp 'c-at-toplevel-p))
348 (defun c-at-toplevel-p ()
349 "Return a determination as to whether point is at the `top-level'.
350 Being at the top-level means that point is either outside any
351 enclosing block (such function definition), or inside a class
352 definition, but outside any method blocks.
353
354 If point is not at the top-level (e.g. it is inside a method
355 definition), then nil is returned. Otherwise, if point is at a
356 top-level not enclosed within a class definition, t is returned.
357 Otherwise, a 2-vector is returned where the zeroth element is the
358 buffer position of the start of the class declaration, and the first
359 element is the buffer position of the enclosing class's opening
360 brace."
361 (let ((state (c-parse-state)))
362 (or (not (c-most-enclosing-brace state))
363 (c-search-uplist-for-classkey state))))
364 )
365
366 ;;}}}
367 ;;{{{ Font-lock keywords and match functions
368
369 ;; This section contains font-lock keywords. A font lock keyword can
370 ;; either contain a regular expression or a match function. All
371 ;; keywords defined here use match functions since the C and C++
372 ;; constructions highlighted by CWarn are too complex to be matched by
373 ;; regular expressions.
374 ;;
375 ;; A match function should act like a normal forward search. They
376 ;; should return non-nil if they found a candidate and the match data
377 ;; should correspond to the highlight part of the font-lock keyword.
378 ;; The functions shold not generate errors, in that case font-lock
379 ;; will fail to highlight the buffer. A match function takes one
380 ;; argument, LIMIT, that represent the end of area to be searched.
381 ;;
382 ;; The variable `cwarn-font-lock-feature-keywords-alist' contains a
383 ;; mapping from CWarn features to the font-lock keywords defined
384 ;; below.
385
386 ;;{{{ Assignment in expressions
387
388 (defconst cwarn-font-lock-assignment-keywords
389 '((cwarn-font-lock-match-assignment-in-expression
390 (1 font-lock-warning-face))))
391
392 (defun cwarn-font-lock-match-assignment-in-expression (limit)
393 "Match assignments inside expressions."
394 (let ((res nil))
395 (while
396 (progn
397 (setq res (re-search-forward "[^!<>=]\\(=\\)[^=]" limit t))
398 (and res
399 (save-excursion
400 (goto-char (match-beginning 1))
401 (condition-case nil ; In case "backward-up-list" barfs.
402 (progn
403 (backward-up-list 1)
404 (or (not (memq (following-char) '(?\( ?\[)))
405 (save-match-data
406 (skip-chars-backward " ")
407 (skip-chars-backward "a-zA-Z0-9_")
408 (or
409 ;; Default parameter of function.
410 (c-at-toplevel-p)
411 (looking-at "for\\>")))))
412 (error t))))))
413 res))
414
415 ;;}}}
416 ;;{{{ Semicolon
417
418 (defconst cwarn-font-lock-semicolon-keywords
419 '((cwarn-font-lock-match-dangerous-semicolon (0 font-lock-warning-face))))
420
421 (defun cwarn-font-lock-match-dangerous-semicolon (limit)
422 "Match semicolons directly after `for', `while', and `if'.
423 Tne semicolon after a `do { ... } while (x);' construction is not matched."
424 (let ((res nil))
425 (while
426 (progn
427 (setq res (search-forward ";" limit t))
428 (and res
429 (save-excursion
430 (condition-case nil ; In case something barfs.
431 (save-match-data
432 (backward-sexp 2) ; Expression and keyword.
433 (not (or (looking-at "\\(for\\|if\\)\\>")
434 (and (looking-at "while\\>")
435 (condition-case nil
436 (progn
437 (backward-sexp 2) ; Body and "do".
438 (not (looking-at "do\\>")))
439 (error t))))))
440 (error t))))))
441 res))
442
443 ;;}}}
444 ;;{{{ Reference
445
446 (defconst cwarn-font-lock-reference-keywords
447 '((cwarn-font-lock-match-reference (1 font-lock-warning-face))))
448
449 (defun cwarn-font-lock-match-reference (limit)
450 "Font-lock matcher for C++ reference parameters."
451 (let ((res nil))
452 (while
453 (progn
454 (setq res (re-search-forward "[^&]\\(&\\)[^&=]" limit t))
455 (and res
456 (save-excursion
457 (goto-char (match-beginning 1))
458 (condition-case nil ; In case something barfs.
459 (save-match-data
460 (backward-up-list 1)
461 (or (not (eq (following-char) ?\())
462 (cwarn-inside-macro)
463 (not (c-at-toplevel-p))))
464 (error t))))))
465 res))
466
467 ;;}}}
468
469 ;;}}}
470 ;;{{{ The end
471
472 (unless (assq 'cwarn-mode minor-mode-alist)
473 (push '(cwarn-mode cwarn-mode-text)
474 minor-mode-alist))
475 (unless (assq 'global-cwarn-mode minor-mode-alist)
476 (push '(global-cwarn-mode global-cwarn-mode-text)
477 minor-mode-alist))
478
479 (provide 'cwarn)
480
481 (run-hooks 'cwarn-load-hook)
482
483 ;; This makes it possible to set Global CWarn mode from
484 ;; Customize.
485 (if global-cwarn-mode
486 (global-cwarn-mode 1))
487
488 ;;}}}
489
490 ;;; cwarn.el ends here