Subject: bug#7611: 23.2; info-xref.el v.3

From: Glenn Morris <[email-masked]>
Date: Tue, 14 Dec 2010 13:39:13 -0500

Kevin Ryde wrote:

>> Can you resend this as a patch
>
> Alas, the bazaar repo is hugely too big for me to use (both initial
> 10-year download and subsequent 10-meg updates).

Well, there is

http://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/info-xref.el

but at that speed, it will take you ~ 10 hours to load the page. :(

> 	* info-xref.el: Use compilation-mode for the errors.
> 	Move explanations from commentary to relevant docstrings.
> 	(info-xref-docstrings): New command.

This doesn't adequately describe the diff (below) between your version
and the version in the Emacs trunk. Also the new version uses a
cl-function (remove-if) at run-time.

*** info-xref.el	2010-12-06 13:38:37.000000000 -0800
--- ../info-xref.el	2010-12-13 14:26:59.000000000 -0800
***************
*** 5,10 ****
--- 5,11 ----
  
  ;; Author: Kevin Ryde <[email-masked]>
  ;; Keywords: docs
+ ;; Version: 3
  
  ;; This file is part of GNU Emacs.
  
***************
*** 23,78 ****
  
  ;;; Commentary:
  
! ;; This file implements some simple checking of external cross references in
! ;; info files, by attempting to visit the nodes specified.
  ;;
! ;; "makeinfo" checks references internal to a document, but not external
! ;; references, which makes it rather easy for mistakes to creep in or node
! ;; name changes to go unnoticed.  `Info-validate' doesn't check external
! ;; references either.
! ;;
! ;; `M-x info-xref-check' checks one file.  When invoked from an Info-mode or
! ;; texinfo-mode buffer, the current info file is the default at the prompt.
! ;;
! ;; `M-x info-xref-check-all' looks at everything in the normal info path.
! ;; This might be a lot of files but it's a good way to check the consistency
! ;; of the whole system.
! ;;
! ;; Results are shown in a buffer.  The format is a bit rough, but hopefully
! ;; there won't be too many problems normally, and correcting them is a
! ;; manual process anyway, a case of finding the right spot in the original
! ;; .texi and finding what node it ought to point to.
! ;;
! ;; When a target info file doesn't exist there's clearly no way to validate
! ;; node references within it.  A message is given for missing target files
! ;; (once per source document), it could be simply that the target hasn't
! ;; been installed, or it could be a mistake in the reference.
! ;;
! ;; Indirect info files are understood, just pass the top-level foo.info to
! ;; `info-xref-check' and it traverses all sub-files.  Compressed info files
! ;; are accepted too, as usual for `Info-mode'.
! ;;
! ;; `info-xref-check-all' is rather permissive in what it considers an info
! ;; file.  It has to be since info files don't necessarily have a ".info"
! ;; suffix (eg. this is usual for the emacs manuals).  One consequence of
! ;; this is that if for instance there's a source code directory in
! ;; `Info-directory-list' then a lot of extraneous files might be read, which
! ;; will be time consuming but should be harmless.
! ;;
! ;; `M-x info-xref-check-all-custom' is a related command, it goes through
! ;; all info document references in customizable variables, checking them
! ;; like info file cross references.
  
  ;;; Code:
  
  (require 'info)
  
! (defconst info-xref-results-buffer "*info-xref results*"
    "Name of the buffer for info-xref results.")
  
  ;;;###autoload
  (defun info-xref-check (filename)
!   "Check external references in FILENAME, an info document."
    (interactive
     (list
      (let* ((default-filename
--- 24,282 ----
  
  ;;; Commentary:
  
! ;; This is some simple checking of external cross references in info files,
! ;; docstrings and custom-links by attempting to visit the nodes specified.
  ;;
! ;; `M-x info-xref-check' checks a single info file.  See the docstring for
! ;; details.
! ;;
! ;; `M-x info-xref-check-all' checks all info files in Info-directory-list.
! ;; This is a good way to check the consistency of the whole system.
! ;;
! ;; `M-x info-xref-check-all-custom' loads up all defcustom variables and
! ;; checks any info references in them.
! ;;
! ;; `M-x info-xref-docstrings' checks docstring "Info node ..." hyperlinks in
! ;; source files (and other files).
! 
! ;;; History:
! 
! ;; Version 3 - new M-x info-xref-docstrings, use compilation-mode
  
  ;;; Code:
  
  (require 'info)
+ (eval-when-compile
+   (require 'cl)) ;; for `incf'
+ 
+ ;;-----------------------------------------------------------------------------
+ ;; vaguely generic
+ 
+ (defun info-xref-lock-file-p (filename)
+   "Return non-nil if FILENAME is an Emacs lock file.
+ A lock file is \".#foo.txt\" etc per `lock-buffer'."
+   (string-match "\\(\\`\\|\\/\\)\\.#" filename))
+ 
+ (defun info-xref-subfile-p (filename)
+   "Return t if FILENAME is an info subfile.
+ If removing the last \"-<NUM>\" from the filename gives a file
+ which exists, then consider FILENAME a subfile.  This is an
+ imperfect test, probably ought to open up the purported top file
+ and see what subfiles it says."
+   (and (string-match "\\`\\(\\([^-]*-\\)*[^-]*\\)-[0-9]+\\(.*\\)\\'" filename)
+        (file-exists-p (concat (match-string 1 filename)
+                               (match-string 3 filename)))))
+ 
+ (defmacro info-xref-with-file (filename &rest body)
+   ;; checkdoc-params: (filename body)
+   "Evaluate BODY in a buffer containing the contents of FILENAME.
+ If FILENAME is already in a buffer then that's used, otherwise a
+ temporary buffer.
+ 
+ The current implementation uses `insert-file-contents' rather
+ than `find-file-noselect' so as not to be held up by queries
+ about local variables or possible weirdness in a major mode.
+ `lm-with-file' does a similar thing, but it sets
+ `emacs-lisp-mode' which is not wanted here."
+ 
+   (declare (debug t) (indent 1))
+   `(let* ((info-xref-with-file--filename ,filename)
+           (info-xref-with-file--body     (lambda () ,@body))
+           (info-xref-with-file--existing
+            (find-buffer-visiting info-xref-with-file--filename)))
+      (if info-xref-with-file--existing
+          (with-current-buffer info-xref-with-file--existing
+            (save-excursion
+              (funcall info-xref-with-file--body)))
+        (with-temp-buffer
+          (insert-file-contents ,filename)
+          (funcall info-xref-with-file--body)))))
+ 
  
! ;;-----------------------------------------------------------------------------
! ;; output buffer
! 
! (defconst info-xref-output-buffer "*info-xref results*"
    "Name of the buffer for info-xref results.")
  
+ (defvar info-xref-good 0
+   "Count of good cross references, during info-xref processing.")
+ (defvar info-xref-bad 0
+   "Count of bad cross references, during info-xref processing.")
+ (defvar info-xref-unavail 0
+   "Count of unavailable cross references, during info-xref processing.")
+ 
+ (defvar info-xref-output-heading ""
+   "A heading string, during info-xref processing.
+ This is shown if there's an error, but not if successful.")
+ 
+ (defvar info-xref-filename nil
+   "The current buffer's filename, during info-xref processing.
+ When looking at file contents in a temp buffer there's no
+ `buffer-file-name', hence this variable.")
+ 
+ (defvar info-xref-xfile-alist nil
+   "Info files found or not found, during info-xref processing.
+ Key is \"(foo)\" etc and value nil or t according to whether info
+ manual \"(foo)\" exists or not.  This is used to suppress
+ duplicate messages about foo not being available.  (Duplicates
+ within one top-level file that is.)")
+ 
+ (defvar info-xref-in-progress nil)
+ (defmacro info-xref-with-output (&rest body)
+   "Run BODY with an info-xref output buffer.
+ This is meant to nest, so you can wrap it around a set of
+ different info-xref checks and have them write to the one output
+ buffer created by the outermost `info-xref-with-output', with an
+ overall good/bad count summary inserted at the very end."
+ 
+   (declare (debug t))
+   `(save-excursion
+      (unless info-xref-in-progress
+        (display-buffer (get-buffer-create info-xref-output-buffer))
+        (set-buffer info-xref-output-buffer)
+        (setq buffer-read-only nil)
+        (fundamental-mode)
+        (erase-buffer)
+        (insert ";; info-xref output -*- mode: compilation -*-\n\n")
+        (compilation-mode)
+        (setq info-xref-good    0
+              info-xref-bad     0
+              info-xref-unavail 0
+              info-xref-xfile-alist nil))
+ 
+      (let ((info-xref-in-progress t)
+            (info-xref-output-heading ""))
+        ,@body)
+ 
+      (unless info-xref-in-progress
+        (info-xref-output "done, %d good, %d bad, %d unavailable"
+                          info-xref-good info-xref-bad info-xref-unavail))))
+   
+ (defun info-xref-output (fmt &rest args)
+   "Emit a `format'-ed message FMT+ARGS to the `info-xref-output-buffer'."
+   (with-current-buffer info-xref-output-buffer
+     (save-excursion
+       (goto-char (point-max))
+       (let ((inhibit-read-only t))
+         (insert info-xref-output-heading
+                 (apply 'format fmt args)
+                 "\n")))
+     (setq info-xref-output-heading "")
+     ;; all this info-xref can be pretty slow, display now so the user sees
+     ;; some progress
+     (sit-for 0)))
+ (put 'info-xref-output 'byte-compile-format-like t)
+ 
+ (defun info-xref-output-error (fmt &rest args)
+   "Emit a `format'-ed error FMT+ARGS to the `info-xref-output-buffer'.
+ The error is attributed to `info-xref-filename' and the current
+ buffer's line and column of point."
+   (apply 'info-xref-output
+          (concat "%s:%s:%s: " fmt)
+          info-xref-filename
+          (1+ (count-lines (point-min) (line-beginning-position)))
+          (1+ (current-column))
+          args))
+ (put 'info-xref-output-error 'byte-compile-format-like t)
+ 
+ 
+ ;;-----------------------------------------------------------------------------
+ ;; node checking
+ 
+ ;; When asking Info-goto-node to fork, *info* needs to be the current
+ ;; buffer, otherwise it seems to clone the current buffer but then do the
+ ;; goto-node in plain *info*.
+ ;;
+ ;; We only fork if *info* already exists, if it doesn't then can create and
+ ;; destroy just that instead of a new name.
+ ;;
+ ;; If Info-goto-node can't find the file, then no new buffer is created.  If
+ ;; it finds the file but not the node, then a buffer is created.  Handle
+ ;; this difference by checking before killing.
+ ;;
+ (defun info-xref-goto-node-p (node)
+   "Return t if it's possible to go to the given NODE."
+   (let ((oldbuf (current-buffer)))
+     (save-excursion
+       (save-window-excursion
+         (prog1
+             (condition-case err
+                 (progn
+                   (Info-goto-node node
+                                   (when (get-buffer "*info*")
+                                     (set-buffer "*info*")
+                                     "xref - temporary"))
+                   t)
+               (error nil))
+           (unless (equal (current-buffer) oldbuf)
+             (kill-buffer)))))))
+ 
+ (defun info-xref-check-node (node)
+ 
+   ;; Collapse spaces as per info.el and `help-make-xrefs'.
+   ;; Note defcustom :info-link nodes don't get this whitespace collapsing,
+   ;; they should be the exact node name ready to visit.
+   ;; `info-xref-check-all-custom' uses `info-xref-goto-node-p' and so
+   ;; doesn't come through here.
+   ;;
+   ;; Could use "[\t\n ]+" but try to avoid uselessly replacing " " with " ".
+   (setq node (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " "
+                                        node t t))
+ 
+   (if (not (string-match "\\`([^)]*)" node))
+       (info-xref-output-error "no `(file)' part at start of node: %s\n" node)
+     (let ((file (match-string 0 node)))
+ 
+       (if (string-equal "()" file)
+           (info-xref-output-error "empty filename part: %s" node)
+ 
+         ;; see if the file exists, if haven't looked before
+         (unless (assoc file info-xref-xfile-alist)
+           (let ((found (info-xref-goto-node-p file)))
+             (push (cons file found) info-xref-xfile-alist)
+             (unless found
+               (info-xref-output-error "not available to check: %s\n    (this reported once per file)" file))))
+ 
+         ;; if the file exists, try the node
+         (cond ((not (cdr (assoc file info-xref-xfile-alist)))
+                (incf info-xref-unavail))
+               ((info-xref-goto-node-p node)
+                (incf info-xref-good))
+               (t
+                (incf info-xref-bad)
+                (info-xref-output-error "no such node: %s" node)))))))
+ 
+ 
+ ;;-----------------------------------------------------------------------------
+ 
  ;;;###autoload
  (defun info-xref-check (filename)
!   "Check external references in FILENAME, an info document.
! Interactively from an `Info-mode' or `texinfo-mode' buffer the
! current info file is the default.
! 
! Results are shown in a `compilation-mode' buffer.  The format is
! a bit rough, but there shouldn't be many problems normally.  The
! file:line:column: is the info document, but of course normally
! any correction should be made in the original .texi file.
! Finding the right place in the .texi is a manual process.
! 
! When a target info file doesn't exist there's obviously no way to
! validate node references within it.  A message is given for
! missing target files once per source document.  It could be
! simply that you don't have the target installed, or it could be a
! mistake in the reference.
! 
! Indirect info files are understood, just pass the top-level
! foo.info to `info-xref-check' and it traverses all sub-files.
! Compressed info files are accepted too as usual for `Info-mode'.
! 
! \"makeinfo\" checks references internal to an info document, but
! not external references, which makes it rather easy for mistakes
! to creep in or node name changes to go unnoticed.
! `Info-validate' doesn't check external references either."
! 
    (interactive
     (list
      (let* ((default-filename
***************
*** 90,187 ****
                         (format "Info file (%s): " default-filename)
                       "Info file: ")))
        (read-file-name prompt nil default-filename t))))
    (info-xref-check-list (list filename)))
  
  ;;;###autoload
  (defun info-xref-check-all ()
!   "Check external references in all info documents in the usual path.
! The usual path is `Info-directory-list' and `Info-additional-directory-list'."
    (interactive)
    (info-xref-check-list (info-xref-all-info-files)))
  
! ;; An alternative to trying to get only top-level files here would be to
! ;; simply return all files, and have info-xref-check-list not follow
! ;; Indirect:.  The current way seems a bit nicer though, because it gets the
! ;; proper top-level filename into the error messages, and suppresses
! ;; duplicate "not available" messages for all subfiles of a single document.
  
  (defun info-xref-all-info-files ()
    "Return a list of all available info files.
! Only top-level files are returned, subfiles are excluded.
  
! Since info files don't have to have a .info suffix, all files in the
! relevant directories are considered, which might mean a lot of extraneous
! things are returned if for instance a source code directory is in the path."
  
    (info-initialize) ;; establish Info-directory-list
    (apply 'nconc
           (mapcar
            (lambda (dir)
              (let ((result nil))
!               (dolist (name (directory-files dir t))
!                 (unless (or (file-directory-p name) (info-xref-subfile-p name))
  		  (push name result)))
                (nreverse result)))
            (append Info-directory-list Info-additional-directory-list))))
  
- (defun info-xref-subfile-p (filename)
-   "Return t if FILENAME is an info subfile.
- If removing the last \"-<NUM>\" from the filename gives a file that exists,
- then consider FILENAME a subfile.  This is an imperfect test, we probably
- should open up the purported top file and see what subfiles it says."
-   (and (string-match "\\`\\(\\([^-]*-\\)*[^-]*\\)-[0-9]+\\(.*\\)\\'" filename)
-        (file-exists-p (concat (match-string 1 filename)
-                               (match-string 3 filename)))))
- 
- 
- ;; Some dynamic variables are used to share information with sub-functions
- ;; below.
- ;;
- ;; info-xref-filename-header - a heading message for the current top-level
- ;;     filename, or "" when it's been printed.
- ;;
- (defvar info-xref-xfile-alist)
- ;;
- ;; info-xref-good - count of good cross references.
- ;;
- (defvar info-xref-good)
- ;;
- ;; info-xref-bad - count of bad cross references.
- ;;
- (defvar info-xref-bad)
- ;;
- ;; info-xref-xfile-alist - indexed by "(foo)" with value nil or t according
- ;;     to whether "(foo)" exists or not.  This is used to suppress duplicate
- ;;     messages about foo not being available.  (Duplicates within one
- ;;     top-level file that is.)
- ;;
- (defvar info-xref-filename-heading)
- 
  (defun info-xref-check-list (filename-list)
    "Check external references in info documents in FILENAME-LIST."
!   (pop-to-buffer info-xref-results-buffer t)
!   (erase-buffer)
!   (let ((info-xref-good 0)
!         (info-xref-bad  0))
      (dolist (info-xref-filename filename-list)
!       (let ((info-xref-filename-heading
!              (format "In file %s:\n" info-xref-filename))
!             (info-xref-xfile-alist nil))
          (with-temp-message (format "Looking at %s" info-xref-filename)
            (with-temp-buffer
              (info-insert-file-contents info-xref-filename)
              (goto-char (point-min))
!             (if (re-search-forward "\^_\nIndirect:\n" nil t)
                  (let ((dir (file-name-directory info-xref-filename)))
                    (while (looking-at "\\(.*\\): [0-9]+\n")
!                     (let ((subfile (match-string 1)))
                        (with-temp-buffer
!                         (info-insert-file-contents
!                          (expand-file-name subfile dir))
                          (info-xref-check-buffer)))
                      (forward-line)))
!               (info-xref-check-buffer))))))
!     (insert (format "done, %d good, %d bad\n" info-xref-good info-xref-bad))))
  
  (defun info-xref-check-buffer ()
    "Check external references in the info file in the current buffer.
--- 294,373 ----
                         (format "Info file (%s): " default-filename)
                       "Info file: ")))
        (read-file-name prompt nil default-filename t))))
+ 
    (info-xref-check-list (list filename)))
  
  ;;;###autoload
  (defun info-xref-check-all ()
!   "Check external references in all info documents in the info path.
! `Info-directory-list' and `Info-additional-directory-list' are
! the info paths.  See `info-xref-check' for how each file is
! checked.
! 
! The search for \"all\" info files is rather permissive, since
! info files don't necessarily have a \".info\" extension and in
! particular the Emacs manuals normally don't.  If you have a
! source code directory in `Info-directory-list' then a lot of
! extraneous files might be read.  This will be time consuming but
! should be harmless."
! 
    (interactive)
    (info-xref-check-list (info-xref-all-info-files)))
  
! ;; An alternative for geting only top-level files here would be to simply
! ;; return all files and have info-xref-check-list not follow "Indirect:".
! ;; The current way seems better because it (potentially) gets the proper
! ;; top-level filename into the error messages, and suppresses duplicate "not
! ;; available" messages for all subfiles of a single document.
  
  (defun info-xref-all-info-files ()
    "Return a list of all available info files.
! Only top level files are returned, subfiles are excluded.
  
! Since info files don't have to have a .info suffix, all files in
! the relevant directories are considered, which might mean a lot
! of extraneous things if for instance a source code directory is
! in the path."
  
    (info-initialize) ;; establish Info-directory-list
    (apply 'nconc
           (mapcar
            (lambda (dir)
              (let ((result nil))
!               (dolist (name (directory-files
!                              dir
!                              t           ;; absolute filenames
!                              "\\`[^.]")) ;; not dotfiles, nor .# lockfiles
!                 (when (and (file-exists-p name) ;; ignore broken symlinks
!                            (not (string-match "\\.te?xi\\'" name)) ;; not .texi
!                            (not (backup-file-name-p name))
!                            (not (file-directory-p name))
!                            (not (info-xref-subfile-p name)))
  		  (push name result)))
                (nreverse result)))
            (append Info-directory-list Info-additional-directory-list))))
  
  (defun info-xref-check-list (filename-list)
    "Check external references in info documents in FILENAME-LIST."
!   (info-xref-with-output
      (dolist (info-xref-filename filename-list)
!       (setq info-xref-xfile-alist nil)
!       (let ((info-xref-output-heading
!              (format "Info file %s\n" info-xref-filename)))
          (with-temp-message (format "Looking at %s" info-xref-filename)
            (with-temp-buffer
              (info-insert-file-contents info-xref-filename)
              (goto-char (point-min))
!             (if (search-forward "\^_\nIndirect:\n" nil t)
                  (let ((dir (file-name-directory info-xref-filename)))
                    (while (looking-at "\\(.*\\): [0-9]+\n")
!                     (let ((info-xref-filename
!                            (expand-file-name (match-string 1) dir)))
                        (with-temp-buffer
!                         (info-insert-file-contents info-xref-filename)
                          (info-xref-check-buffer)))
                      (forward-line)))
!               (info-xref-check-buffer))))))))
  
  (defun info-xref-check-buffer ()
    "Check external references in the info file in the current buffer.
***************
*** 190,301 ****
    (while (re-search-forward
            "\\*[Nn]ote[ \n\t]+[^:]*:[ \n\t]+\\(\\(([^)]*)\\)[^.,]+\\)[.,]"
            nil t)
-     (let* ((file (match-string 2))
-            (node ;; Canonicalize spaces: we could use "[\t\n ]+" but
- 	    ;; we try to avoid uselessly replacing " " with " ".
- 	    (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " "
- 				      (match-string 1) t t)))
-       (if (string-equal "()" file)
-           (info-xref-output "Empty filename part: %s\n" node)
-         ;; see if the file exists, if we haven't tried it before
-         (unless (assoc file info-xref-xfile-alist)
-           (let ((found (info-xref-goto-node-p file)))
-             (push (cons file found) info-xref-xfile-alist)
-             (unless found
-               (info-xref-output "Not available to check: %s\n" file))))
-         ;; if the file exists, try the node
-         (when (cdr (assoc file info-xref-xfile-alist))
-           (if (info-xref-goto-node-p node)
-               (setq info-xref-good (1+ info-xref-good))
-             (setq info-xref-bad (1+ info-xref-bad))
-             (info-xref-output "No such node: %s\n" node)))))))
- 
- (defun info-xref-output (str &rest args)
-   "Emit a `format'-ed message STR+ARGS to the info-xref output buffer."
-   (with-current-buffer info-xref-results-buffer
-     (insert info-xref-filename-heading
-             (apply 'format str args))
-     (setq info-xref-filename-heading "")
-     ;; all this info-xref can be pretty slow, display now so the user can
-     ;; see some progress
-     (sit-for 0)))
- 
- ;; When asking Info-goto-node to fork, *info* needs to be the current
- ;; buffer, otherwise it seems to clone the current buffer but then do the
- ;; goto-node in plain *info*.
- ;;
- ;; We only fork if *info* already exists, if it doesn't then we can create
- ;; and destroy just that instead of a new name.
- ;;
- ;; If Info-goto-node can't find the file, then no new buffer is created.  If
- ;; it finds the file but not the node, then a buffer is created.  Handle
- ;; this difference by checking before killing.
- ;;
- (defun info-xref-goto-node-p (node)
-   "Return t if it's possible to go to the given NODE."
-   (let ((oldbuf (current-buffer)))
      (save-excursion
!       (save-window-excursion
!         (prog1
!             (condition-case err
!                 (progn
!                   (Info-goto-node node
!                                   (when (get-buffer "*info*")
!                                     (set-buffer "*info*")
!                                     "xref - temporary"))
!                   t)
!               (error nil))
!           (unless (equal (current-buffer) oldbuf)
!             (kill-buffer (current-buffer))))))))
  
  ;;;###autoload
  (defun info-xref-check-all-custom ()
    "Check info references in all customize groups and variables.
! `custom-manual' and `info-link' entries in the `custom-links' list are checked.
  
! `custom-load' autoloads for all symbols are loaded in order to get all the
! link information.  This will be a lot of lisp packages loaded, and can take
! quite a while."
  
    (interactive)
!   (pop-to-buffer info-xref-results-buffer t)
!   (erase-buffer)
!   (let ((info-xref-filename-heading ""))
  
!     ;; `custom-load-symbol' is not used, since it quietly ignores errors,
!     ;; but we want to show them (since they may mean incomplete checking).
      ;;
      ;; Just one pass through mapatoms is made.  There shouldn't be any new
      ;; custom-loads setup by packages loaded.
      ;;
!     (info-xref-output "Loading custom-load autoloads ...\n")
      (require 'cus-start)
      (require 'cus-load)
!     (let ((viper-mode nil)) ;; tell viper.el not to ask about viperizing
        (mapatoms
         (lambda (symbol)
           (dolist (load (get symbol 'custom-loads))
             (cond ((symbolp load)
                    (condition-case cause (require load)
                      (error
!                      (info-xref-output "Symbol `%s': cannot require '%s: %s\n"
                                         symbol load cause))))
                   ;; skip if previously loaded
                   ((assoc load load-history))
                   ((assoc (locate-library load) load-history))
                   (t
!                   (condition-case cause (load load)
                      (error
!                      (info-xref-output "Symbol `%s': cannot load \"%s\": %s\n"
!                                        symbol load cause)))))))))
  
      ;; Don't bother to check whether the info file exists as opposed to just
!     ;; a missing node.  If you have the lisp then you should have the
!     ;; documentation, so missing node name will be the usual fault.
      ;;
!     (info-xref-output "\nChecking custom-links references ...\n")
!     (let ((good 0)
!           (bad  0))
        (mapatoms
         (lambda (symbol)
           (dolist (link (get symbol 'custom-links))
--- 376,445 ----
    (while (re-search-forward
            "\\*[Nn]ote[ \n\t]+[^:]*:[ \n\t]+\\(\\(([^)]*)\\)[^.,]+\\)[.,]"
            nil t)
      (save-excursion
!       (goto-char (match-beginning 1)) ;; start of nodename as error position
!       (info-xref-check-node (match-string 1)))))
! 
! (defvar viper-mode) ;; quieten the byte compiler
! (defvar gnus-registry-install)
  
  ;;;###autoload
  (defun info-xref-check-all-custom ()
    "Check info references in all customize groups and variables.
! Info references can be in `custom-manual' or `info-link' entries
! of the `custom-links' for a variable.
  
! Any `custom-load' autoloads in variables are loaded in order to
! get full link information.  This will be a lot of Lisp packages
! and can take a long time."
  
    (interactive)
!   (info-xref-with-output
  
!    ;; `custom-load-symbol' is not used, since it quietly ignores errors, but
!    ;; we want to show them since they mean incomplete checking.
     ;;
     ;; Just one pass through mapatoms is made.  There shouldn't be any new
     ;; custom-loads setup by packages loaded.
     ;;
!    (info-xref-output "Loading custom-load autoloads ...")
     (require 'cus-start)
     (require 'cus-load)
! 
!    ;; These are `setq' rather than `let' since a let would unbind the
!    ;; variables after viper.el/gnus-registry.el have loaded, defeating the
!    ;; defvars in those files.  Of course it'd be better if those files
!    ;; didn't make interactive queries on loading at all, to allow for
!    ;; programmatic loading like here.
!    (unless (boundp 'viper-mode)
!      (setq viper-mode nil))  ;; avoid viper.el ask about viperizing
!    (unless (boundp 'gnus-registry-install)
!      (setq gnus-registry-install nil))  ;; avoid gnus-registery.el querying
! 
     (mapatoms
      (lambda (symbol)
        (dolist (load (get symbol 'custom-loads))
          (cond ((symbolp load)
                 (condition-case cause (require load)
                   (error
!                   (info-xref-output "Symbol `%s': cannot require '%s: %s"
                                      symbol load cause))))
                ;; skip if previously loaded
                ((assoc load load-history))
                ((assoc (locate-library load) load-history))
                (t
!                (condition-case err
!                    (load load)
                   (error
!                   (info-xref-output "Symbol `%s': cannot load \"%s\": %s"
!                                     symbol load
!                                     (error-message-string err)))))))))
  
     ;; Don't bother to check whether the info file exists as opposed to just
!    ;; a missing node.  If you have the code then you should have the
!    ;; documentation, so a wrong node name will be the usual fault.
     ;;
!    (info-xref-output "\nChecking custom-links references ...")
     (mapatoms
      (lambda (symbol)
        (dolist (link (get symbol 'custom-links))
***************
*** 304,318 ****
  	     (if (eq :tag (cadr link))
  		 (setq link (cddr link)))
  	     (if (info-xref-goto-node-p (cadr link))
!                  (setq good (1+ good))
!                (setq bad (1+ bad))
                 ;; symbol-file gives nil for preloaded variables, would need
                 ;; to copy what describe-variable does to show the right place
!                (info-xref-output "Symbol `%s' (in %s): cannot goto node: %s\n"
!                                  symbol (symbol-file symbol) (cadr link)))))))
!       (info-xref-output "%d good, %d bad\n" good bad))))
  
  (provide 'info-xref)
  
- ;; arch-tag: 69d4d528-69ed-4cc2-8eb4-c666a0c1d5ac
  ;;; info-xref.el ends here
--- 448,528 ----
            (if (eq :tag (cadr link))
                (setq link (cddr link)))
            (if (info-xref-goto-node-p (cadr link))
!               (incf info-xref-good)
!             (incf info-xref-bad)
              ;; symbol-file gives nil for preloaded variables, would need
              ;; to copy what describe-variable does to show the right place
!             (info-xref-output "Symbol `%s' (file %s): cannot goto node: %s"
!                               symbol
!                               (symbol-file symbol 'defvar)
!                               (cadr link)))))))))
! 
! ;;;###autoload
! (defun info-xref-docstrings (filename-list)
!   ;; checkdoc-params: (filename-list)
!   "Check docstring info node references in source files.
! The given files are searched for docstring hyperlinks like
! 
!     Info node `(elisp)Documentation Tips'
! 
! and those links checked by attempting to visit the target nodes
! as per `info-xref-check' does.
! 
! Interactively filenames are read as a wildcard pattern like
! \"foo*.el\", with the current file as a default.  Usually this
! will be lisp sources, but anything with such hyperlinks can be
! checked, including the Emacs .c sources (or the etc/DOC file of
! all builtins).
! 
! Because info node hyperlinks are found by a simple regexp search
! in the files, the Lisp code checked doesn't have to be loaded,
! and links can be in the file commentary or elsewhere too.  Even
! .elc files can usually be checked successfully if you don't have
! the sources handy."
! 
!   (interactive
!    (let* ((default       (and buffer-file-name
!                               (file-relative-name buffer-file-name)))
!           (prompt        (if default
!                              (format "Filename with wildcards (%s): "
!                                      default)
!                            "Filename with wildcards: "))
!           (pattern       (read-file-name prompt nil default))
!           (filename-list (file-expand-wildcards pattern
!                                                 t))) ;; absolute filenames
!      (eval-and-compile
!        (require 'cl)) ;; for `remove-if'
!      (setq filename-list (remove-if 'info-xref-lock-file-p filename-list))
!      (unless filename-list
!        (error "No files: %S" pattern))
!      (list filename-list)))
! 
!   (eval-and-compile
!     (require 'help-mode)) ;; for `help-xref-info-regexp'
! 
!   (info-xref-with-output
!    (dolist (info-xref-filename filename-list)
!      (setq info-xref-xfile-alist nil)  ;; "not found"s once per file
! 
!      (info-xref-with-file info-xref-filename
!        (goto-char (point-min))
!        (while (re-search-forward help-xref-info-regexp nil t)
!          (let ((node (match-string 2)))
!            (save-excursion
!              (goto-char (match-beginning 2)) ;; start of node as error position
! 
!              ;; skip nodes with "%" as probably `format' strings such as in
!              ;; info-look.el
!              (unless (string-match "%" node)
! 
!                ;; "(emacs)" is the default manual for docstring hyperlinks,
!                ;; per `help-make-xrefs'
!                (unless (string-match "\\`(" node)
!                  (setq node (concat "(emacs)" node)))
! 
!                (info-xref-check-node node)))))))))
! 
  
  (provide 'info-xref)
  
  ;;; info-xref.el ends here