+;;; `error' and `message' text verifier.
+;;
+(defun checkdoc-message-text-search (&optional beg end)
+ "Search between BEG and END for a style error with message text.
+Optional arguments BEG and END represent the boundary of the check.
+The default boundary is the entire buffer."
+ (let ((e nil)
+ (type nil))
+ (if (not (or beg end)) (setq beg (point-min) end (point-max)))
+ (goto-char beg)
+ (while (setq type (checkdoc-message-text-next-string end))
+ (setq e (checkdoc-message-text-engine type)))
+ e))
+
+(defun checkdoc-message-text-next-string (end)
+ "Move cursor to the next checkable message string after point.
+Return the message classification.
+Argument END is the maximum bounds to search in."
+ (let ((return nil))
+ (while (and (not return)
+ (re-search-forward
+ "(\\s-*\\(\\(\\w\\|\\s_\\)*error\\|\
+\\(\\w\\|\\s_\\)*y-or-n-p\\(-with-timeout\\)?\
+\\|checkdoc-autofix-ask-replace\\)[ \t\n]+" end t))
+ (let* ((fn (match-string 1))
+ (type (cond ((string-match "error" fn)
+ 'error)
+ (t 'y-or-n-p))))
+ (if (string-match "checkdoc-autofix-ask-replace" fn)
+ (progn (forward-sexp 2)
+ (skip-chars-forward " \t\n")))
+ (if (and (eq type 'y-or-n-p)
+ (looking-at "(format[ \t\n]+"))
+ (goto-char (match-end 0)))
+ (skip-chars-forward " \t\n")
+ (if (not (looking-at "\""))
+ nil
+ (setq return type))))
+ return))
+
+(defun checkdoc-message-text-engine (&optional type)
+ "Return or fix errors found in strings passed to a message display function.
+According to the documentation for the function `error', the error list
+should not end with a period, and should start with a capital letter.
+The function `y-or-n-p' has similar constraints.
+Argument TYPE specifies the type of question, such as `error or `y-or-n-p."
+ ;; If type is nil, then attempt to derive it.
+ (if (not type)
+ (save-excursion
+ (up-list -1)
+ (if (looking-at "(format")
+ (up-list -1))
+ (setq type
+ (cond ((looking-at "(error")
+ 'error)
+ (t 'y-or-n-p)))))
+ (let ((case-fold-search nil))
+ (or
+ ;; From the documentation of the symbol `error':
+ ;; In Emacs, the convention is that error messages start with a capital
+ ;; letter but *do not* end with a period. Please follow this convention
+ ;; for the sake of consistency.
+ (if (and (save-excursion (forward-char 1)
+ (looking-at "[a-z]\\w+"))
+ (not (checkdoc-autofix-ask-replace
+ (match-beginning 0) (match-end 0)
+ "Capitalize your message text? "
+ (capitalize (match-string 0))
+ t)))
+ (checkdoc-create-error
+ "Messages should start with a capital letter"
+ (match-beginning 0) (match-end 0))
+ nil)
+ ;; In general, sentences should have two spaces after the period.
+ (checkdoc-sentencespace-region-engine (point)
+ (save-excursion (forward-sexp 1)
+ (point)))
+ ;; Look for proper nouns in this region too.
+ (checkdoc-proper-noun-region-engine (point)
+ (save-excursion (forward-sexp 1)
+ (point)))
+ ;; Here are message type specific questions.
+ (if (and (eq type 'error)
+ (save-excursion (forward-sexp 1)
+ (forward-char -2)
+ (looking-at "\\."))
+ (not (checkdoc-autofix-ask-replace (match-beginning 0)
+ (match-end 0)
+ "Remove period from error? "
+ ""
+ t)))
+ (checkdoc-create-error
+ "Error messages should *not* end with a period"
+ (match-beginning 0) (match-end 0))
+ nil)
+ ;; `y-or-n-p' documentation explicitly says:
+ ;; It should end in a space; `y-or-n-p' adds `(y or n) ' to it.
+ ;; I added the ? requirement. Without it, it is unclear that we
+ ;; ask a question and it appears to be an undocumented style.
+ (if (eq type 'y-or-n-p)
+ (if (not (save-excursion (forward-sexp 1)
+ (forward-char -3)
+ (not (looking-at "\\? "))))
+ nil
+ (if (save-excursion (forward-sexp 1)
+ (forward-char -2)
+ (looking-at "\\?"))
+ ;; If we see a ?, then replace with "? ".
+ (if (checkdoc-autofix-ask-replace
+ (match-beginning 0) (match-end 0)
+ "`y-or-n-p' argument should end with \"? \". Fix? "
+ "? " t)
+ nil
+ (checkdoc-create-error
+ "`y-or-n-p' argument should end with \"? \""
+ (match-beginning 0) (match-end 0)))
+ (if (save-excursion (forward-sexp 1)
+ (forward-char -2)
+ (looking-at " "))
+ (if (checkdoc-autofix-ask-replace
+ (match-beginning 0) (match-end 0)
+ "`y-or-n-p' argument should end with \"? \". Fix? "
+ "? " t)
+ nil
+ (checkdoc-create-error
+ "`y-or-n-p' argument should end with \"? \""
+ (match-beginning 0) (match-end 0)))
+ (if (and ;; if this isn't true, we have a problem.
+ (save-excursion (forward-sexp 1)
+ (forward-char -1)
+ (looking-at "\""))
+ (checkdoc-autofix-ask-replace
+ (match-beginning 0) (match-end 0)
+ "`y-or-n-p' argument should end with \"? \". Fix? "
+ "? \"" t))
+ nil
+ (checkdoc-create-error
+ "`y-or-n-p' argument should end with \"? \""
+ (match-beginning 0) (match-end 0)))))))
+ ;; Now, let's just run the spell checker on this guy.
+ (checkdoc-ispell-docstring-engine (save-excursion (forward-sexp 1)
+ (point)))
+ )))
+