Fix a comment whitespace typo.
[emacs.git] / lisp / progmodes / flymake-proc.el
blobaf16e522c3a532cb4f07052f55f553ce41b7bf6a
1 ;;; flymake-proc.el --- Flymake for external syntax checker processes -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2003-2017 Free Software Foundation, Inc.
5 ;; Author: Pavel Kobyakov <pk_at_work@yahoo.com>
6 ;; Maintainer: Leo Liu <sdl.web@gmail.com>
7 ;; Version: 0.3
8 ;; Keywords: c languages tools
10 ;; This file is part of GNU Emacs.
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 3 of the License, or
15 ;; (at your option) any later version.
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.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25 ;;; Commentary:
27 ;; Flymake is a minor Emacs mode performing on-the-fly syntax checks.
29 ;; This file contains the most original implementation of flymake's
30 ;; main source of on-the-fly diagnostic info, the external syntax
31 ;; checker backend.
33 ;;; Bugs/todo:
35 ;; - Only uses "Makefile", not "makefile" or "GNUmakefile"
36 ;; (from http://bugs.debian.org/337339).
38 ;;; Code:
40 (require 'flymake-ui)
42 (defcustom flymake-compilation-prevents-syntax-check t
43 "If non-nil, don't start syntax check if compilation is running."
44 :group 'flymake
45 :type 'boolean)
47 (defcustom flymake-xml-program
48 (if (executable-find "xmlstarlet") "xmlstarlet" "xml")
49 "Program to use for XML validation."
50 :type 'file
51 :group 'flymake
52 :version "24.4")
54 (defcustom flymake-master-file-dirs '("." "./src" "./UnitTest")
55 "Dirs where to look for master files."
56 :group 'flymake
57 :type '(repeat (string)))
59 (defcustom flymake-master-file-count-limit 32
60 "Max number of master files to check."
61 :group 'flymake
62 :type 'integer)
64 (defcustom flymake-allowed-file-name-masks
65 '(("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'" flymake-simple-make-init)
66 ("\\.xml\\'" flymake-xml-init)
67 ("\\.html?\\'" flymake-xml-init)
68 ("\\.cs\\'" flymake-simple-make-init)
69 ("\\.p[ml]\\'" flymake-perl-init)
70 ("\\.php[345]?\\'" flymake-php-init)
71 ("\\.h\\'" flymake-master-make-header-init flymake-master-cleanup)
72 ("\\.java\\'" flymake-simple-make-java-init flymake-simple-java-cleanup)
73 ("[0-9]+\\.tex\\'" flymake-master-tex-init flymake-master-cleanup)
74 ("\\.tex\\'" flymake-simple-tex-init)
75 ("\\.idl\\'" flymake-simple-make-init)
76 ;; ("\\.cpp\\'" 1)
77 ;; ("\\.java\\'" 3)
78 ;; ("\\.h\\'" 2 ("\\.cpp\\'" "\\.c\\'")
79 ;; ("[ \t]*#[ \t]*include[ \t]*\"\\([\w0-9/\\_\.]*[/\\]*\\)\\(%s\\)\"" 1 2))
80 ;; ("\\.idl\\'" 1)
81 ;; ("\\.odl\\'" 1)
82 ;; ("[0-9]+\\.tex\\'" 2 ("\\.tex\\'")
83 ;; ("[ \t]*\\input[ \t]*{\\(.*\\)\\(%s\\)}" 1 2 ))
84 ;; ("\\.tex\\'" 1)
86 "Files syntax checking is allowed for.
87 This is an alist with elements of the form:
88 REGEXP [INIT [CLEANUP [NAME]]]
89 REGEXP is a regular expression that matches a file name.
90 INIT is the init function to use, missing means disable `flymake-mode'.
91 CLEANUP is the cleanup function to use, default `flymake-simple-cleanup'.
92 NAME is the file name function to use, default `flymake-get-real-file-name'."
93 :group 'flymake
94 :type '(alist :key-type (regexp :tag "File regexp")
95 :value-type
96 (list :tag "Handler functions"
97 (choice :tag "Init function"
98 (const :tag "disable" nil)
99 function)
100 (choice :tag "Cleanup function"
101 (const :tag "flymake-simple-cleanup" nil)
102 function)
103 (choice :tag "Name function"
104 (const :tag "flymake-get-real-file-name" nil)
105 function))))
107 (defvar flymake-processes nil
108 "List of currently active flymake processes.")
110 (defvar-local flymake-output-residual nil)
112 (defun flymake-get-file-name-mode-and-masks (file-name)
113 "Return the corresponding entry from `flymake-allowed-file-name-masks'."
114 (unless (stringp file-name)
115 (error "Invalid file-name"))
116 (let ((fnm flymake-allowed-file-name-masks)
117 (mode-and-masks nil))
118 (while (and (not mode-and-masks) fnm)
119 (let ((item (pop fnm)))
120 (when (string-match (car item) file-name)
121 (setq mode-and-masks item)))) ; (cdr item) may be nil
122 (setq mode-and-masks (cdr mode-and-masks))
123 (flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks))
124 mode-and-masks))
126 (defun flymake-proc-can-syntax-check-buffer ()
127 "Determine whether we can syntax check current buffer.
128 Return nil if we cannot, non-nil if
129 we can."
130 (and buffer-file-name
131 (if (flymake-get-init-function buffer-file-name) t nil)))
133 (defun flymake-get-init-function (file-name)
134 "Return init function to be used for the file."
135 (let* ((init-f (nth 0 (flymake-get-file-name-mode-and-masks file-name))))
136 ;;(flymake-log 0 "calling %s" init-f)
137 ;;(funcall init-f (current-buffer))
138 init-f))
140 (defun flymake-get-cleanup-function (file-name)
141 "Return cleanup function to be used for the file."
142 (or (nth 1 (flymake-get-file-name-mode-and-masks file-name))
143 'flymake-simple-cleanup))
145 (defun flymake-get-real-file-name-function (file-name)
146 (or (nth 2 (flymake-get-file-name-mode-and-masks file-name))
147 'flymake-get-real-file-name))
149 (defvar flymake-find-buildfile-cache (make-hash-table :test #'equal))
151 (defun flymake-get-buildfile-from-cache (dir-name)
152 "Look up DIR-NAME in cache and return its associated value.
153 If DIR-NAME is not found, return nil."
154 (gethash dir-name flymake-find-buildfile-cache))
156 (defun flymake-add-buildfile-to-cache (dir-name buildfile)
157 "Associate DIR-NAME with BUILDFILE in the buildfile cache."
158 (puthash dir-name buildfile flymake-find-buildfile-cache))
160 (defun flymake-clear-buildfile-cache ()
161 "Clear the buildfile cache."
162 (clrhash flymake-find-buildfile-cache))
164 (defun flymake-find-buildfile (buildfile-name source-dir-name)
165 "Find buildfile starting from current directory.
166 Buildfile includes Makefile, build.xml etc.
167 Return its file name if found, or nil if not found."
168 (or (flymake-get-buildfile-from-cache source-dir-name)
169 (let* ((file (locate-dominating-file source-dir-name buildfile-name)))
170 (if file
171 (progn
172 (flymake-log 3 "found buildfile at %s" file)
173 (flymake-add-buildfile-to-cache source-dir-name file)
174 file)
175 (progn
176 (flymake-log 3 "buildfile for %s not found" source-dir-name)
177 nil)))))
179 (defun flymake-fix-file-name (name)
180 "Replace all occurrences of `\\' with `/'."
181 (when name
182 (setq name (expand-file-name name))
183 (setq name (abbreviate-file-name name))
184 (setq name (directory-file-name name))
185 name))
187 (defun flymake-same-files (file-name-one file-name-two)
188 "Check if FILE-NAME-ONE and FILE-NAME-TWO point to same file.
189 Return t if so, nil if not."
190 (equal (flymake-fix-file-name file-name-one)
191 (flymake-fix-file-name file-name-two)))
193 ;; This is bound dynamically to pass a parameter to a sort predicate below
194 (defvar flymake-included-file-name)
196 (defun flymake-find-possible-master-files (file-name master-file-dirs masks)
197 "Find (by name and location) all possible master files.
199 Name is specified by FILE-NAME and location is specified by
200 MASTER-FILE-DIRS. Master files include .cpp and .c for .h.
201 Files are searched for starting from the .h directory and max
202 max-level parent dirs. File contents are not checked."
203 (let* ((dirs master-file-dirs)
204 (files nil)
205 (done nil))
207 (while (and (not done) dirs)
208 (let* ((dir (expand-file-name (car dirs) (file-name-directory file-name)))
209 (masks masks))
210 (while (and (file-exists-p dir) (not done) masks)
211 (let* ((mask (car masks))
212 (dir-files (directory-files dir t mask)))
214 (flymake-log 3 "dir %s, %d file(s) for mask %s"
215 dir (length dir-files) mask)
216 (while (and (not done) dir-files)
217 (when (not (file-directory-p (car dir-files)))
218 (setq files (cons (car dir-files) files))
219 (when (>= (length files) flymake-master-file-count-limit)
220 (flymake-log 3 "master file count limit (%d) reached" flymake-master-file-count-limit)
221 (setq done t)))
222 (setq dir-files (cdr dir-files))))
223 (setq masks (cdr masks))))
224 (setq dirs (cdr dirs)))
225 (when files
226 (let ((flymake-included-file-name (file-name-nondirectory file-name)))
227 (setq files (sort files 'flymake-master-file-compare))))
228 (flymake-log 3 "found %d possible master file(s)" (length files))
229 files))
231 (defun flymake-master-file-compare (file-one file-two)
232 "Compare two files specified by FILE-ONE and FILE-TWO.
233 This function is used in sort to move most possible file names
234 to the beginning of the list (File.h -> File.cpp moved to top)."
235 (and (equal (file-name-sans-extension flymake-included-file-name)
236 (file-name-base file-one))
237 (not (equal file-one file-two))))
239 (defvar flymake-check-file-limit 8192
240 "Maximum number of chars to look at when checking possible master file.
241 Nil means search the entire file.")
243 (defun flymake-check-patch-master-file-buffer
244 (master-file-temp-buffer
245 master-file-name patched-master-file-name
246 source-file-name patched-source-file-name
247 include-dirs regexp)
248 "Check if MASTER-FILE-NAME is a master file for SOURCE-FILE-NAME.
249 If yes, patch a copy of MASTER-FILE-NAME to include PATCHED-SOURCE-FILE-NAME
250 instead of SOURCE-FILE-NAME.
252 For example, foo.cpp is a master file if it includes foo.h.
254 When a buffer for MASTER-FILE-NAME exists, use it as a source
255 instead of reading master file from disk."
256 (let* ((source-file-nondir (file-name-nondirectory source-file-name))
257 (source-file-extension (file-name-extension source-file-nondir))
258 (source-file-nonext (file-name-sans-extension source-file-nondir))
259 (found nil)
260 (inc-name nil)
261 (search-limit flymake-check-file-limit))
262 (setq regexp
263 (format regexp ; "[ \t]*#[ \t]*include[ \t]*\"\\(.*%s\\)\""
264 ;; Hack for tex files, where \include often excludes .tex.
265 ;; Maybe this is safe generally.
266 (if (and (> (length source-file-extension) 1)
267 (string-equal source-file-extension "tex"))
268 (format "%s\\(?:\\.%s\\)?"
269 (regexp-quote source-file-nonext)
270 (regexp-quote source-file-extension))
271 (regexp-quote source-file-nondir))))
272 (unwind-protect
273 (with-current-buffer master-file-temp-buffer
274 (if (or (not search-limit)
275 (> search-limit (point-max)))
276 (setq search-limit (point-max)))
277 (flymake-log 3 "checking %s against regexp %s"
278 master-file-name regexp)
279 (goto-char (point-min))
280 (while (and (< (point) search-limit)
281 (re-search-forward regexp search-limit t))
282 (let ((match-beg (match-beginning 1))
283 (match-end (match-end 1)))
285 (flymake-log 3 "found possible match for %s" source-file-nondir)
286 (setq inc-name (match-string 1))
287 (and (> (length source-file-extension) 1)
288 (string-equal source-file-extension "tex")
289 (not (string-match (format "\\.%s\\'" source-file-extension)
290 inc-name))
291 (setq inc-name (concat inc-name "." source-file-extension)))
292 (when (eq t (compare-strings
293 source-file-nondir nil nil
294 inc-name (- (length inc-name)
295 (length source-file-nondir)) nil))
296 (flymake-log 3 "inc-name=%s" inc-name)
297 (when (flymake-check-include source-file-name inc-name
298 include-dirs)
299 (setq found t)
300 ;; replace-match is not used here as it fails in
301 ;; XEmacs with 'last match not a buffer' error as
302 ;; check-includes calls replace-in-string
303 (flymake-replace-region
304 match-beg match-end
305 (file-name-nondirectory patched-source-file-name))))
306 (forward-line 1)))
307 (when found
308 (flymake-save-buffer-in-file patched-master-file-name)))
309 ;;+(flymake-log 3 "killing buffer %s"
310 ;; (buffer-name master-file-temp-buffer))
311 (kill-buffer master-file-temp-buffer))
312 ;;+(flymake-log 3 "check-patch master file %s: %s" master-file-name found)
313 (when found
314 (flymake-log 2 "found master file %s" master-file-name))
315 found))
317 ;;; XXX: remove
318 (defun flymake-replace-region (beg end rep)
319 "Replace text in BUFFER in region (BEG END) with REP."
320 (save-excursion
321 (goto-char end)
322 ;; Insert before deleting, so as to better preserve markers's positions.
323 (insert rep)
324 (delete-region beg end)))
326 (defun flymake-read-file-to-temp-buffer (file-name)
327 "Insert contents of FILE-NAME into newly created temp buffer."
328 (let* ((temp-buffer (get-buffer-create (generate-new-buffer-name (concat "flymake:" (file-name-nondirectory file-name))))))
329 (with-current-buffer temp-buffer
330 (insert-file-contents file-name))
331 temp-buffer))
333 (defun flymake-copy-buffer-to-temp-buffer (buffer)
334 "Copy contents of BUFFER into newly created temp buffer."
335 (with-current-buffer
336 (get-buffer-create (generate-new-buffer-name
337 (concat "flymake:" (buffer-name buffer))))
338 (insert-buffer-substring buffer)
339 (current-buffer)))
341 (defun flymake-check-include (source-file-name inc-name include-dirs)
342 "Check if SOURCE-FILE-NAME can be found in include path.
343 Return t if it can be found via include path using INC-NAME."
344 (if (file-name-absolute-p inc-name)
345 (flymake-same-files source-file-name inc-name)
346 (while (and include-dirs
347 (not (flymake-same-files
348 source-file-name
349 (concat (file-name-directory source-file-name)
350 "/" (car include-dirs)
351 "/" inc-name))))
352 (setq include-dirs (cdr include-dirs)))
353 include-dirs))
355 (defun flymake-find-buffer-for-file (file-name)
356 "Check if there exists a buffer visiting FILE-NAME.
357 Return t if so, nil if not."
358 (let ((buffer-name (get-file-buffer file-name)))
359 (if buffer-name
360 (get-buffer buffer-name))))
362 (defun flymake-create-master-file (source-file-name patched-source-file-name get-incl-dirs-f create-temp-f masks include-regexp)
363 "Save SOURCE-FILE-NAME with a different name.
364 Find master file, patch and save it."
365 (let* ((possible-master-files (flymake-find-possible-master-files source-file-name flymake-master-file-dirs masks))
366 (master-file-count (length possible-master-files))
367 (idx 0)
368 (temp-buffer nil)
369 (master-file-name nil)
370 (patched-master-file-name nil)
371 (found nil))
373 (while (and (not found) (< idx master-file-count))
374 (setq master-file-name (nth idx possible-master-files))
375 (setq patched-master-file-name (funcall create-temp-f master-file-name "flymake_master"))
376 (if (flymake-find-buffer-for-file master-file-name)
377 (setq temp-buffer (flymake-copy-buffer-to-temp-buffer (flymake-find-buffer-for-file master-file-name)))
378 (setq temp-buffer (flymake-read-file-to-temp-buffer master-file-name)))
379 (setq found
380 (flymake-check-patch-master-file-buffer
381 temp-buffer
382 master-file-name
383 patched-master-file-name
384 source-file-name
385 patched-source-file-name
386 (funcall get-incl-dirs-f (file-name-directory master-file-name))
387 include-regexp))
388 (setq idx (1+ idx)))
389 (if found
390 (list master-file-name patched-master-file-name)
391 (progn
392 (flymake-log 3 "none of %d master file(s) checked includes %s" master-file-count
393 (file-name-nondirectory source-file-name))
394 nil))))
396 (defun flymake-save-buffer-in-file (file-name)
397 "Save the entire buffer contents into file FILE-NAME.
398 Create parent directories as needed."
399 (make-directory (file-name-directory file-name) 1)
400 (write-region nil nil file-name nil 566)
401 (flymake-log 3 "saved buffer %s in file %s" (buffer-name) file-name))
403 (defun flymake-process-filter (process output)
404 "Parse OUTPUT and highlight error lines.
405 It's flymake process filter."
406 (let ((source-buffer (process-buffer process)))
408 (flymake-log 3 "received %d byte(s) of output from process %d"
409 (length output) (process-id process))
410 (when (buffer-live-p source-buffer)
411 (with-current-buffer source-buffer
412 (flymake-parse-output-and-residual output)))))
414 (defun flymake-process-sentinel (process _event)
415 "Sentinel for syntax check buffers."
416 (when (memq (process-status process) '(signal exit))
417 (let* ((exit-status (process-exit-status process))
418 (command (process-command process))
419 (source-buffer (process-buffer process))
420 (cleanup-f (flymake-get-cleanup-function (buffer-file-name source-buffer))))
422 (flymake-log 2 "process %d exited with code %d"
423 (process-id process) exit-status)
424 (condition-case err
425 (progn
426 (flymake-log 3 "cleaning up using %s" cleanup-f)
427 (when (buffer-live-p source-buffer)
428 (with-current-buffer source-buffer
429 (funcall cleanup-f)))
431 (delete-process process)
432 (setq flymake-processes (delq process flymake-processes))
434 (when (buffer-live-p source-buffer)
435 (with-current-buffer source-buffer
437 (flymake-parse-residual)
438 (flymake-post-syntax-check exit-status command)
439 (setq flymake-is-running nil))))
440 (error
441 (let ((err-str (format "Error in process sentinel for buffer %s: %s"
442 source-buffer (error-message-string err))))
443 (flymake-log 0 err-str)
444 (with-current-buffer source-buffer
445 (setq flymake-is-running nil))))))))
447 (defun flymake-post-syntax-check (exit-status command)
448 (save-restriction
449 (widen)
450 (setq flymake-err-info flymake-new-err-info)
451 (setq flymake-new-err-info nil)
452 (setq flymake-err-info
453 (flymake-fix-line-numbers
454 flymake-err-info 1 (count-lines (point-min) (point-max))))
455 (flymake-delete-own-overlays)
456 (flymake-highlight-err-lines flymake-err-info)
457 (let (err-count warn-count)
458 (setq err-count (flymake-get-err-count flymake-err-info "e"))
459 (setq warn-count (flymake-get-err-count flymake-err-info "w"))
460 (flymake-log 2 "%s: %d error(s), %d warning(s) in %.2f second(s)"
461 (buffer-name) err-count warn-count
462 (- (float-time) flymake-check-start-time))
463 (setq flymake-check-start-time nil)
465 (if (and (equal 0 err-count) (equal 0 warn-count))
466 (if (equal 0 exit-status)
467 (flymake-report-status "" "") ; PASSED
468 (if (not flymake-check-was-interrupted)
469 (flymake-report-fatal-status "CFGERR"
470 (format "Configuration error has occurred while running %s" command))
471 (flymake-report-status nil ""))) ; "STOPPED"
472 (flymake-report-status (format "%d/%d" err-count warn-count) "")))))
474 (defun flymake-parse-output-and-residual (output)
475 "Split OUTPUT into lines, merge in residual if necessary."
476 (let* ((buffer-residual flymake-output-residual)
477 (total-output (if buffer-residual (concat buffer-residual output) output))
478 (lines-and-residual (flymake-split-output total-output))
479 (lines (nth 0 lines-and-residual))
480 (new-residual (nth 1 lines-and-residual)))
481 (setq flymake-output-residual new-residual)
482 (setq flymake-new-err-info
483 (flymake-parse-err-lines
484 flymake-new-err-info lines))))
486 (defun flymake-parse-residual ()
487 "Parse residual if it's non empty."
488 (when flymake-output-residual
489 (setq flymake-new-err-info
490 (flymake-parse-err-lines
491 flymake-new-err-info
492 (list flymake-output-residual)))
493 (setq flymake-output-residual nil)))
495 (defun flymake-fix-line-numbers (err-info-list min-line max-line)
496 "Replace line numbers with fixed value.
497 If line-numbers is less than MIN-LINE, set line numbers to MIN-LINE.
498 If line numbers is greater than MAX-LINE, set line numbers to MAX-LINE.
499 The reason for this fix is because some compilers might report
500 line number outside the file being compiled."
501 (let* ((count (length err-info-list))
502 (err-info nil)
503 (line 0))
504 (while (> count 0)
505 (setq err-info (nth (1- count) err-info-list))
506 (setq line (flymake-er-get-line err-info))
507 (when (or (< line min-line) (> line max-line))
508 (setq line (if (< line min-line) min-line max-line))
509 (setq err-info-list (flymake-set-at err-info-list (1- count)
510 (flymake-er-make-er line
511 (flymake-er-get-line-err-info-list err-info)))))
512 (setq count (1- count))))
513 err-info-list)
515 (defun flymake-parse-err-lines (err-info-list lines)
516 "Parse err LINES, store info in ERR-INFO-LIST."
517 (let* ((count (length lines))
518 (idx 0)
519 (line-err-info nil)
520 (real-file-name nil)
521 (source-file-name buffer-file-name)
522 (get-real-file-name-f (flymake-get-real-file-name-function source-file-name)))
524 (while (< idx count)
525 (setq line-err-info (flymake-parse-line (nth idx lines)))
526 (when line-err-info
527 (setq real-file-name (funcall get-real-file-name-f
528 (flymake-ler-file line-err-info)))
529 (setq line-err-info (flymake-ler-set-full-file line-err-info real-file-name))
531 (when (flymake-same-files real-file-name source-file-name)
532 (setq line-err-info (flymake-ler-set-file line-err-info nil))
533 (setq err-info-list (flymake-add-err-info err-info-list line-err-info))))
534 (flymake-log 3 "parsed `%s', %s line-err-info" (nth idx lines) (if line-err-info "got" "no"))
535 (setq idx (1+ idx)))
536 err-info-list))
538 (defun flymake-split-output (output)
539 "Split OUTPUT into lines.
540 Return last one as residual if it does not end with newline char.
541 Returns ((LINES) RESIDUAL)."
542 (when (and output (> (length output) 0))
543 (let* ((lines (split-string output "[\n\r]+" t))
544 (complete (equal "\n" (char-to-string (aref output (1- (length output))))))
545 (residual nil))
546 (when (not complete)
547 (setq residual (car (last lines)))
548 (setq lines (butlast lines)))
549 (list lines residual))))
551 (defun flymake-reformat-err-line-patterns-from-compile-el (original-list)
552 "Grab error line patterns from ORIGINAL-LIST in compile.el format.
553 Convert it to flymake internal format."
554 (let* ((converted-list '()))
555 (dolist (item original-list)
556 (setq item (cdr item))
557 (let ((regexp (nth 0 item))
558 (file (nth 1 item))
559 (line (nth 2 item))
560 (col (nth 3 item)))
561 (if (consp file) (setq file (car file)))
562 (if (consp line) (setq line (car line)))
563 (if (consp col) (setq col (car col)))
565 (when (not (functionp line))
566 (setq converted-list (cons (list regexp file line col) converted-list)))))
567 converted-list))
569 (require 'compile)
571 (defvar flymake-err-line-patterns ; regexp file-idx line-idx col-idx (optional) text-idx(optional), match-end to end of string is error text
572 (append
574 ;; MS Visual C++ 6.0
575 ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\)) : \\(\\(error\\|warning\\|fatal error\\) \\(C[0-9]+\\):[ \t\n]*\\(.+\\)\\)"
576 1 3 nil 4)
577 ;; jikes
578 ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\):[0-9]+:[0-9]+:[0-9]+: \\(\\(Error\\|Warning\\|Caution\\|Semantic Error\\):[ \t\n]*\\(.+\\)\\)"
579 1 3 nil 4)
580 ;; MS midl
581 ("midl[ ]*:[ ]*\\(command line error .*\\)"
582 nil nil nil 1)
583 ;; MS C#
584 ("\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)(\\([0-9]+\\),[0-9]+): \\(\\(error\\|warning\\|fatal error\\) \\(CS[0-9]+\\):[ \t\n]*\\(.+\\)\\)"
585 1 3 nil 4)
586 ;; perl
587 ("\\(.*\\) at \\([^ \n]+\\) line \\([0-9]+\\)[,.\n]" 2 3 nil 1)
588 ;; PHP
589 ("\\(?:Parse\\|Fatal\\) error: \\(.*\\) in \\(.*\\) on line \\([0-9]+\\)" 2 3 nil 1)
590 ;; LaTeX warnings (fileless) ("\\(LaTeX \\(Warning\\|Error\\): .*\\) on input line \\([0-9]+\\)" 20 3 nil 1)
591 ;; ant/javac. Note this also matches gcc warnings!
592 (" *\\(\\[javac\\] *\\)?\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\):\\([0-9]+\\)\\(?::[0-9]+\\)?:[ \t\n]*\\(.+\\)"
593 2 4 nil 5))
594 ;; compilation-error-regexp-alist)
595 (flymake-reformat-err-line-patterns-from-compile-el compilation-error-regexp-alist-alist))
596 "Patterns for matching error/warning lines. Each pattern has the form
597 \(REGEXP FILE-IDX LINE-IDX COL-IDX ERR-TEXT-IDX).
598 Use `flymake-reformat-err-line-patterns-from-compile-el' to add patterns
599 from compile.el")
601 (define-obsolete-variable-alias 'flymake-warning-re 'flymake-warning-predicate "24.4")
602 (defvar flymake-warning-predicate "^[wW]arning"
603 "Predicate matching against error text to detect a warning.
604 Takes a single argument, the error's text and should return non-nil
605 if it's a warning.
606 Instead of a function, it can also be a regular expression.")
608 (defun flymake-parse-line (line)
609 "Parse LINE to see if it is an error or warning.
610 Return its components if so, nil otherwise."
611 (let ((raw-file-name nil)
612 (line-no 0)
613 (err-type "e")
614 (err-text nil)
615 (patterns flymake-err-line-patterns)
616 (matched nil))
617 (while (and patterns (not matched))
618 (when (string-match (car (car patterns)) line)
619 (let* ((file-idx (nth 1 (car patterns)))
620 (line-idx (nth 2 (car patterns))))
622 (setq raw-file-name (if file-idx (match-string file-idx line) nil))
623 (setq line-no (if line-idx (string-to-number
624 (match-string line-idx line)) 0))
625 (setq err-text (if (> (length (car patterns)) 4)
626 (match-string (nth 4 (car patterns)) line)
627 (flymake-patch-err-text
628 (substring line (match-end 0)))))
629 (if (null err-text)
630 (setq err-text "<no error text>")
631 (when (cond ((stringp flymake-warning-predicate)
632 (string-match flymake-warning-predicate err-text))
633 ((functionp flymake-warning-predicate)
634 (funcall flymake-warning-predicate err-text)))
635 (setq err-type "w")))
636 (flymake-log
637 3 "parse line: file-idx=%s line-idx=%s file=%s line=%s text=%s"
638 file-idx line-idx raw-file-name line-no err-text)
639 (setq matched t)))
640 (setq patterns (cdr patterns)))
641 (if matched
642 (flymake-ler-make-ler raw-file-name line-no err-type err-text)
643 ())))
645 (defun flymake-get-project-include-dirs-imp (basedir)
646 "Include dirs for the project current file belongs to."
647 (if (flymake-get-project-include-dirs-from-cache basedir)
648 (progn
649 (flymake-get-project-include-dirs-from-cache basedir))
650 ;;else
651 (let* ((command-line (concat "make -C "
652 (shell-quote-argument basedir)
653 " DUMPVARS=INCLUDE_DIRS dumpvars"))
654 (output (shell-command-to-string command-line))
655 (lines (split-string output "\n" t))
656 (count (length lines))
657 (idx 0)
658 (inc-dirs nil))
659 (while (and (< idx count) (not (string-match "^INCLUDE_DIRS=.*" (nth idx lines))))
660 (setq idx (1+ idx)))
661 (when (< idx count)
662 (let* ((inc-lines (split-string (nth idx lines) " *-I" t))
663 (inc-count (length inc-lines)))
664 (while (> inc-count 0)
665 (when (not (string-match "^INCLUDE_DIRS=.*" (nth (1- inc-count) inc-lines)))
666 (push (replace-regexp-in-string "\"" "" (nth (1- inc-count) inc-lines)) inc-dirs))
667 (setq inc-count (1- inc-count)))))
668 (flymake-add-project-include-dirs-to-cache basedir inc-dirs)
669 inc-dirs)))
671 (defvar flymake-get-project-include-dirs-function #'flymake-get-project-include-dirs-imp
672 "Function used to get project include dirs, one parameter: basedir name.")
674 (defun flymake-get-project-include-dirs (basedir)
675 (funcall flymake-get-project-include-dirs-function basedir))
677 (defun flymake-get-system-include-dirs ()
678 "System include dirs - from the `INCLUDE' env setting."
679 (let* ((includes (getenv "INCLUDE")))
680 (if includes (split-string includes path-separator t) nil)))
682 (defvar flymake-project-include-dirs-cache (make-hash-table :test #'equal))
684 (defun flymake-get-project-include-dirs-from-cache (base-dir)
685 (gethash base-dir flymake-project-include-dirs-cache))
687 (defun flymake-add-project-include-dirs-to-cache (base-dir include-dirs)
688 (puthash base-dir include-dirs flymake-project-include-dirs-cache))
690 (defun flymake-clear-project-include-dirs-cache ()
691 (clrhash flymake-project-include-dirs-cache))
693 (defun flymake-get-include-dirs (base-dir)
694 "Get dirs to use when resolving local file names."
695 (let* ((include-dirs (append '(".") (flymake-get-project-include-dirs base-dir) (flymake-get-system-include-dirs))))
696 include-dirs))
698 ;; (defun flymake-restore-formatting ()
699 ;; "Remove any formatting made by flymake."
700 ;; )
702 ;; (defun flymake-get-program-dir (buffer)
703 ;; "Get dir to start program in."
704 ;; (unless (bufferp buffer)
705 ;; (error "Invalid buffer"))
706 ;; (with-current-buffer buffer
707 ;; default-directory))
709 (defun flymake-safe-delete-file (file-name)
710 (when (and file-name (file-exists-p file-name))
711 (delete-file file-name)
712 (flymake-log 1 "deleted file %s" file-name)))
714 (defun flymake-safe-delete-directory (dir-name)
715 (condition-case nil
716 (progn
717 (delete-directory dir-name)
718 (flymake-log 1 "deleted dir %s" dir-name))
719 (error
720 (flymake-log 1 "Failed to delete dir %s, error ignored" dir-name))))
722 (defun flymake-proc-start-syntax-check ()
723 "Start syntax checking for current buffer."
724 (interactive)
725 (flymake-log 3 "flymake is running: %s" flymake-is-running)
726 (when (not flymake-is-running)
727 (when (or (not flymake-compilation-prevents-syntax-check)
728 (not (flymake-compilation-is-running))) ;+ (flymake-rep-ort-status buffer "COMP")
729 (flymake-clear-buildfile-cache)
730 (flymake-clear-project-include-dirs-cache)
732 (setq flymake-check-was-interrupted nil)
734 (let* ((source-file-name buffer-file-name)
735 (init-f (flymake-get-init-function source-file-name))
736 (cleanup-f (flymake-get-cleanup-function source-file-name))
737 (cmd-and-args (funcall init-f))
738 (cmd (nth 0 cmd-and-args))
739 (args (nth 1 cmd-and-args))
740 (dir (nth 2 cmd-and-args)))
741 (if (not cmd-and-args)
742 (progn
743 (flymake-log 0 "init function %s for %s failed, cleaning up" init-f source-file-name)
744 (funcall cleanup-f))
745 (progn
746 (setq flymake-last-change-time nil)
747 (flymake-start-syntax-check-process cmd args dir)))))))
749 (defun flymake-start-syntax-check-process (cmd args dir)
750 "Start syntax check process."
751 (condition-case err
752 (let* ((process
753 (let ((default-directory (or dir default-directory)))
754 (when dir
755 (flymake-log 3 "starting process on dir %s" dir))
756 (apply 'start-file-process
757 "flymake-proc" (current-buffer) cmd args))))
758 (set-process-sentinel process 'flymake-process-sentinel)
759 (set-process-filter process 'flymake-process-filter)
760 (set-process-query-on-exit-flag process nil)
761 (push process flymake-processes)
763 (setq flymake-is-running t)
764 (setq flymake-last-change-time nil)
765 (setq flymake-check-start-time (float-time))
767 (flymake-report-status nil "*")
768 (flymake-log 2 "started process %d, command=%s, dir=%s"
769 (process-id process) (process-command process)
770 default-directory)
771 process)
772 (error
773 (let* ((err-str
774 (format-message
775 "Failed to launch syntax check process `%s' with args %s: %s"
776 cmd args (error-message-string err)))
777 (source-file-name buffer-file-name)
778 (cleanup-f (flymake-get-cleanup-function source-file-name)))
779 (flymake-log 0 err-str)
780 (funcall cleanup-f)
781 (flymake-report-fatal-status "PROCERR" err-str)))))
783 (defun flymake-kill-process (proc)
784 "Kill process PROC."
785 (kill-process proc)
786 (let* ((buf (process-buffer proc)))
787 (when (buffer-live-p buf)
788 (with-current-buffer buf
789 (setq flymake-check-was-interrupted t))))
790 (flymake-log 1 "killed process %d" (process-id proc)))
792 (defun flymake-stop-all-syntax-checks ()
793 "Kill all syntax check processes."
794 (interactive)
795 (while flymake-processes
796 (flymake-kill-process (pop flymake-processes))))
798 (defun flymake-compilation-is-running ()
799 (and (boundp 'compilation-in-progress)
800 compilation-in-progress))
802 (defun flymake-compile ()
803 "Kill all flymake syntax checks, start compilation."
804 (interactive)
805 (flymake-stop-all-syntax-checks)
806 (call-interactively 'compile))
808 ;;;; general init-cleanup and helper routines
809 (defun flymake-create-temp-inplace (file-name prefix)
810 (unless (stringp file-name)
811 (error "Invalid file-name"))
812 (or prefix
813 (setq prefix "flymake"))
814 (let* ((ext (file-name-extension file-name))
815 (temp-name (file-truename
816 (concat (file-name-sans-extension file-name)
817 "_" prefix
818 (and ext (concat "." ext))))))
819 (flymake-log 3 "create-temp-inplace: file=%s temp=%s" file-name temp-name)
820 temp-name))
822 (defun flymake-create-temp-with-folder-structure (file-name _prefix)
823 (unless (stringp file-name)
824 (error "Invalid file-name"))
826 (let* ((dir (file-name-directory file-name))
827 ;; Not sure what this slash-pos is all about, but I guess it's just
828 ;; trying to remove the leading / of absolute file names.
829 (slash-pos (string-match "/" dir))
830 (temp-dir (expand-file-name (substring dir (1+ slash-pos))
831 temporary-file-directory)))
833 (file-truename (expand-file-name (file-name-nondirectory file-name)
834 temp-dir))))
836 (defun flymake-delete-temp-directory (dir-name)
837 "Attempt to delete temp dir created by `flymake-create-temp-with-folder-structure', do not fail on error."
838 (let* ((temp-dir temporary-file-directory)
839 (suffix (substring dir-name (1+ (length temp-dir)))))
841 (while (> (length suffix) 0)
842 (setq suffix (directory-file-name suffix))
843 ;;+(flymake-log 0 "suffix=%s" suffix)
844 (flymake-safe-delete-directory
845 (file-truename (expand-file-name suffix temp-dir)))
846 (setq suffix (file-name-directory suffix)))))
848 (defvar-local flymake-temp-source-file-name nil)
849 (defvar-local flymake-master-file-name nil)
850 (defvar-local flymake-temp-master-file-name nil)
851 (defvar-local flymake-base-dir nil)
853 (defun flymake-init-create-temp-buffer-copy (create-temp-f)
854 "Make a temporary copy of the current buffer, save its name in buffer data and return the name."
855 (let* ((source-file-name buffer-file-name)
856 (temp-source-file-name (funcall create-temp-f source-file-name "flymake")))
858 (flymake-save-buffer-in-file temp-source-file-name)
859 (setq flymake-temp-source-file-name temp-source-file-name)
860 temp-source-file-name))
862 (defun flymake-simple-cleanup ()
863 "Do cleanup after `flymake-init-create-temp-buffer-copy'.
864 Delete temp file."
865 (flymake-safe-delete-file flymake-temp-source-file-name)
866 (setq flymake-last-change-time nil))
868 (defun flymake-get-real-file-name (file-name-from-err-msg)
869 "Translate file name from error message to \"real\" file name.
870 Return full-name. Names are real, not patched."
871 (let* ((real-name nil)
872 (source-file-name buffer-file-name)
873 (master-file-name flymake-master-file-name)
874 (temp-source-file-name flymake-temp-source-file-name)
875 (temp-master-file-name flymake-temp-master-file-name)
876 (base-dirs
877 (list flymake-base-dir
878 (file-name-directory source-file-name)
879 (if master-file-name (file-name-directory master-file-name))))
880 (files (list (list source-file-name source-file-name)
881 (list temp-source-file-name source-file-name)
882 (list master-file-name master-file-name)
883 (list temp-master-file-name master-file-name))))
885 (when (equal 0 (length file-name-from-err-msg))
886 (setq file-name-from-err-msg source-file-name))
888 (setq real-name (flymake-get-full-patched-file-name file-name-from-err-msg base-dirs files))
889 ;; if real-name is nil, than file name from err msg is none of the files we've patched
890 (if (not real-name)
891 (setq real-name (flymake-get-full-nonpatched-file-name file-name-from-err-msg base-dirs)))
892 (if (not real-name)
893 (setq real-name file-name-from-err-msg))
894 (setq real-name (flymake-fix-file-name real-name))
895 (flymake-log 3 "get-real-file-name: file-name=%s real-name=%s" file-name-from-err-msg real-name)
896 real-name))
898 (defun flymake-get-full-patched-file-name (file-name-from-err-msg base-dirs files)
899 (let* ((base-dirs-count (length base-dirs))
900 (file-count (length files))
901 (real-name nil))
903 (while (and (not real-name) (> base-dirs-count 0))
904 (setq file-count (length files))
905 (while (and (not real-name) (> file-count 0))
906 (let* ((this-dir (nth (1- base-dirs-count) base-dirs))
907 (this-file (nth 0 (nth (1- file-count) files)))
908 (this-real-name (nth 1 (nth (1- file-count) files))))
909 ;;+(flymake-log 0 "this-dir=%s this-file=%s this-real=%s msg-file=%s" this-dir this-file this-real-name file-name-from-err-msg)
910 (when (and this-dir this-file (flymake-same-files
911 (expand-file-name file-name-from-err-msg this-dir)
912 this-file))
913 (setq real-name this-real-name)))
914 (setq file-count (1- file-count)))
915 (setq base-dirs-count (1- base-dirs-count)))
916 real-name))
918 (defun flymake-get-full-nonpatched-file-name (file-name-from-err-msg base-dirs)
919 (let* ((real-name nil))
920 (if (file-name-absolute-p file-name-from-err-msg)
921 (setq real-name file-name-from-err-msg)
922 (let* ((base-dirs-count (length base-dirs)))
923 (while (and (not real-name) (> base-dirs-count 0))
924 (let* ((full-name (expand-file-name file-name-from-err-msg
925 (nth (1- base-dirs-count) base-dirs))))
926 (if (file-exists-p full-name)
927 (setq real-name full-name))
928 (setq base-dirs-count (1- base-dirs-count))))))
929 real-name))
931 (defun flymake-init-find-buildfile-dir (source-file-name buildfile-name)
932 "Find buildfile, store its dir in buffer data and return its dir, if found."
933 (let* ((buildfile-dir
934 (flymake-find-buildfile buildfile-name
935 (file-name-directory source-file-name))))
936 (if buildfile-dir
937 (setq flymake-base-dir buildfile-dir)
938 (flymake-log 1 "no buildfile (%s) for %s" buildfile-name source-file-name)
939 (flymake-report-fatal-status
940 "NOMK" (format "No buildfile (%s) found for %s"
941 buildfile-name source-file-name)))))
943 (defun flymake-init-create-temp-source-and-master-buffer-copy (get-incl-dirs-f create-temp-f master-file-masks include-regexp)
944 "Find master file (or buffer), create its copy along with a copy of the source file."
945 (let* ((source-file-name buffer-file-name)
946 (temp-source-file-name (flymake-init-create-temp-buffer-copy create-temp-f))
947 (master-and-temp-master (flymake-create-master-file
948 source-file-name temp-source-file-name
949 get-incl-dirs-f create-temp-f
950 master-file-masks include-regexp)))
952 (if (not master-and-temp-master)
953 (progn
954 (flymake-log 1 "cannot find master file for %s" source-file-name)
955 (flymake-report-status "!" "") ; NOMASTER
956 nil)
957 (setq flymake-master-file-name (nth 0 master-and-temp-master))
958 (setq flymake-temp-master-file-name (nth 1 master-and-temp-master)))))
960 (defun flymake-master-cleanup ()
961 (flymake-simple-cleanup)
962 (flymake-safe-delete-file flymake-temp-master-file-name))
964 ;;;; make-specific init-cleanup routines
965 (defun flymake-get-syntax-check-program-args (source-file-name base-dir use-relative-base-dir use-relative-source get-cmd-line-f)
966 "Create a command line for syntax check using GET-CMD-LINE-F."
967 (funcall get-cmd-line-f
968 (if use-relative-source
969 (file-relative-name source-file-name base-dir)
970 source-file-name)
971 (if use-relative-base-dir
972 (file-relative-name base-dir
973 (file-name-directory source-file-name))
974 base-dir)))
976 (defun flymake-get-make-cmdline (source base-dir)
977 (list "make"
978 (list "-s"
979 "-C"
980 base-dir
981 (concat "CHK_SOURCES=" source)
982 "SYNTAX_CHECK_MODE=1"
983 "check-syntax")))
985 (defun flymake-get-ant-cmdline (source base-dir)
986 (list "ant"
987 (list "-buildfile"
988 (concat base-dir "/" "build.xml")
989 (concat "-DCHK_SOURCES=" source)
990 "check-syntax")))
992 (defun flymake-simple-make-init-impl (create-temp-f use-relative-base-dir use-relative-source build-file-name get-cmdline-f)
993 "Create syntax check command line for a directly checked source file.
994 Use CREATE-TEMP-F for creating temp copy."
995 (let* ((args nil)
996 (source-file-name buffer-file-name)
997 (buildfile-dir (flymake-init-find-buildfile-dir source-file-name build-file-name)))
998 (if buildfile-dir
999 (let* ((temp-source-file-name (flymake-init-create-temp-buffer-copy create-temp-f)))
1000 (setq args (flymake-get-syntax-check-program-args temp-source-file-name buildfile-dir
1001 use-relative-base-dir use-relative-source
1002 get-cmdline-f))))
1003 args))
1005 (defun flymake-simple-make-init ()
1006 (flymake-simple-make-init-impl 'flymake-create-temp-inplace t t "Makefile" 'flymake-get-make-cmdline))
1008 (defun flymake-master-make-init (get-incl-dirs-f master-file-masks include-regexp)
1009 "Create make command line for a source file checked via master file compilation."
1010 (let* ((make-args nil)
1011 (temp-master-file-name (flymake-init-create-temp-source-and-master-buffer-copy
1012 get-incl-dirs-f 'flymake-create-temp-inplace
1013 master-file-masks include-regexp)))
1014 (when temp-master-file-name
1015 (let* ((buildfile-dir (flymake-init-find-buildfile-dir temp-master-file-name "Makefile")))
1016 (if buildfile-dir
1017 (setq make-args (flymake-get-syntax-check-program-args
1018 temp-master-file-name buildfile-dir nil nil 'flymake-get-make-cmdline)))))
1019 make-args))
1021 (defun flymake-find-make-buildfile (source-dir)
1022 (flymake-find-buildfile "Makefile" source-dir))
1024 ;;;; .h/make specific
1025 (defun flymake-master-make-header-init ()
1026 (flymake-master-make-init
1027 'flymake-get-include-dirs
1028 '("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'")
1029 "[ \t]*#[ \t]*include[ \t]*\"\\([[:word:]0-9/\\_.]*%s\\)\""))
1031 ;;;; .java/make specific
1032 (defun flymake-simple-make-java-init ()
1033 (flymake-simple-make-init-impl 'flymake-create-temp-with-folder-structure nil nil "Makefile" 'flymake-get-make-cmdline))
1035 (defun flymake-simple-ant-java-init ()
1036 (flymake-simple-make-init-impl 'flymake-create-temp-with-folder-structure nil nil "build.xml" 'flymake-get-ant-cmdline))
1038 (defun flymake-simple-java-cleanup ()
1039 "Cleanup after `flymake-simple-make-java-init' -- delete temp file and dirs."
1040 (flymake-safe-delete-file flymake-temp-source-file-name)
1041 (when flymake-temp-source-file-name
1042 (flymake-delete-temp-directory
1043 (file-name-directory flymake-temp-source-file-name))))
1045 ;;;; perl-specific init-cleanup routines
1046 (defun flymake-perl-init ()
1047 (let* ((temp-file (flymake-init-create-temp-buffer-copy
1048 'flymake-create-temp-inplace))
1049 (local-file (file-relative-name
1050 temp-file
1051 (file-name-directory buffer-file-name))))
1052 (list "perl" (list "-wc " local-file))))
1054 ;;;; php-specific init-cleanup routines
1055 (defun flymake-php-init ()
1056 (let* ((temp-file (flymake-init-create-temp-buffer-copy
1057 'flymake-create-temp-inplace))
1058 (local-file (file-relative-name
1059 temp-file
1060 (file-name-directory buffer-file-name))))
1061 (list "php" (list "-f" local-file "-l"))))
1063 ;;;; tex-specific init-cleanup routines
1064 (defun flymake-get-tex-args (file-name)
1065 ;;(list "latex" (list "-c-style-errors" file-name))
1066 (list "texify" (list "--pdf" "--tex-option=-c-style-errors" file-name)))
1068 (defun flymake-simple-tex-init ()
1069 (flymake-get-tex-args (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)))
1071 ;; Perhaps there should be a buffer-local variable flymake-master-file
1072 ;; that people can set to override this stuff. Could inherit from
1073 ;; the similar AUCTeX variable.
1074 (defun flymake-master-tex-init ()
1075 (let* ((temp-master-file-name (flymake-init-create-temp-source-and-master-buffer-copy
1076 'flymake-get-include-dirs-dot 'flymake-create-temp-inplace
1077 '("\\.tex\\'")
1078 "[ \t]*\\in\\(?:put\\|clude\\)[ \t]*{\\(.*%s\\)}")))
1079 (when temp-master-file-name
1080 (flymake-get-tex-args temp-master-file-name))))
1082 (defun flymake-get-include-dirs-dot (_base-dir)
1083 '("."))
1085 ;;;; xml-specific init-cleanup routines
1086 (defun flymake-xml-init ()
1087 (list flymake-xml-program
1088 (list "val" (flymake-init-create-temp-buffer-copy
1089 'flymake-create-temp-inplace))))
1092 ;;;; Hook onto flymake-ui
1094 (add-to-list 'flymake-backends
1095 `(flymake-proc-can-syntax-check-buffer
1097 flymake-proc-start-syntax-check))
1099 (provide 'flymake-proc)
1100 ;;; flymake-proc.el ends here