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 (rx (not (any "0-9A-Za-z_`‘-"))
(group (or (seq (any "CMs") "-" (any "A-Za-z"))
(group (opt (group (any "CMs") "-"))
"mouse-" (any "0-3"))))
eow)))
;; 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" "nil")
(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)))))