Function: checkdoc-this-string-valid-engine

checkdoc-this-string-valid-engine is a byte-compiled function defined in checkdoc.el.gz.

Signature

(checkdoc-this-string-valid-engine FP &optional TAKE-NOTES)

Documentation

Return an error list or string if the current doc string is invalid.

Depends on checkdoc-this-string-valid to reset the syntax table so that regexp short cuts work. FP is the function defun information.

With a non-nil TAKE-NOTES, store all errors found in a warnings buffer, otherwise stop after the first error.

Source Code

;; Defined in /usr/src/emacs/lisp/emacs-lisp/checkdoc.el.gz
(defun checkdoc-this-string-valid-engine (fp &optional take-notes)
  "Return an error list or string if the current doc string is invalid.
Depends on `checkdoc-this-string-valid' to reset the syntax table so that
regexp short cuts work.  FP is the function defun information.

With a non-nil TAKE-NOTES, store all errors found in a warnings
buffer, otherwise stop after the first error."
  (let ((case-fold-search nil)
	;; Use a marker so if an early check modifies the text,
	;; we won't accidentally lose our place.  This could cause
	;; end-of doc string whitespace to also delete the " char.
	(s (point))
	(e (if (= (following-char) ?\")
	       (save-excursion (forward-sexp 1) (point-marker))
	     (point))))
    (or
     ;; * *Do not* indent subsequent lines of a documentation string so that
     ;;   the text is lined up in the source code with the text of the first
     ;;   line.  This looks nice in the source code, but looks bizarre when
     ;;   users view the documentation.  Remember that the indentation
     ;;   before the starting double-quote is not part of the string!
     (save-excursion
       (forward-line 1)
       (beginning-of-line)
       (if (and (< (point) e)
		(looking-at "\\([ \t]+\\)[^ \t\n]"))
	   (if (checkdoc-autofix-ask-replace (match-beginning 1)
					     (match-end 1)
                                             "Remove this whitespace?"
					     "")
	       nil
	     (checkdoc-create-error
	      "Second line should not have indentation"
	      (match-beginning 1)
	      (match-end 1)))))
     ;; * Check for '(' in column 0.
     (when checkdoc-column-zero-backslash-before-paren
       (save-excursion
         (when (re-search-forward "^(" e t)
           (if (checkdoc-autofix-ask-replace (match-beginning 0)
                                     (match-end 0)
                                     (format-message "Escape this `('?")
                                     "\\(")
               nil
             (checkdoc-create-error
              "Open parenthesis in column 0 should be escaped"
              (match-beginning 0) (match-end 0))))))
     ;; * Do not start or end a documentation string with whitespace.
     (let (start end)
       (if (or (if (looking-at "\"\\([ \t\n]+\\)")
		   (setq start (match-beginning 1)
			 end (match-end 1)))
	       (save-excursion
		 (forward-sexp 1)
		 (forward-char -1)
		 (if (/= (skip-chars-backward " \t\n") 0)
		     (setq start (point)
			   end (1- e)))))
	   (if (checkdoc-autofix-ask-replace
                start end "Remove this whitespace?" "")
	       nil
	     (checkdoc-create-error
	      "Documentation strings should not start or end with whitespace"
	      start end))))
     ;; * The first line of the documentation string should consist of one
     ;;   or two complete sentences that stand on their own as a summary.
     ;;   `M-x apropos' displays just the first line, and if it doesn't
     ;;   stand on its own, the result looks bad.  In particular, start the
     ;;   first line with a capital letter and end with a period.
     (save-excursion
       (end-of-line)
       (skip-chars-backward " \t\n")
       (if (> (point) e) (goto-char e)) ;of the form (defun n () "doc" nil)
       (forward-char -1)
       (cond
	((and (eq (following-char) ?\")
	      ;; A backslashed double quote at the end of a sentence
	      (not (eq (preceding-char) ?\\)))
	 ;; We might have to add a period in this case
	 (forward-char -1)
	 (if (looking-at "[.!?]")
	     nil
	   (forward-char 1)
	   (if (checkdoc-autofix-ask-replace
                (point) (1+ (point)) "Add period to sentence?"
		".\"" t)
	       nil
	     (checkdoc-create-error
	      "First sentence should end with punctuation"
	      (point) (1+ (point))))))
	((looking-at "[\\!?;:.)]")
	 ;; These are ok
	 nil)
        ((and checkdoc-permit-comma-termination-flag (= (following-char) ?,))
	 nil)
	(t
	 ;; If it is not a complete sentence, let's see if we can
	 ;; predict a clever way to make it one.
	 (let ((msg "First line is not a complete sentence")
	       (e (point)))
	   (beginning-of-line)
	   (if (re-search-forward "\\. +" e t)
	       ;; Here we have found a complete sentence, but no break.
	       (if (checkdoc-autofix-ask-replace
		    (1+ (match-beginning 0)) (match-end 0)
                    "First line not a complete sentence.  Add RET here?"
		    "\n" t)
		   (let (l1 l2)
		     (end-of-line 2)
		     (setq l1 (current-column)
			   l2 (save-excursion
				(end-of-line 2)
				(current-column)))
		     (if (> (+ l1 l2 1) 80)
			 (setq msg "Incomplete auto-fix; doc string \
may require more formatting")
		       ;; We can merge these lines!  Replace this CR
		       ;; with a space.
		       (delete-char 1) (insert " ")
		       (setq msg nil))))
	     ;; Let's see if there is enough room to draw the next
	     ;; line's sentence up here.  I often get hit w/
	     ;; auto-fill moving my words around.
	     (let ((numc (progn (end-of-line) (- 80 (current-column))))
		   (p    (point)))
	       (forward-line 1)
	       (beginning-of-line)
	       (if (and (re-search-forward "[.!?:\"]\\([ \t\n]+\\|\"\\)"
					   (line-end-position) t)
			(< (current-column) numc))
		   (when (checkdoc-autofix-ask-replace
                          p (1+ p)
                          "First line not a complete sentence.  Join these lines?"
                          " " t)
		     (setq msg nil)))))
	   (if msg
	       (checkdoc-create-error msg s (save-excursion
					      (goto-char s)
					      (line-end-position))))))))
     ;; Continuation of above.  Make sure our sentence is capitalized.
     (save-excursion
       (skip-chars-forward "\"*")
       (if (looking-at "[a-z]")
	   (if (checkdoc-autofix-ask-replace
		(match-beginning 0) (match-end 0)
                "Capitalize your sentence?" (upcase (match-string 0))
		t)
	       nil
	     (checkdoc-create-error
	      "First line should be capitalized"
	      (match-beginning 0) (match-end 0)))
	 nil))
     ;;   * Don't write key sequences directly in documentation strings.
     ;;     Instead, use the `\\[...]' construct to stand for them.
     (save-excursion
       (let ((f nil) (m nil) (start (point))
             ;; Ignore the "A-" modifier: it is uncommon in practice,
             ;; and leads to false positives in regexp ranges.
             (re "[^`‘A-Za-z0-9_]\\([CMs]-[a-zA-Z]\\|\\(\\([CMs]-\\)?\
mouse-[0-3]\\)\\)\\>"))
	 ;; Find the first key sequence not in a sample
	 (while (and (not f) (setq m (re-search-forward re e t)))
	   (setq f (not (checkdoc-in-sample-code-p start e))))
	 (if m
	     (checkdoc-create-error
	      (concat
	       "Keycode " (match-string 1)
	       " embedded in doc string.  Use \\\\<mapvar> & \\\\[command] "
	       "instead")
	      (match-beginning 1) (match-end 1) t))))
     ;; Optionally warn about too many command substitutions.
     (when checkdoc-max-keyref-before-warn
       (save-excursion
         (if (and (re-search-forward "\\\\\\\\\\[\\w+" e t
                                     (1+ checkdoc-max-keyref-before-warn))
                  (not (re-search-forward "\\\\\\\\{\\w+}" e t)))
             (checkdoc-create-error
              "Too many occurrences of \\[command].  Use \\{mapvar} instead"
              s (marker-position e)))))
     ;; Ambiguous quoted symbol.  When a symbol is both bound and fbound,
     ;; and is referred to in documentation, it should be prefixed with
     ;; something to disambiguate it.  This check must be before the
     ;; 80 column check because it might break that.
     (save-excursion
       (let ((case-fold-search t)
	     (ret nil) mb me)
	 (while (and (re-search-forward
                      "[`‘]\\(\\sw\\(\\sw\\|\\s_\\)+\\)['’]" e t)
		     (not ret))
	   (let* ((ms1 (match-string 1))
		  (sym (intern-soft ms1)))
	     (setq mb (match-beginning 1)
		   me (match-end 1))
	     (if (and sym (boundp sym) (fboundp sym)
                      checkdoc--disambiguate-symbol-flag
                      ;; Mode names do not need disambiguating.  (Bug#4110)
                      (not (string-match (rx "-mode" string-end)
                                         (symbol-name sym)))
                      (save-excursion
			(goto-char mb)
			(forward-word-strictly -1)
			(not (looking-at
			      "variable\\|option\\|function\\|command\\|symbol"))))
		 (if (checkdoc-autofix-ask-replace
                      mb me "Prefix this ambiguous symbol?" ms1 t)
		     ;; We didn't actually replace anything.  Here we find
		     ;; out what special word form they wish to use as
		     ;; a prefix.
		     (let ((disambiguate
			    (completing-read
			     (format-prompt "Disambiguating Keyword"
                                            "variable")
			     '(("function") ("command") ("variable")
			       ("option") ("symbol"))
			     nil t nil nil "variable")))
		       (goto-char (1- mb))
		       (insert disambiguate " ")
		       (forward-word-strictly 1))
		   (setq ret
			 (format "Disambiguate %s by preceding w/ \
function,command,variable,option or symbol." ms1))))))
	 (if ret
	     (checkdoc-create-error ret mb me)
	   nil)))
     ;; * Format the documentation string so that it fits in an
     ;;   Emacs window on an 80-column screen.  It is a good idea
     ;;   for most lines to be no wider than 60 characters.  The
     ;;   first line can be wider if necessary to fit the
     ;;   information that ought to be there.
     (save-excursion
       (let* ((start (point))
              (eol nil)
              ;; Respect this file local variable.
              (max-column (max 80 byte-compile-docstring-max-column))
              ;; Allow the first line to be three characters longer, to
              ;; fit the leading ` "' while still having a docstring
              ;; shorter than e.g. 80 characters.
              (first t)
              (get-max-column (lambda () (+ max-column (if first 3 0)))))
	 (while (and (< (point) e)
		     (or (progn (end-of-line) (setq eol (point))
                                (< (current-column) (funcall get-max-column)))
			 (progn (beginning-of-line)
				(re-search-forward "\\\\\\\\[[<{]"
						   eol t))
                         (checkdoc-in-sample-code-p start e)))
           (setq first nil)
	   (forward-line 1))
	 (end-of-line)
         (if (and (< (point) e) (> (current-column) (funcall get-max-column)))
	     (checkdoc-create-error
              (format "Some lines are over %d columns wide" max-column)
	      s (save-excursion (goto-char s) (line-end-position))))))
     ;; Here we deviate to tests based on a variable or function.
     ;; We must do this before checking for symbols in quotes because there
     ;; is a chance that just such a symbol might really be an argument.
     (cond ((eq (nth 1 fp) t)
	    ;; This is if we are in a variable
	    (or
	     ;; * The documentation string for a variable that is a
	     ;;   yes-or-no flag should start with words such as Non-nil
	     ;;   means..., to make it clear that all non-nil values are
	     ;;   equivalent and indicate explicitly what nil and non-nil
	     ;;   mean.
	     ;; * If a user option variable records a true-or-false
	     ;;   condition, give it a name that ends in `-flag'.

	     ;; "True ..." should be "Non-nil ..."
	     (when (looking-at "\"\\*?\\(True\\)\\b")
               (if (checkdoc-autofix-ask-replace
                    (match-beginning 1) (match-end 1)
                    "Say \"Non-nil\" instead of \"True\"?"
                    "Non-nil")
                   nil
                 (checkdoc-create-error
                  "\"True\" should usually be \"Non-nil\""
                  (match-beginning 1) (match-end 1))))

	     ;; If the variable has -flag in the name, make sure
	     (if (and (string-match "-flag$" (car fp))
		      (not (looking-at "\"\\*?Non-nil\\s-+means\\s-+")))
		 (checkdoc-create-error
		  "Flag variable doc strings should usually start: Non-nil means"
		  s (marker-position e) t))
             ;; Don't rename variable to "foo-flag".  This is unnecessary
             ;; and such names often end up inconvenient when the variable
             ;; is later expanded to non-boolean values. --Stef
	     ;; If the doc string starts with "Non-nil means"
	     ;; (if (and (looking-at "\"\\*?Non-nil\\s-+means\\s-+")
	     ;;          (not (string-match "-flag$" (car fp))))
	     ;;     (let ((newname
	     ;;         (if (string-match "-p$" (car fp))
	     ;;             (concat (substring (car fp) 0 -2) "-flag")
	     ;;           (concat (car fp) "-flag"))))
	     ;;       (if (checkdoc-y-or-n-p
	     ;;         (format
             ;;          "Rename to %s and Query-Replace all occurrences?"
	     ;;          newname))
	     ;;           (progn
	     ;;          (beginning-of-defun)
	     ;;          (query-replace-regexp
	     ;;           (concat "\\<" (regexp-quote (car fp)) "\\>")
	     ;;           newname))
	     ;;         (checkdoc-create-error
	     ;;          "Flag variable names should normally end in `-flag'" s
	     ;;          (marker-position e)))))
	     ;; Done with variables
	     ))
	   (t
	    ;; This if we are in a function definition
	    (or
	     ;; * When a function's documentation string mentions the value
	     ;;   of an argument of the function, use the argument name in
	     ;;   capital letters as if it were a name for that value.  Thus,
	     ;;   the documentation string of the function `/' refers to its
	     ;;   second argument as `DIVISOR', because the actual argument
	     ;;   name is `divisor'.

	     ;;   Addendum:  Make sure they appear in the doc in the same
	     ;;              order that they are found in the arg list.
	     (let ((args (nthcdr 4 fp))
		   (last-pos 0)
		   (found 1)
		   (order (and (nth 3 fp) (car (nth 3 fp))))
		   (nocheck (append '("&optional" "&rest" "&key" "&aux"
                                      "&context" "&environment" "&whole"
                                      "&body" "&allow-other-keys")
                                    (nth 3 fp)))
		   (inopts nil))
	       (while (and args found (> found last-pos))
                 (if (or (member (car args) nocheck)
                         (string-match "\\`_" (car args)))
		     (setq args (cdr args)
			   inopts t)
		   (setq last-pos found
			 found (save-excursion
				 (re-search-forward
				  (concat "\\<" (upcase (car args))
					  ;; Require whitespace OR
					  ;; ITEMth<space> OR
					  ;; ITEMs<space>
					  "\\(\\>\\|th\\>\\|s\\>\\|[.,;:]\\)")
				  e t)))
		   (if (not found)
		       (let ((case-fold-search t))
			 ;; If the symbol was not found, let's see if we
			 ;; can find it with a different capitalization
			 ;; and see if the user wants to capitalize it.
			 (if (save-excursion
			       (re-search-forward
				(concat "\\<\\(" (car args)
					;; Require whitespace OR
					;; ITEMth<space> OR
					;; ITEMs<space>
					"\\)\\(\\>\\|th\\>\\|s\\>\\)")
				e t))
			     (if (checkdoc-autofix-ask-replace
				  (match-beginning 1) (match-end 1)
				  (format-message
                                   "If this is the argument `%s', it should appear as %s.  Fix?"
				   (car args) (upcase (car args)))
				  (upcase (car args)) t)
				 (setq found (match-beginning 1))))))
		   (if found (setq args (cdr args)))))
	       (if (not found)
		   ;; It wasn't found at all!  Offer to attach this new symbol
		   ;; to the end of the documentation string.
		   (if (checkdoc-y-or-n-p
			(format
                         "Add %s documentation to end of doc string?"
			 (upcase (car args))))
		       ;; Now do some magic and invent a doc string.
		       (save-excursion
			 (goto-char e) (forward-char -1)
			 (insert "\n"
				 (if inopts "Optional a" "A")
				 "rgument " (upcase (car args))
				 " ")
			 (insert (read-string "Describe: "))
			 (if (not (save-excursion (forward-char -1)
						  (looking-at "[.?!]")))
			     (insert "."))
			 nil)
                     (when checkdoc--argument-missing-flag
                       (checkdoc-create-error
                        (format-message
                         "Argument `%s' should appear (as %s) in the doc string"
                         (car args) (upcase (car args)))
                        s (marker-position e))))
		 (if (or (and order (eq order 'yes))
			 (and (not order) checkdoc-arguments-in-order-flag))
		     (if (< found last-pos)
			 (checkdoc-create-error
			  "Arguments occur in the doc string out of order"
			  s (marker-position e) t)))))
	     ;; * For consistency, phrase the verb in the first sentence of a
	     ;;   documentation string for functions as an imperative.
	     ;;   For instance, use `Return the cons of A and
	     ;;   B.' in preference to `Returns the cons of A and B.'
	     ;;   Usually it looks good to do likewise for the rest of the
	     ;;   first paragraph.  Subsequent paragraphs usually look better
	     ;;   if they have proper subjects.
	     ;;
	     ;; This is the least important of the above tests.  Make sure
	     ;; it occurs last.
	     (and checkdoc-verb-check-experimental-flag
		  (save-excursion
		    ;; Maybe rebuild the monster-regexp
		    (checkdoc-create-common-verbs-regexp)
		    (let ((lim (save-excursion
				 (end-of-line)
				 ;; check string-continuation
				 (if (eq (preceding-char) ?\\)
				     (line-end-position 2)
				   (point))))
			  (rs nil) replace original (case-fold-search t))
		      (while (and (not rs)
				  (re-search-forward
				   checkdoc-common-verbs-regexp
				   lim t))
			(setq original (buffer-substring-no-properties
					(match-beginning 1) (match-end 1))
			      rs (assoc (downcase original)
					checkdoc-common-verbs-wrong-voice))
			(if (not rs) (error "Verb voice alist corrupted"))
			(setq replace (let ((case-fold-search nil))
					(if (string-match-p "^[A-Z]" original)
					    (capitalize (cdr rs))
					  (cdr rs))))
			(if (checkdoc-autofix-ask-replace
			     (match-beginning 1) (match-end 1)
			     (format "Use the imperative for \"%s\".  \
Replace with \"%s\"?" original replace)
			     replace t)
			    (setq rs nil)))
		      (if rs
			  ;; there was a match, but no replace
			  (checkdoc-create-error
			   (format
			    "Probably \"%s\" should be imperative \"%s\""
			    original replace)
			   (match-beginning 1) (match-end 1))))))
	     ;; "Return true ..." should be "Return non-nil ..."
	     (when (looking-at "\"Return \\(true\\)\\b")
               (if (checkdoc-autofix-ask-replace
                    (match-beginning 1) (match-end 1)
                    "Say \"non-nil\" instead of \"true\"?"
                    "non-nil")
                   nil
                 (checkdoc-create-error
                  "\"true\" should usually be \"non-nil\""
                  (match-beginning 1) (match-end 1))))
	     ;; Done with functions
	     )))
     ;;* When a documentation string refers to a Lisp symbol, write it as
     ;;  it would be printed (which usually means in lower case), with
     ;;  single-quotes around it.  For example: ‘lambda’.  There are two
     ;;  exceptions: write t and nil without single-quotes.  (For
     ;;  compatibility with an older Emacs style, quoting with ` and '
     ;;  also works, e.g., `lambda' is treated like ‘lambda’.)
     (save-excursion
       (let ((found nil) (start (point)) (msg nil) (ms nil))
	 (while (and (not msg)
		     (re-search-forward
		      ;; Ignore manual page references like
		      ;; git-config(1).
		      "[^-([`'‘’:a-zA-Z]\\(\\w+[:-]\\(\\w\\|\\s_\\)+\\)[^]('’]"
		      e t))
	   (setq ms (match-string 1))
	   ;; A . is a \s_ char, so we must remove periods from
	   ;; sentences more carefully.
	   (when (string-match-p "\\.$" ms)
	     (setq ms (substring ms 0 (1- (length ms)))))
	   (if (and (not (checkdoc-in-sample-code-p start e))
		    (not (checkdoc-in-example-string-p start e))
		    (not (member ms checkdoc-symbol-words))
		    (setq found (intern-soft ms))
		    (or (boundp found) (fboundp found)))
	       (progn
		 (setq msg (format-message
                            "Add quotes around Lisp symbol `%s'?" ms))
		 (if (checkdoc-autofix-ask-replace
		      (match-beginning 1) (+ (match-beginning 1)
					     (length ms))
		      msg (format "`%s'" ms) t)
		     (setq msg nil)
		   (setq msg
			 (format-message
                          "Lisp symbol `%s' should appear in quotes" ms))))))
	 (if msg
	     (checkdoc-create-error msg (match-beginning 1)
				    (+ (match-beginning 1)
				       (length ms)))
	   nil)))
     ;; t and nil case
     (save-excursion
       (if (re-search-forward "\\([`‘]\\(t\\|nil\\)['’]\\)" e t)
	   (if (checkdoc-autofix-ask-replace
		(match-beginning 1) (match-end 1)
                (format "%s should not appear in quotes.  Remove?"
			(match-string 2))
		(match-string 2) t)
	       nil
	     (checkdoc-create-error
	      "Symbols t and nil should not appear in single quotes"
	      (match-beginning 1) (match-end 1)))))
     ;; Here is some basic sentence formatting
     (checkdoc-sentencespace-region-engine (point) e)
     ;; Here are common proper nouns that should always appear capitalized.
     (checkdoc-proper-noun-region-engine (point) e)
     ;; Make sure the doc string has correctly spelled English words
     ;; in it.  This function is extracted due to its complexity,
     ;; and reliance on the Ispell program.
     (checkdoc-ispell-docstring-engine e take-notes)
     ;; User supplied checks
     (save-excursion (run-hook-with-args-until-success 'checkdoc-style-functions fp e)))))