config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

org-lint.el (69243B)


      1 ;;; org-lint.el --- Linting for Org documents        -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
      4 
      5 ;; Author: Nicolas Goaziou <mail@nicolasgoaziou.fr>
      6 ;; Keywords: outlines, hypermedia, calendar, text
      7 
      8 ;; This file is part of GNU Emacs.
      9 
     10 ;; GNU Emacs is free software; you can redistribute it and/or modify
     11 ;; it under the terms of the GNU General Public License as published by
     12 ;; the Free Software Foundation, either version 3 of the License, or
     13 ;; (at your option) any later version.
     14 
     15 ;; GNU Emacs is distributed in the hope that it will be useful,
     16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18 ;; GNU General Public License for more details.
     19 
     20 ;; You should have received a copy of the GNU General Public License
     21 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
     22 
     23 ;;; Commentary:
     24 
     25 ;; This library implements linting for Org syntax.  The process is
     26 ;; started by calling `org-lint' command, which see.
     27 
     28 ;; New checkers are added by `org-lint-add-checker' function.
     29 ;; Internally, all checks are listed in `org-lint--checkers'.
     30 
     31 ;; Results are displayed in a special "*Org Lint*" buffer with
     32 ;; a dedicated major mode, derived from `tabulated-list-mode'.
     33 ;; In addition to the usual key-bindings inherited from it, "C-j" and
     34 ;; "TAB" display problematic line reported under point whereas "RET"
     35 ;; jumps to it.  Also, "h" hides all reports similar to the current
     36 ;; one.  Additionally, "i" removes them from subsequent reports.
     37 
     38 ;; Checks currently implemented report the following:
     39 
     40 ;; - duplicates CUSTOM_ID properties,
     41 ;; - duplicate NAME values,
     42 ;; - duplicate targets,
     43 ;; - duplicate footnote definitions,
     44 ;; - orphaned affiliated keywords,
     45 ;; - obsolete affiliated keywords,
     46 ;; - deprecated export block syntax,
     47 ;; - deprecated Babel header syntax,
     48 ;; - missing language in source blocks,
     49 ;; - missing backend in export blocks,
     50 ;; - invalid Babel call blocks,
     51 ;; - NAME values with a colon,
     52 ;; - wrong babel headers,
     53 ;; - invalid value in babel headers,
     54 ;; - misuse of CATEGORY keyword,
     55 ;; - "coderef" links with unknown destination,
     56 ;; - "custom-id" links with unknown destination,
     57 ;; - "fuzzy" links with unknown destination,
     58 ;; - "id" links with unknown destination,
     59 ;; - links to non-existent local files,
     60 ;; - SETUPFILE keywords with non-existent file parameter,
     61 ;; - INCLUDE keywords with misleading link parameter,
     62 ;; - obsolete markup in INCLUDE keyword,
     63 ;; - unknown items in OPTIONS keyword,
     64 ;; - spurious macro arguments or invalid macro templates,
     65 ;; - special properties in properties drawers,
     66 ;; - obsolete syntax for properties drawers,
     67 ;; - invalid duration in EFFORT property,
     68 ;; - invalid ID property with a double colon,
     69 ;; - missing definition for footnote references,
     70 ;; - missing reference for footnote definitions,
     71 ;; - non-footnote definitions in footnote section,
     72 ;; - probable invalid keywords,
     73 ;; - invalid blocks,
     74 ;; - mismatched repeaters in planning info line,
     75 ;; - misplaced planning info line,
     76 ;; - probable incomplete drawers,
     77 ;; - probable indented diary-sexps,
     78 ;; - obsolete QUOTE section,
     79 ;; - obsolete "file+application" link,
     80 ;; - obsolete escape syntax in links,
     81 ;; - spurious colons in tags,
     82 ;; - invalid bibliography file,
     83 ;; - missing "print_bibliography" keyword,
     84 ;; - invalid value for "cite_export" keyword,
     85 ;; - incomplete citation object.
     86 
     87 
     88 ;;; Code:
     89 
     90 (require 'org-macs)
     91 (org-assert-version)
     92 
     93 (require 'cl-lib)
     94 (require 'ob)
     95 (require 'oc)
     96 (require 'ol)
     97 (require 'org-attach)
     98 (require 'org-macro)
     99 (require 'org-fold)
    100 (require 'ox)
    101 (require 'seq)
    102 
    103 
    104 ;;; Checkers structure
    105 
    106 (cl-defstruct (org-lint-checker (:copier nil))
    107   name summary function trust categories)
    108 
    109 (defvar org-lint--checkers nil
    110   "List of all available checkers.
    111 This list is populated by `org-lint-add-checker' function.")
    112 
    113 ;;;###autoload
    114 (defun org-lint-add-checker (name summary fun &rest props)
    115   "Add a new checker for linter.
    116 
    117 NAME is a unique check identifier, as a non-nil symbol.  SUMMARY
    118 is a short description of the check, as a string.
    119 
    120 The check is done calling the function FUN with one mandatory
    121 argument, the parse tree describing the current Org buffer.  Such
    122 function calls are wrapped within a `save-excursion' and point is
    123 always at `point-min'.  Its return value has to be an
    124 alist (POSITION MESSAGE) where POSITION refer to the buffer
    125 position of the error, as an integer, and MESSAGE is a one-line
    126 string describing the error.
    127 
    128 Optional argument PROPS provides additional information about the
    129 checker.  Currently, two properties are supported:
    130 
    131   `:categories'
    132 
    133      Categories relative to the check, as a list of symbol.  They
    134      are used for filtering when calling `org-lint'.  Checkers
    135      not explicitly associated to a category are collected in the
    136      `default' one.
    137 
    138   `:trust'
    139 
    140     The trust level one can have in the check.  It is either
    141     `low' or `high', depending on the heuristics implemented and
    142     the nature of the check.  This has an indicative value only
    143     and is displayed along reports."
    144   (declare (indent 1))
    145   ;; Sanity checks.
    146   (pcase name
    147     (`nil (error "Name field is mandatory for checkers"))
    148     ((pred symbolp) nil)
    149     (_ (error "Invalid type for name field")))
    150   (unless (functionp fun)
    151     (error "Checker field is expected to be a valid function"))
    152   ;; Install checker in `org-lint--checkers'; uniquify by name.
    153   (setq org-lint--checkers
    154         (cons (apply #'make-org-lint-checker
    155                      :name name
    156                      :summary summary
    157                      :function fun
    158                      props)
    159               (seq-remove (lambda (c) (eq name (org-lint-checker-name c)))
    160                           org-lint--checkers))))
    161 
    162 
    163 ;;; Reports UI
    164 
    165 (defvar org-lint--report-mode-map
    166   (let ((map (make-sparse-keymap)))
    167     (set-keymap-parent map tabulated-list-mode-map)
    168     (define-key map (kbd "RET") 'org-lint--jump-to-source)
    169     (define-key map (kbd "TAB") 'org-lint--show-source)
    170     (define-key map (kbd "C-j") 'org-lint--show-source)
    171     (define-key map (kbd "h") 'org-lint--hide-checker)
    172     (define-key map (kbd "i") 'org-lint--ignore-checker)
    173     map)
    174   "Local keymap for `org-lint--report-mode' buffers.")
    175 
    176 (define-derived-mode org-lint--report-mode tabulated-list-mode "OrgLint"
    177   "Major mode used to display reports emitted during linting.
    178 \\{org-lint--report-mode-map}"
    179   (setf tabulated-list-format
    180 	`[("Line" 6
    181 	   (lambda (a b)
    182 	     (< (string-to-number (aref (cadr a) 0))
    183 		(string-to-number (aref (cadr b) 0))))
    184 	   :right-align t)
    185 	  ("Trust" 5 t)
    186 	  ("Warning" 0 t)])
    187   (tabulated-list-init-header))
    188 
    189 (defun org-lint--generate-reports (buffer checkers)
    190   "Generate linting report for BUFFER.
    191 
    192 CHECKERS is the list of checkers used.
    193 
    194 Return an alist (ID [LINE TRUST DESCRIPTION CHECKER]), suitable
    195 for `tabulated-list-printer'."
    196   (with-current-buffer buffer
    197     (save-excursion
    198       (goto-char (point-min))
    199       (let ((ast (org-element-parse-buffer nil nil 'defer))
    200 	    (id 0)
    201 	    (last-line 1)
    202 	    (last-pos 1))
    203 	;; Insert unique ID for each report.  Replace buffer positions
    204 	;; with line numbers.
    205 	(mapcar
    206 	 (lambda (report)
    207 	   (list
    208 	    (cl-incf id)
    209 	    (apply #'vector
    210 		   (cons
    211 		    (progn
    212 		      (goto-char (car report))
    213 		      (forward-line 0)
    214 		      (prog1 (propertize
    215                               (number-to-string
    216 			       (cl-incf last-line
    217 				        (count-lines last-pos (point))))
    218                               'org-lint-marker (car report))
    219 			(setf last-pos (point))))
    220 		    (cdr report)))))
    221 	 ;; Insert trust level in generated reports.  Also sort them
    222 	 ;; by buffer position in order to optimize lines computation.
    223 	 (sort (cl-mapcan
    224 		(lambda (c)
    225 		  (let ((trust (symbol-name (org-lint-checker-trust c))))
    226 		    (mapcar
    227 		     (lambda (report)
    228 		       (list (copy-marker (car report)) trust (nth 1 report) c))
    229 		     (save-excursion
    230 		       (funcall (org-lint-checker-function c)
    231 			        ast)))))
    232 		checkers)
    233 	       #'car-less-than-car))))))
    234 
    235 (defvar-local org-lint--source-buffer nil
    236   "Source buffer associated to current report buffer.")
    237 
    238 (defvar-local org-lint--local-checkers nil
    239   "List of checkers used to build current report.")
    240 
    241 (defun org-lint--refresh-reports ()
    242   (setq tabulated-list-entries
    243 	(org-lint--generate-reports org-lint--source-buffer
    244 				    org-lint--local-checkers))
    245   (tabulated-list-print))
    246 
    247 (defun org-lint--current-line ()
    248   "Return current report line, as a number."
    249   (string-to-number (aref (tabulated-list-get-entry) 0)))
    250 
    251 (defun org-lint--current-marker ()
    252   "Return current report marker."
    253   (get-text-property 0 'org-lint-marker (aref (tabulated-list-get-entry) 0)))
    254 
    255 (defun org-lint--current-checker (&optional entry)
    256   "Return current report checker.
    257 When optional argument ENTRY is non-nil, use this entry instead
    258 of current one."
    259   (aref (if entry (nth 1 entry) (tabulated-list-get-entry)) 3))
    260 
    261 (defun org-lint--display-reports (source checkers)
    262   "Display linting reports for buffer SOURCE.
    263 CHECKERS is the list of checkers used."
    264   (let ((buffer (get-buffer-create "*Org Lint*")))
    265     (with-current-buffer buffer
    266       (org-lint--report-mode)
    267       (setf org-lint--source-buffer source)
    268       (setf org-lint--local-checkers checkers)
    269       (org-lint--refresh-reports)
    270       (add-hook 'tabulated-list-revert-hook #'org-lint--refresh-reports nil t))
    271     (pop-to-buffer buffer)))
    272 
    273 (defun org-lint--jump-to-source ()
    274   "Move to source line that generated the report at point."
    275   (interactive)
    276   (let ((mk (org-lint--current-marker)))
    277     (switch-to-buffer-other-window org-lint--source-buffer)
    278     (unless (<= (point-min) mk (point-max)) (widen))
    279     (goto-char mk)
    280     (org-fold-show-set-visibility 'local)
    281     (recenter)))
    282 
    283 (defun org-lint--show-source ()
    284   "Show source line that generated the report at point."
    285   (interactive)
    286   (let ((buffer (current-buffer)))
    287     (org-lint--jump-to-source)
    288     (switch-to-buffer-other-window buffer)))
    289 
    290 (defun org-lint--hide-checker ()
    291   "Hide all reports from checker that generated the report at point."
    292   (interactive)
    293   (let ((c (org-lint--current-checker)))
    294     (setf tabulated-list-entries
    295 	  (cl-remove-if (lambda (e) (equal c (org-lint--current-checker e)))
    296 			tabulated-list-entries))
    297     (tabulated-list-print)))
    298 
    299 (defun org-lint--ignore-checker ()
    300   "Ignore all reports from checker that generated the report at point.
    301 Checker will also be ignored in all subsequent reports."
    302   (interactive)
    303   (setf org-lint--local-checkers
    304 	(remove (org-lint--current-checker) org-lint--local-checkers))
    305   (org-lint--hide-checker))
    306 
    307 
    308 ;;; Main function
    309 
    310 ;;;###autoload
    311 (defun org-lint (&optional arg)
    312   "Check current Org buffer for syntax mistakes.
    313 
    314 By default, run all checkers.  With a `\\[universal-argument]' prefix ARG, \
    315 select one
    316 category of checkers only.  With a `\\[universal-argument] \
    317 \\[universal-argument]' prefix, run one precise
    318 checker by its name.
    319 
    320 ARG can also be a list of checker names, as symbols, to run."
    321   (interactive "P")
    322   (unless (derived-mode-p 'org-mode) (user-error "Not in an Org buffer"))
    323   (when (called-interactively-p 'any)
    324     (message "Org linting process starting..."))
    325   (let ((checkers
    326 	 (pcase arg
    327 	   (`nil org-lint--checkers)
    328 	   (`(4)
    329 	    (let ((category
    330 		   (completing-read
    331 		    "Checker category: "
    332 		    (mapcar #'org-lint-checker-categories org-lint--checkers)
    333 		    nil t)))
    334 	      (cl-remove-if-not
    335 	       (lambda (c)
    336 		 (assoc-string category (org-lint-checker-categories c)))
    337 	       org-lint--checkers)))
    338 	   (`(16)
    339 	    (list
    340 	     (let ((name (completing-read
    341 			  "Checker name: "
    342 			  (mapcar #'org-lint-checker-name org-lint--checkers)
    343 			  nil t)))
    344 	       (catch 'exit
    345 		 (dolist (c org-lint--checkers)
    346 		   (when (string= (org-lint-checker-name c) name)
    347 		     (throw 'exit c)))))))
    348 	   ((pred consp)
    349 	    (cl-remove-if-not (lambda (c) (memq (org-lint-checker-name c) arg))
    350 			      org-lint--checkers))
    351 	   (_ (user-error "Invalid argument `%S' for `org-lint'" arg)))))
    352     (if (not (called-interactively-p 'any))
    353 	(org-lint--generate-reports (current-buffer) checkers)
    354       (org-lint--display-reports (current-buffer) checkers)
    355       (message "Org linting process completed"))))
    356 
    357 
    358 ;;; Checker functions
    359 
    360 (defun org-lint--collect-duplicates
    361     (ast type extract-key extract-position build-message)
    362   "Helper function to collect duplicates in parse tree AST.
    363 
    364 EXTRACT-KEY is a function extracting key.  It is called with
    365 a single argument: the element or object.  Comparison is done
    366 with `equal'.
    367 
    368 EXTRACT-POSITION is a function returning position for the report.
    369 It is called with two arguments, the object or element, and the
    370 key.
    371 
    372 BUILD-MESSAGE is a function creating the report message.  It is
    373 called with one argument, the key used for comparison."
    374   (let* (keys
    375 	 originals
    376 	 reports
    377 	 (make-report
    378 	  (lambda (position value)
    379 	    (push (list position (funcall build-message value)) reports))))
    380     (org-element-map ast type
    381       (lambda (datum)
    382 	(let ((key (funcall extract-key datum)))
    383 	  (cond
    384 	   ((not key))
    385 	   ((assoc key keys) (cl-pushnew (assoc key keys) originals)
    386 	    (funcall make-report (funcall extract-position datum key) key))
    387 	   (t (push (cons key (funcall extract-position datum key)) keys))))))
    388     (dolist (e originals reports) (funcall make-report (cdr e) (car e)))))
    389 
    390 (defun org-lint-misplaced-heading (ast)
    391   "Check for accidentally misplaced heading lines.
    392 Example:
    393 ** Heading 1
    394 ** Heading 2** Oops heading 3
    395 ** Heading 4"
    396   (org-with-point-at ast
    397     (goto-char (point-min))
    398     (let (result)
    399       ;; Heuristics for 2+ level heading not at bol.
    400       (while (re-search-forward (rx (not (any "*\n\r ,")) ;; Not a bol; not escaped ,** heading; not " *** words"
    401                                     "*" (1+ "*") " ") nil t)
    402         ;; Limit false-positive rate by only complaining about
    403         ;; ** Heading** Heading and
    404         ;; ** Oops heading
    405         ;; Paragraph** Oops heading
    406         (when (org-element-type-p
    407                (org-element-at-point)
    408                '(paragraph headline))
    409           (push (list (match-beginning 0) "Possibly misplaced heading line") result)))
    410       result)))
    411 
    412 (defun org-lint-duplicate-custom-id (ast)
    413   (org-lint--collect-duplicates
    414    ast
    415    'node-property
    416    (lambda (property)
    417      (and (org-string-equal-ignore-case
    418            "CUSTOM_ID" (org-element-property :key property))
    419 	  (org-element-property :value property)))
    420    (lambda (property _) (org-element-begin property))
    421    (lambda (key) (format "Duplicate CUSTOM_ID property \"%s\"" key))))
    422 
    423 (defun org-lint-duplicate-name (ast)
    424   (org-lint--collect-duplicates
    425    ast
    426    org-element-all-elements
    427    (lambda (datum) (org-element-property :name datum))
    428    (lambda (datum name)
    429      (goto-char (org-element-begin datum))
    430      (re-search-forward
    431       (format "^[ \t]*#\\+[A-Za-z]+:[ \t]*%s[ \t]*$" (regexp-quote name)))
    432      (match-beginning 0))
    433    (lambda (key) (format "Duplicate NAME \"%s\"" key))))
    434 
    435 (defun org-lint-duplicate-target (ast)
    436   (org-lint--collect-duplicates
    437    ast
    438    'target
    439    (lambda (target) (split-string (org-element-property :value target)))
    440    (lambda (target _) (org-element-begin target))
    441    (lambda (key)
    442      (format "Duplicate target <<%s>>" (mapconcat #'identity key " ")))))
    443 
    444 (defun org-lint-duplicate-footnote-definition (ast)
    445   (org-lint--collect-duplicates
    446    ast
    447    'footnote-definition
    448    (lambda (definition)  (org-element-property :label definition))
    449    (lambda (definition _) (org-element-post-affiliated definition))
    450    (lambda (key) (format "Duplicate footnote definition \"%s\"" key))))
    451 
    452 (defun org-lint-orphaned-affiliated-keywords (ast)
    453   ;; Ignore orphan RESULTS keywords, which could be generated from
    454   ;; a source block returning no value.
    455   (let ((keywords (cl-set-difference org-element-affiliated-keywords
    456 				     '("RESULT" "RESULTS")
    457 				     :test #'equal)))
    458     (org-element-map ast 'keyword
    459       (lambda (k)
    460 	(let ((key (org-element-property :key k)))
    461 	  (and (or (let ((case-fold-search t))
    462 		     (string-match-p "\\`ATTR_[-_A-Za-z0-9]+\\'" key))
    463 		   (member key keywords))
    464 	       (list (org-element-post-affiliated k)
    465 		     (format "Orphaned affiliated keyword: \"%s\"" key))))))))
    466 
    467 (defun org-lint-regular-keyword-before-affiliated (ast)
    468   (org-element-map ast 'keyword
    469     (lambda (keyword)
    470       (when (= (org-element-post-blank keyword) 0)
    471         (let ((next-element (org-with-point-at (org-element-end keyword)
    472                               (org-element-at-point))))
    473           (when (< (org-element-begin next-element) (org-element-post-affiliated next-element))
    474             ;; A keyword followed without blank lines by an element with affiliated keywords.
    475             ;; The keyword may be confused with affiliated keywords.
    476             (list (org-element-begin keyword)
    477                   (format "Independent keyword %s may be confused with affiliated keywords below"
    478                           (org-element-property :key keyword)))))))))
    479 
    480 (defun org-lint-obsolete-affiliated-keywords (_)
    481   (let ((regexp (format "^[ \t]*#\\+%s:"
    482 			(regexp-opt '("DATA" "LABEL" "RESNAME" "SOURCE"
    483 				      "SRCNAME" "TBLNAME" "RESULT" "HEADERS")
    484 				    t)))
    485 	reports)
    486     (while (re-search-forward regexp nil t)
    487       (let ((key (upcase (match-string-no-properties 1))))
    488 	(when (< (point)
    489 		 (org-element-post-affiliated (org-element-at-point)))
    490 	  (push
    491 	   (list (line-beginning-position)
    492 		 (format
    493 		  "Obsolete affiliated keyword: \"%s\".  Use \"%s\" instead"
    494 		  key
    495 		  (pcase key
    496 		    ("HEADERS" "HEADER")
    497 		    ("RESULT" "RESULTS")
    498 		    (_ "NAME"))))
    499 	   reports))))
    500     reports))
    501 
    502 (defun org-lint-deprecated-export-blocks (ast)
    503   (let ((deprecated '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
    504 		      "ODT" "ORG" "TEXINFO")))
    505     (org-element-map ast 'special-block
    506       (lambda (b)
    507 	(let ((type (org-element-property :type b)))
    508 	  (when (member-ignore-case type deprecated)
    509 	    (list
    510 	     (org-element-post-affiliated b)
    511 	     (format
    512 	      "Deprecated syntax for export block.  Use \"BEGIN_EXPORT %s\" \
    513 instead"
    514 	      type))))))))
    515 
    516 (defun org-lint-deprecated-header-syntax (ast)
    517   (let* ((deprecated-babel-properties
    518 	  ;; DIR is also used for attachments.
    519 	  (delete "dir"
    520 		  (mapcar (lambda (arg) (downcase (symbol-name (car arg))))
    521 			  org-babel-common-header-args-w-values)))
    522 	 (deprecated-re
    523 	  (format "\\`%s[ \t]" (regexp-opt deprecated-babel-properties t))))
    524     (org-element-map ast '(keyword node-property)
    525       (lambda (datum)
    526 	(let ((key (org-element-property :key datum)))
    527 	  (pcase (org-element-type datum)
    528 	    (`keyword
    529 	     (let ((value (org-element-property :value datum)))
    530 	       (and (string= key "PROPERTY")
    531 		    (string-match deprecated-re value)
    532 		    (list (org-element-begin datum)
    533 			  (format "Deprecated syntax for \"%s\".  \
    534 Use header-args instead"
    535 				  (match-string-no-properties 1 value))))))
    536 	    (`node-property
    537 	     (and (member-ignore-case key deprecated-babel-properties)
    538 		  (list
    539 		   (org-element-begin datum)
    540 		   (format "Deprecated syntax for \"%s\".  \
    541 Use :header-args: instead"
    542 			   key))))))))))
    543 
    544 (defun org-lint-missing-language-in-src-block (ast)
    545   (org-element-map ast 'src-block
    546     (lambda (b)
    547       (unless (org-element-property :language b)
    548 	(list (org-element-post-affiliated b)
    549 	      "Missing language in source block")))))
    550 
    551 (defun org-lint-suspicious-language-in-src-block (ast)
    552   (org-element-map ast 'src-block
    553     (lambda (b)
    554       (when-let* ((lang (org-element-property :language b)))
    555         (unless (or (functionp (intern (format "org-babel-execute:%s" lang)))
    556                     ;; No babel backend, but there is corresponding
    557                     ;; major mode.
    558                     (fboundp (org-src-get-lang-mode lang)))
    559 	  (list (org-element-property :post-affiliated b)
    560 	        (format "Unknown source block language: '%s'" lang)))))))
    561 
    562 (defun org-lint-missing-backend-in-export-block (ast)
    563   (org-element-map ast 'export-block
    564     (lambda (b)
    565       (unless (org-element-property :type b)
    566 	(list (org-element-post-affiliated b)
    567 	      "Missing backend in export block")))))
    568 
    569 (defun org-lint-invalid-babel-call-block (ast)
    570   (org-element-map ast 'babel-call
    571     (lambda (b)
    572       (cond
    573        ((not (org-element-property :call b))
    574 	(list (org-element-post-affiliated b)
    575 	      "Invalid syntax in babel call block"))
    576        ((let ((h (org-element-property :end-header b)))
    577 	  (and h (string-match-p "\\`\\[.*\\]\\'" h)))
    578 	(list
    579 	 (org-element-post-affiliated b)
    580 	 "Babel call's end header must not be wrapped within brackets"))))))
    581 
    582 (defun org-lint-deprecated-category-setup (ast)
    583   (org-element-map ast 'keyword
    584     (let (category-flag)
    585       (lambda (k)
    586 	(cond
    587 	 ((not (string= (org-element-property :key k) "CATEGORY")) nil)
    588 	 (category-flag
    589 	  (list (org-element-post-affiliated k)
    590 		"Spurious CATEGORY keyword.  Set :CATEGORY: property instead"))
    591 	 (t (setf category-flag t) nil))))))
    592 
    593 (defun org-lint-invalid-coderef-link (ast)
    594   (let ((info (list :parse-tree ast)))
    595     (org-element-map ast 'link
    596       (lambda (link)
    597 	(let ((ref (org-element-property :path link)))
    598 	  (and (equal (org-element-property :type link) "coderef")
    599 	       (not (ignore-errors (org-export-resolve-coderef ref info)))
    600 	       (list (org-element-begin link)
    601 		     (format "Unknown coderef \"%s\"" ref))))))))
    602 
    603 (defun org-lint-invalid-custom-id-link (ast)
    604   (let ((info (list :parse-tree ast)))
    605     (org-element-map ast 'link
    606       (lambda (link)
    607 	(and (equal (org-element-property :type link) "custom-id")
    608 	     (not (ignore-errors (org-export-resolve-id-link link info)))
    609 	     (list (org-element-begin link)
    610 		   (format "Unknown custom ID \"%s\""
    611 			   (org-element-property :path link))))))))
    612 
    613 (defun org-lint-invalid-fuzzy-link (ast)
    614   (let ((info (list :parse-tree ast)))
    615     (org-element-map ast 'link
    616       (lambda (link)
    617 	(and (equal (org-element-property :type link) "fuzzy")
    618 	     (not (ignore-errors (org-export-resolve-fuzzy-link link info)))
    619 	     (list (org-element-begin link)
    620 		   (format "Unknown fuzzy location \"%s\""
    621 			   (let ((path (org-element-property :path link)))
    622 			     (if (string-prefix-p "*" path)
    623 				 (substring path 1)
    624 			       path)))))))))
    625 
    626 (defun org-lint-invalid-id-link (ast)
    627   (let ((id-locations-updated nil))
    628     (org-element-map ast 'link
    629       (lambda (link)
    630         (let ((id (org-element-property :path link)))
    631 	  (and (equal (org-element-property :type link) "id")
    632                (progn
    633                  (unless id-locations-updated
    634                    (org-id-update-id-locations nil t)
    635                    (setq id-locations-updated t))
    636                  t)
    637                ;; The locations are up-to-date with file changes after
    638                ;; the call to `org-id-update-id-locations'.  We do not
    639                ;; need to double-check if recorded ID is still present
    640                ;; in the file.
    641 	       (not (org-id-find-id-file id))
    642 	       (list (org-element-begin link)
    643 		     (format "Unknown ID \"%s\"" id))))))))
    644 
    645 (defun org-lint-confusing-brackets (ast)
    646   (org-element-map ast 'link
    647     (lambda (link)
    648       (org-with-wide-buffer
    649        (when (eq (char-after (org-element-end link)) ?\])
    650          (list (org-element-begin link)
    651 	       (format "Trailing ']' after link end")))))))
    652 
    653 (defun org-lint-brackets-inside-description (ast)
    654   (org-element-map ast 'link
    655     (lambda (link)
    656       (when (org-element-contents-begin link)
    657         (org-with-point-at link
    658           (goto-char (org-element-contents-begin link))
    659           (let ((count 0))
    660             (while (re-search-forward (rx (or ?\] ?\[)) (org-element-contents-end link) t)
    661               (if (equal (match-string 0) "[") (cl-incf count) (cl-decf count)))
    662             (when (> count 0)
    663               (list (org-element-begin link)
    664 	            (format "No closing ']' matches '[' in link description: %s"
    665                             (buffer-substring-no-properties
    666                              (org-element-contents-begin link)
    667                              (org-element-contents-end link)))))))))))
    668 
    669 (defun org-lint-special-property-in-properties-drawer (ast)
    670   (org-element-map ast 'node-property
    671     (lambda (p)
    672       (let ((key (org-element-property :key p)))
    673 	(and (member-ignore-case key org-special-properties)
    674 	     (list (org-element-begin p)
    675 		   (format
    676 		    "Special property \"%s\" found in a properties drawer"
    677 		    key)))))))
    678 
    679 (defun org-lint-obsolete-properties-drawer (ast)
    680   (org-element-map ast 'drawer
    681     (lambda (d)
    682       (when (equal (org-element-property :drawer-name d) "PROPERTIES")
    683 	(let ((headline? (org-element-lineage d 'headline))
    684 	      (before
    685 	       (mapcar #'org-element-type
    686 		       (assq d (reverse (org-element-contents
    687 					 (org-element-parent d)))))))
    688 	  (list (org-element-post-affiliated d)
    689 		(if (or (and headline? (member before '(nil (planning))))
    690 			(and (null headline?) (member before '(nil (comment)))))
    691 		    "Incorrect contents for PROPERTIES drawer"
    692 		  "Incorrect location for PROPERTIES drawer")))))))
    693 
    694 (defun org-lint-invalid-effort-property (ast)
    695   (org-element-map ast 'node-property
    696     (lambda (p)
    697       (when (equal "EFFORT" (org-element-property :key p))
    698 	(let ((value (org-element-property :value p)))
    699 	  (and (org-string-nw-p value)
    700 	       (not (org-duration-p value))
    701 	       (list (org-element-begin p)
    702 		     (format "Invalid effort duration format: %S" value))))))))
    703 
    704 (defun org-lint-invalid-id-property (ast)
    705   (org-element-map ast 'node-property
    706     (lambda (p)
    707       (when (equal "ID" (org-element-property :key p))
    708 	(let ((value (org-element-property :value p)))
    709 	  (and (org-string-nw-p value)
    710                (string-match-p "::" value)
    711 	       (list (org-element-begin p)
    712 		     (format "IDs should not include \"::\": %S" value))))))))
    713 
    714 (defun org-lint-link-to-local-file (ast)
    715   (org-element-map ast 'link
    716     (lambda (l)
    717       (let ((type (org-element-property :type l)))
    718 	(pcase type
    719 	  ((or "attachment" "file")
    720 	   (let* ((path (org-element-property :path l))
    721 		  (file (if (string= type "file")
    722 			    path
    723                           (org-with-point-at (org-element-begin l)
    724 			    (org-attach-expand path)))))
    725              (setq file (substitute-env-in-file-name file))
    726 	     (and (not (file-remote-p file))
    727 		  (not (file-exists-p file))
    728 		  (list (org-element-begin l)
    729 			(format (if (org-element-lineage l 'link)
    730 				    "Link to non-existent image file %S \
    731 in description"
    732 				  "Link to non-existent local file %S")
    733                                 file)))))
    734 	  (_ nil))))))
    735 
    736 (defun org-lint-non-existent-setupfile-parameter (ast)
    737   (org-element-map ast 'keyword
    738     (lambda (k)
    739       (when (equal (org-element-property :key k) "SETUPFILE")
    740 	(let ((file (org-unbracket-string
    741 			"\"" "\""
    742 		      (org-element-property :value k))))
    743 	  (and (not (org-url-p file))
    744 	       (not (file-remote-p file))
    745 	       (not (file-exists-p file))
    746 	       (list (org-element-begin k)
    747 		     (format "Non-existent setup file %S" file))))))))
    748 
    749 (defun org-lint-wrong-include-link-parameter (ast)
    750   (org-element-map ast 'keyword
    751     (lambda (k)
    752       (when (equal (org-element-property :key k) "INCLUDE")
    753         (let* ((value (org-element-property :value k))
    754                (path
    755                 (and (string-match "^\\(\".+?\"\\|\\S-+\\)[ \t]*" value)
    756                      (save-match-data
    757                        (org-strip-quotes (match-string 1 value))))))
    758           (if (not path)
    759               (list (org-element-post-affiliated k)
    760                     "Missing location argument in INCLUDE keyword")
    761             (let* ((file (org-string-nw-p
    762                           (if (string-match "::\\(.*\\)\\'" path)
    763                               (substring path 0 (match-beginning 0))
    764                             path)))
    765                    (search (and (not (equal file path))
    766                                 (org-string-nw-p (match-string 1 path)))))
    767               (unless (org-url-p file)
    768                 (if (and file
    769                          (not (file-remote-p file))
    770                          (not (file-exists-p file)))
    771                     (list (org-element-post-affiliated k)
    772                           "Non-existent file argument in INCLUDE keyword")
    773                   (let* ((visiting (if file (find-buffer-visiting file)
    774                                      (current-buffer)))
    775                          (buffer (or visiting (find-file-noselect file)))
    776                          (org-link-search-must-match-exact-headline t))
    777                     (unwind-protect
    778                         (with-current-buffer buffer
    779                           (org-with-wide-buffer
    780                            (when (and search
    781                                       (not (ignore-errors
    782                                            (org-link-search search nil t))))
    783                              (list (org-element-post-affiliated k)
    784                                    (format
    785                                     "Invalid search part \"%s\" in INCLUDE keyword"
    786                                     search)))))
    787                       (unless visiting (kill-buffer buffer)))))))))))))
    788 
    789 (defun org-lint-obsolete-include-markup (ast)
    790   (let ((regexp (format "\\`\\(?:\".+\"\\|\\S-+\\)[ \t]+%s"
    791 			(regexp-opt
    792 			 '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
    793 			   "ODT" "ORG" "TEXINFO")
    794 			 t))))
    795     (org-element-map ast 'keyword
    796       (lambda (k)
    797 	(when (equal (org-element-property :key k) "INCLUDE")
    798 	  (let ((case-fold-search t)
    799 		(value (org-element-property :value k)))
    800 	    (when (string-match regexp value)
    801 	      (let ((markup (match-string-no-properties 1 value)))
    802 		(list (org-element-post-affiliated k)
    803 		      (format "Obsolete markup \"%s\" in INCLUDE keyword.  \
    804 Use \"export %s\" instead"
    805 			      markup
    806 			      markup))))))))))
    807 
    808 (defun org-lint-unknown-options-item (ast)
    809   (let ((allowed (delq nil
    810 		       (append
    811 			(mapcar (lambda (o) (nth 2 o)) org-export-options-alist)
    812 			(cl-mapcan
    813 			 (lambda (b)
    814 			   (mapcar (lambda (o) (nth 2 o))
    815 				   (org-export-backend-options b)))
    816 			 org-export-registered-backends))))
    817 	reports)
    818     (org-element-map ast 'keyword
    819       (lambda (k)
    820 	(when (string= (org-element-property :key k) "OPTIONS")
    821 	  (let ((value (org-element-property :value k))
    822 		(start 0))
    823 	    (while (string-match "\\(.+?\\):\\((.*?)\\|\\S-+\\)?[ \t]*"
    824 				 value
    825 				 start)
    826 	      (setf start (match-end 0))
    827 	      (let ((item (match-string 1 value)))
    828 		(unless (member item allowed)
    829 		  (push (list (org-element-post-affiliated k)
    830 			      (format "Unknown OPTIONS item \"%s\"" item))
    831 			reports))
    832                 (unless (match-string 2 value)
    833                   (push (list (org-element-post-affiliated k)
    834                               (format "Missing value for option item %S" item))
    835                         reports))))))))
    836     reports))
    837 
    838 (defun org-lint-export-option-keywords (ast)
    839   "Check for options keyword properties without EXPORT in AST."
    840   (require 'ox)
    841   (let (options reports common-options options-alist)
    842     (dolist (opt org-export-options-alist)
    843       (when (stringp (nth 1 opt))
    844         (cl-pushnew (nth 1 opt) common-options :test #'equal)))
    845     (dolist (backend org-export-registered-backends)
    846       (dolist (opt (org-export-backend-options backend))
    847         (when (stringp (nth 1 opt))
    848           (cl-pushnew (or (org-export-backend-name backend) 'anonymous)
    849                       (alist-get (nth 1 opt) options-alist nil nil #'equal))
    850 	  (cl-pushnew (nth 1 opt) options :test #'equal))))
    851     (setq options-alist (nreverse options-alist))
    852     (org-element-map ast 'node-property
    853       (lambda (node)
    854         (let ((prop (org-element-property :key node)))
    855           (when (and (or (member prop options) (member prop common-options))
    856                      (not (member prop org-default-properties)))
    857             (push (list (org-element-post-affiliated node)
    858                         (format "Potentially misspelled %sexport option \"%s\"%s.  Consider \"EXPORT_%s\"."
    859                                 (when (member prop common-options)
    860                                   "global ")
    861                                 prop
    862                                 (if-let* ((backends
    863                                            (and (not (member prop common-options))
    864                                                 (cdr (assoc-string prop options-alist)))))
    865                                     (format
    866                                      " in %S export %s"
    867                                      (if (= 1 (length backends)) (car backends) backends)
    868                                      (if (> (length backends) 1) "backends" "backend"))
    869                                   "")
    870                                 prop))
    871                   reports)))))
    872     reports))
    873 
    874 (defun org-lint-invalid-macro-argument-and-template (ast)
    875   (let* ((reports nil)
    876          (extract-placeholders
    877 	  (lambda (template)
    878 	    (let ((start 0)
    879 		  args)
    880 	      (while (string-match "\\$\\([1-9][0-9]*\\)" template start)
    881 	        (setf start (match-end 0))
    882 	        (push (string-to-number (match-string 1 template)) args))
    883 	      (sort (org-uniquify args) #'<))))
    884          (check-arity
    885           (lambda (arity macro)
    886             (let* ((name (org-element-property :key macro))
    887                    (pos (org-element-begin macro))
    888                    (args (org-element-property :args macro))
    889                    (l (length args)))
    890               (cond
    891                ((< l (1- (car arity)))
    892                 (push (list pos (format "Missing arguments in macro %S" name))
    893                       reports))
    894                ((< l (car arity))
    895                 (push (list pos (format "Missing argument in macro %S" name))
    896                       reports))
    897                ((> l (1+ (cdr arity)))
    898                 (push (let ((spurious-args (nthcdr (cdr arity) args)))
    899                         (list pos
    900                               (format "Spurious arguments in macro %S: %s"
    901                                       name
    902                                       (mapconcat #'org-trim spurious-args ", "))))
    903                       reports))
    904                ((> l (cdr arity))
    905                 (push (list pos
    906                             (format "Spurious argument in macro %S: %s"
    907                                     name
    908                                     (org-last args)))
    909                       reports))
    910                (t nil))))))
    911     ;; Check arguments for macro templates.
    912     (org-element-map ast 'keyword
    913       (lambda (k)
    914 	(when (string= (org-element-property :key k) "MACRO")
    915 	  (let* ((value (org-element-property :value k))
    916 		 (name (and (string-match "^\\S-+" value)
    917 			    (match-string 0 value)))
    918 		 (template (and name
    919 				(org-trim (substring value (match-end 0))))))
    920 	    (cond
    921 	     ((not name)
    922 	      (push (list (org-element-post-affiliated k)
    923 			  "Missing name in MACRO keyword")
    924 		    reports))
    925 	     ((not (org-string-nw-p template))
    926 	      (push (list (org-element-post-affiliated k)
    927 			  "Missing template in macro \"%s\"" name)
    928 		    reports))
    929 	     (t
    930 	      (unless (let ((args (funcall extract-placeholders template)))
    931 			(equal (number-sequence 1 (or (org-last args) 0)) args))
    932 		(push (list (org-element-post-affiliated k)
    933 			    (format "Unused placeholders in macro \"%s\""
    934 				    name))
    935 		      reports))))))))
    936     ;; Check arguments for macros.
    937     (org-macro-initialize-templates)
    938     (let ((templates (append
    939 		      (mapcar (lambda (m) (cons m "$1"))
    940 			      '("author" "date" "email" "title" "results"))
    941 		      org-macro-templates)))
    942       (org-element-map ast 'macro
    943 	(lambda (macro)
    944 	  (let* ((name (org-element-property :key macro))
    945 		 (template (cdr (assoc-string name templates t))))
    946             (pcase template
    947               (`nil
    948                (push (list (org-element-begin macro)
    949 			   (format "Undefined macro %S" name))
    950 		     reports))
    951               ((guard (string= name "keyword"))
    952                (funcall check-arity '(1 . 1) macro))
    953               ((guard (string= name "modification-time"))
    954                (funcall check-arity '(1 . 2) macro))
    955               ((guard (string= name "n"))
    956                (funcall check-arity '(0 . 2) macro))
    957               ((guard (string= name "property"))
    958                (funcall check-arity '(1 . 2) macro))
    959               ((guard (string= name "time"))
    960                (funcall check-arity '(1 . 1) macro))
    961               ((pred functionp))        ;ignore (eval ...) templates
    962               (_
    963                (let* ((arg-numbers (funcall extract-placeholders template))
    964                       (arity (if (null arg-numbers)
    965                                  '(0 . 0)
    966                                (let ((m (apply #'max arg-numbers)))
    967                                  (cons m m)))))
    968                  (funcall check-arity arity macro))))))))
    969     reports))
    970 
    971 (defun org-lint-undefined-footnote-reference (ast)
    972   (let ((definitions
    973          (org-element-map ast '(footnote-definition footnote-reference)
    974 	   (lambda (f)
    975              (and (or (org-element-type-p f 'footnote-definition)
    976                       (eq 'inline (org-element-property :type f)))
    977                   (org-element-property :label f))))))
    978     (org-element-map ast 'footnote-reference
    979       (lambda (f)
    980 	(let ((label (org-element-property :label f)))
    981 	  (and (eq 'standard (org-element-property :type f))
    982 	       (not (member label definitions))
    983 	       (list (org-element-begin f)
    984 		     (format "Missing definition for footnote [%s]"
    985 			     label))))))))
    986 
    987 (defun org-lint-unreferenced-footnote-definition (ast)
    988   (let ((references (org-element-map ast 'footnote-reference
    989 		      (lambda (f) (org-element-property :label f)))))
    990     (org-element-map ast 'footnote-definition
    991       (lambda (f)
    992 	(let ((label (org-element-property :label f)))
    993 	  (and label
    994 	       (not (member label references))
    995 	       (list (org-element-post-affiliated f)
    996 		     (format "No reference for footnote definition [%s]"
    997 			     label))))))))
    998 
    999 (defun org-lint-mismatched-planning-repeaters (ast)
   1000   (org-element-map ast 'planning
   1001     (lambda (e)
   1002       (let* ((scheduled (org-element-property :scheduled e))
   1003              (deadline (org-element-property :deadline e))
   1004              (scheduled-repeater-type (org-element-property
   1005                                        :repeater-type scheduled))
   1006              (deadline-repeater-type (org-element-property
   1007                                       :repeater-type deadline))
   1008              (scheduled-repeater-value (org-element-property
   1009                                         :repeater-value scheduled))
   1010              (deadline-repeater-value (org-element-property
   1011                                        :repeater-value deadline)))
   1012         (when (and scheduled deadline
   1013                    (memq scheduled-repeater-type '(cumulate catch-up))
   1014                    (memq deadline-repeater-type '(cumulate catch-up))
   1015                    (> scheduled-repeater-value 0)
   1016                    (> deadline-repeater-value 0)
   1017                    (not
   1018                     (and
   1019                      (eq scheduled-repeater-type deadline-repeater-type)
   1020                      (eq (org-element-property :repeater-unit scheduled)
   1021                          (org-element-property :repeater-unit deadline))
   1022                      (eql scheduled-repeater-value deadline-repeater-value))))
   1023           (list
   1024            (org-element-property :begin e)
   1025            "Different repeaters in SCHEDULED and DEADLINE timestamps."))))))
   1026 
   1027 (defun org-lint-misplaced-planning-info (_)
   1028   (let ((case-fold-search t)
   1029 	reports)
   1030     (while (re-search-forward org-planning-line-re nil t)
   1031       (unless (org-element-type-p
   1032                (org-element-at-point)
   1033 	       '(comment-block example-block export-block planning
   1034 			       src-block verse-block))
   1035 	(push (list (line-beginning-position) "Misplaced planning info line")
   1036 	      reports)))
   1037     reports))
   1038 
   1039 (defun org-lint-incomplete-drawer (_)
   1040   (let (reports)
   1041     (while (re-search-forward org-drawer-regexp nil t)
   1042       (let ((name (org-trim (match-string-no-properties 0)))
   1043 	    (element (org-element-at-point)))
   1044 	(pcase (org-element-type element)
   1045 	  (`drawer
   1046 	   ;; Find drawer opening lines within non-empty drawers.
   1047 	   (let ((end (org-element-contents-end element)))
   1048 	     (when end
   1049 	       (while (re-search-forward org-drawer-regexp end t)
   1050 		 (let ((n (org-trim (match-string-no-properties 0))))
   1051 		   (push (list (line-beginning-position)
   1052 			       (format "Possible misleading drawer entry %S" n))
   1053 			 reports))))
   1054 	     (goto-char (org-element-end element))))
   1055 	  (`property-drawer
   1056 	   (goto-char (org-element-end element)))
   1057 	  ((or `comment-block `example-block `export-block `src-block
   1058 	       `verse-block)
   1059 	   nil)
   1060 	  (_
   1061 	   ;; Find drawer opening lines outside of any drawer.
   1062 	   (push (list (line-beginning-position)
   1063 		       (format "Possible incomplete drawer %S" name))
   1064 		 reports)))))
   1065     reports))
   1066 
   1067 (defun org-lint-indented-diary-sexp (_)
   1068   (let (reports)
   1069     (while (re-search-forward "^[ \t]+%%(" nil t)
   1070       (unless (org-element-type-p
   1071                (org-element-at-point)
   1072 	       '(comment-block diary-sexp example-block export-block
   1073 			       src-block verse-block))
   1074 	(push (list (line-beginning-position) "Possible indented diary-sexp")
   1075 	      reports)))
   1076     reports))
   1077 
   1078 (defun org-lint-invalid-block (_)
   1079   (let ((case-fold-search t)
   1080 	(regexp "^[ \t]*#\\+\\(BEGIN\\|END\\)\\(?::\\|_[^[:space:]]*\\)?[ \t]*")
   1081 	reports)
   1082     (while (re-search-forward regexp nil t)
   1083       (let ((name (org-trim (buffer-substring-no-properties
   1084 			     (line-beginning-position) (line-end-position)))))
   1085 	(cond
   1086 	 ((and (string-prefix-p "END" (match-string 1) t)
   1087 	       (not (eolp)))
   1088 	  (push (list (line-beginning-position)
   1089 		      (format "Invalid block closing line \"%s\"" name))
   1090 		reports))
   1091 	 ((not (org-element-type-p
   1092               (org-element-at-point)
   1093 	      '(center-block comment-block dynamic-block example-block
   1094 			     export-block quote-block special-block
   1095 			     src-block verse-block)))
   1096 	  (push (list (line-beginning-position)
   1097 		      (format "Possible incomplete block \"%s\""
   1098 			      name))
   1099 		reports)))))
   1100     reports))
   1101 
   1102 (defun org-lint-invalid-keyword-syntax (_)
   1103   (let ((regexp "^[ \t]*#\\+\\([^[:space:]:]*\\)\\(?: \\|$\\)")
   1104 	(exception-re
   1105 	 (format "[ \t]*#\\+%s\\(\\[.*\\]\\)?:\\(?: \\|$\\)"
   1106 		 (regexp-opt org-element-dual-keywords)))
   1107 	reports)
   1108     (while (re-search-forward regexp nil t)
   1109       (let ((name (match-string-no-properties 1)))
   1110 	(unless (or (string-prefix-p "BEGIN" name t)
   1111 		    (string-prefix-p "END" name t)
   1112 		    (save-excursion
   1113 		      (forward-line 0)
   1114 		      (let ((case-fold-search t)) (looking-at exception-re))))
   1115 	  (push (list (match-beginning 0)
   1116 		      (format "Possible missing colon in keyword \"%s\"" name))
   1117 		reports))))
   1118     reports))
   1119 
   1120 (defun org-lint-invalid-image-alignment (ast)
   1121   (apply
   1122    #'nconc
   1123    (org-element-map ast 'paragraph
   1124      (lambda (p)
   1125        (let ((center-re ":center[[:space:]]+\\(\\S-+\\)")
   1126              (align-re ":align[[:space:]]+\\(\\S-+\\)")
   1127              (keyword-string
   1128               (car-safe (org-element-property :attr_org p)))
   1129              reports)
   1130          (when keyword-string
   1131            (when (and (string-match align-re keyword-string)
   1132                       (not (member (match-string 1 keyword-string)
   1133                                    '("left" "center" "right"))))
   1134              (push
   1135               (list (org-element-begin p)
   1136                     (format
   1137                      "\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":align\"."
   1138                      (match-string 1 keyword-string)))
   1139               reports))
   1140            (when (and (string-match center-re keyword-string)
   1141                       (not (equal (match-string 1 keyword-string) "t")))
   1142              (push
   1143               (list (org-element-begin p)
   1144                     (format
   1145                      "\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":center\"."
   1146                      (match-string 1 keyword-string)))
   1147               reports)))
   1148          reports)))))
   1149 
   1150 (defun org-lint-extraneous-element-in-footnote-section (ast)
   1151   (org-element-map ast 'headline
   1152     (lambda (h)
   1153       (and (org-element-property :footnote-section-p h)
   1154 	   (org-element-map (org-element-contents h)
   1155 	       (cl-remove-if
   1156 		(lambda (e)
   1157 		  (memq e '(comment comment-block footnote-definition
   1158 				    property-drawer section)))
   1159 		org-element-all-elements)
   1160 	     (lambda (e)
   1161 	       (not (and (org-element-type-p e 'headline)
   1162 		       (org-element-property :commentedp e))))
   1163 	     nil t '(footnote-definition property-drawer))
   1164 	   (list (org-element-begin h)
   1165 		 "Extraneous elements in footnote section are not exported")))))
   1166 
   1167 (defun org-lint-quote-section (ast)
   1168   (org-element-map ast '(headline inlinetask)
   1169     (lambda (h)
   1170       (let ((title (org-element-property :raw-value h)))
   1171 	(and (or (string-prefix-p "QUOTE " title)
   1172 		 (string-prefix-p (concat org-comment-string " QUOTE ") title))
   1173 	     (list (org-element-begin h)
   1174 		   "Deprecated QUOTE section"))))))
   1175 
   1176 (defun org-lint-file-application (ast)
   1177   (org-element-map ast 'link
   1178     (lambda (l)
   1179       (let ((app (org-element-property :application l)))
   1180 	(and app
   1181 	     (list (org-element-begin l)
   1182 		   (format "Deprecated \"file+%s\" link type" app)))))))
   1183 
   1184 (defun org-lint-percent-encoding-link-escape (ast)
   1185   (org-element-map ast 'link
   1186     (lambda (l)
   1187       (when (eq 'bracket (org-element-property :format l))
   1188 	(let* ((uri (org-element-property :path l))
   1189 	       (start 0)
   1190 	       (obsolete-flag
   1191 		(catch :obsolete
   1192 		  (while (string-match "%\\(..\\)?" uri start)
   1193 		    (setq start (match-end 0))
   1194 		    (unless (member (match-string 1 uri) '("25" "5B" "5D" "20"))
   1195 		      (throw :obsolete nil)))
   1196 		  (string-match-p "%" uri))))
   1197 	  (when obsolete-flag
   1198 	    (list (org-element-begin l)
   1199 		  "Link escaped with obsolete percent-encoding syntax")))))))
   1200 
   1201 (defun org-lint-wrong-header-argument (ast)
   1202   (let* ((reports)
   1203 	 (verify
   1204 	  (lambda (datum language headers)
   1205 	    (let ((allowed
   1206 		   ;; If LANGUAGE is specified, restrict allowed
   1207 		   ;; headers to both LANGUAGE-specific and default
   1208 		   ;; ones.  Otherwise, accept headers from any loaded
   1209 		   ;; language.
   1210 		   (append
   1211 		    org-babel-header-arg-names
   1212 		    (cl-mapcan
   1213 		     (lambda (l)
   1214 		       (let ((v (intern (format "org-babel-header-args:%s" l))))
   1215 			 (and (boundp v) (mapcar #'car (symbol-value v)))))
   1216 		     (if language (list language)
   1217 		       (mapcar #'car org-babel-load-languages))))))
   1218 	      (dolist (header headers)
   1219 		(let ((h (symbol-name (car header)))
   1220 		      (p (or (org-element-post-affiliated datum)
   1221 			     (org-element-begin datum))))
   1222 		  (cond
   1223 		   ((not (string-prefix-p ":" h))
   1224 		    (push
   1225 		     (list p
   1226 			   (format "Missing colon in header argument \"%s\"" h))
   1227 		     reports))
   1228 		   ((assoc-string (substring h 1) allowed))
   1229 		   (t (push (list p (format "Unknown header argument \"%s\"" h))
   1230 			    reports)))))))))
   1231     (org-element-map ast '(babel-call inline-babel-call inline-src-block keyword
   1232 				      node-property src-block)
   1233       (lambda (datum)
   1234 	(pcase (org-element-type datum)
   1235 	  ((or `babel-call `inline-babel-call)
   1236 	   (funcall verify
   1237 		    datum
   1238 		    nil
   1239 		    (cl-mapcan #'org-babel-parse-header-arguments
   1240 			       (list
   1241 				(org-element-property :inside-header datum)
   1242 				(org-element-property :end-header datum)))))
   1243 	  (`inline-src-block
   1244 	   (funcall verify
   1245 		    datum
   1246 		    (org-element-property :language datum)
   1247 		    (org-babel-parse-header-arguments
   1248 		     (org-element-property :parameters datum))))
   1249 	  (`keyword
   1250 	   (when (string= (org-element-property :key datum) "PROPERTY")
   1251 	     (let ((value (org-element-property :value datum)))
   1252 	       (when (or (string-match "\\`header-args\\(?::\\(\\S-+\\)\\)?\\+ *"
   1253 				       value)
   1254                          (string-match "\\`header-args\\(?::\\(\\S-+\\)\\)? *"
   1255 				       value))
   1256 		 (funcall verify
   1257 			  datum
   1258 			  (match-string 1 value)
   1259 			  (org-babel-parse-header-arguments
   1260 			   (substring value (match-end 0))))))))
   1261 	  (`node-property
   1262 	   (let ((key (org-element-property :key datum)))
   1263 	     (when (let ((case-fold-search t))
   1264 		     (or (string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?\\+"
   1265 				       key)
   1266                          (string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?"
   1267 				       key)))
   1268 	       (funcall verify
   1269 			datum
   1270 			(match-string 1 key)
   1271 			(org-babel-parse-header-arguments
   1272 			 (org-element-property :value datum))))))
   1273 	  (`src-block
   1274 	   (funcall verify
   1275 		    datum
   1276 		    (org-element-property :language datum)
   1277 		    (cl-mapcan #'org-babel-parse-header-arguments
   1278 			       (cons (org-element-property :parameters datum)
   1279 				     (org-element-property :header datum))))))))
   1280     reports))
   1281 
   1282 (defun org-lint-empty-header-argument (ast)
   1283   (let* (reports)
   1284     (org-element-map ast '(babel-call inline-babel-call inline-src-block src-block)
   1285       (lambda (datum)
   1286         (let ((headers
   1287 	       (pcase (org-element-type datum)
   1288 	         ((or `babel-call `inline-babel-call)
   1289 		  (cl-mapcan
   1290                    (lambda (header) (org-babel-parse-header-arguments header 'no-eval))
   1291 		   (list
   1292 		    (org-element-property :inside-header datum)
   1293 		    (org-element-property :end-header datum))))
   1294 	         (`inline-src-block
   1295 		  (org-babel-parse-header-arguments
   1296 		   (org-element-property :parameters datum)
   1297                    'no-eval))
   1298 	         (`src-block
   1299 		  (cl-mapcan
   1300                    (lambda (header) (org-babel-parse-header-arguments header 'no-eval))
   1301 		   (cons (org-element-property :parameters datum)
   1302 			 (org-element-property :header datum)))))))
   1303           (dolist (header headers)
   1304             (when (not (cdr header))
   1305               (push
   1306 	       (list
   1307                 (or (org-element-post-affiliated datum)
   1308 		    (org-element-begin datum))
   1309 		(format "Empty value in header argument \"%s\"" (symbol-name (car header))))
   1310 	       reports))))))
   1311     reports))
   1312 
   1313 (defun org-lint-wrong-header-value (ast)
   1314   (let (reports)
   1315     (org-element-map ast
   1316 	'(babel-call inline-babel-call inline-src-block src-block)
   1317       (lambda (datum)
   1318 	(let* ((type (org-element-type datum))
   1319 	       (language (org-element-property :language datum))
   1320 	       (allowed-header-values
   1321 		(append (and language
   1322 			     (let ((v (intern (concat "org-babel-header-args:"
   1323 						      language))))
   1324 			       (and (boundp v) (symbol-value v))))
   1325 			org-babel-common-header-args-w-values))
   1326 	       (datum-header-values
   1327 		(org-babel-parse-header-arguments
   1328 		 (org-trim
   1329 		  (pcase type
   1330 		    (`src-block
   1331 		     (mapconcat
   1332 		      #'identity
   1333 		      (cons (org-element-property :parameters datum)
   1334 			    (org-element-property :header datum))
   1335 		      " "))
   1336 		    (`inline-src-block
   1337 		     (or (org-element-property :parameters datum) ""))
   1338 		    (_
   1339 		     (concat
   1340 		      (org-element-property :inside-header datum)
   1341 		      " "
   1342 		      (org-element-property :end-header datum))))))))
   1343 	  (dolist (header datum-header-values)
   1344 	    (let ((allowed-values
   1345 		   (cdr (assoc-string (substring (symbol-name (car header)) 1)
   1346 				      allowed-header-values))))
   1347 	      (unless (memq allowed-values '(:any nil))
   1348 		(let ((values (cdr header))
   1349 		      groups-alist)
   1350 		  (dolist (v (if (stringp values) (split-string values)
   1351 			       (list values)))
   1352 		    (let ((valid-value nil))
   1353 		      (catch 'exit
   1354 			(dolist (group allowed-values)
   1355 			  (cond
   1356 			   ((not (funcall
   1357 				  (if (stringp v) #'assoc-string #'assoc)
   1358 				  v group))
   1359 			    (when (memq :any group)
   1360 			      (setf valid-value t)
   1361 			      (push (cons group v) groups-alist)))
   1362 			   ((assq group groups-alist)
   1363 			    (push
   1364 			     (list
   1365 			      (or (org-element-post-affiliated datum)
   1366 				  (org-element-begin datum))
   1367 			      (format
   1368 			       "Forbidden combination in header \"%s\": %s, %s"
   1369 			       (car header)
   1370 			       (cdr (assq group groups-alist))
   1371 			       v))
   1372 			     reports)
   1373 			    (throw 'exit nil))
   1374 			   (t (push (cons group v) groups-alist)
   1375 			      (setf valid-value t))))
   1376 			(unless valid-value
   1377 			  (push
   1378 			   (list
   1379 			    (or (org-element-post-affiliated datum)
   1380 				(org-element-begin datum))
   1381 			    (format "Unknown value \"%s\" for header \"%s\""
   1382 				    v
   1383 				    (car header)))
   1384 			   reports))))))))))))
   1385     reports))
   1386 
   1387 (defun org-lint-named-result (ast)
   1388   (org-element-map ast org-element-all-elements
   1389     (lambda (el)
   1390       (when-let* ((result (org-element-property :results el))
   1391                   (result-name (org-element-property :name el))
   1392                   (origin-block
   1393                    (if (org-string-nw-p (car result))
   1394                        (condition-case _
   1395                            (org-export-resolve-link (car result) `(:parse-tree ,ast))
   1396                          (org-link-broken nil))
   1397                      (org-export-get-previous-element el nil))))
   1398         (when (org-element-type-p origin-block 'src-block)
   1399           (list (org-element-begin el)
   1400                 (format "Links to \"%s\" will not be valid during export unless the parent source block has :exports results or both" result-name)))))))
   1401 
   1402 (defun org-lint-spurious-colons (ast)
   1403   (org-element-map ast '(headline inlinetask)
   1404     (lambda (h)
   1405       (when (member "" (org-element-property :tags h))
   1406 	(list (org-element-begin h)
   1407 	      "Tags contain a spurious colon")))))
   1408 
   1409 (defun org-lint-non-existent-bibliography (ast)
   1410   (org-element-map ast 'keyword
   1411     (lambda (k)
   1412       (when (equal "BIBLIOGRAPHY" (org-element-property :key k))
   1413         (let ((file (org-strip-quotes (org-element-property :value k))))
   1414           (and (not (file-remote-p file))
   1415 	       (not (file-exists-p file))
   1416 	       (list (org-element-begin k)
   1417 		     (format "Non-existent bibliography %S" file))))))))
   1418 
   1419 (defun org-lint-missing-print-bibliography (ast)
   1420   (and (org-element-map ast 'citation #'identity nil t)
   1421        (not (org-element-map ast 'keyword
   1422               (lambda (k)
   1423                 (equal "PRINT_BIBLIOGRAPHY" (org-element-property :key k)))
   1424               nil t))
   1425        (list
   1426         (list (point-max) "Possibly missing \"PRINT_BIBLIOGRAPHY\" keyword"))))
   1427 
   1428 (defun org-lint-invalid-cite-export-declaration (ast)
   1429   (org-element-map ast 'keyword
   1430     (lambda (k)
   1431       (when (equal "CITE_EXPORT" (org-element-property :key k))
   1432         (let ((value (org-element-property :value k))
   1433               (source (org-element-begin k)))
   1434           (if (equal value "")
   1435               (list source "Missing export processor name")
   1436             (condition-case _
   1437                 (pcase (org-cite-read-processor-declaration value)
   1438                   (`(,(and (pred symbolp) name)
   1439                      ,(pred string-or-null-p)
   1440                      ,(pred string-or-null-p))
   1441                    (unless (or (org-cite-get-processor name)
   1442                                (progn
   1443                                  (org-cite-try-load-processor name)
   1444                                  (org-cite-get-processor name)))
   1445                      (list source (format "Unknown cite export processor %S" name))))
   1446                   (_
   1447                    (list source "Invalid cite export processor declaration")))
   1448               (error
   1449                (list source "Invalid cite export processor declaration")))))))))
   1450 
   1451 (defun org-lint-incomplete-citation (ast)
   1452   (org-element-map ast 'plain-text
   1453     (lambda (text)
   1454       (and (string-match-p org-element-citation-prefix-re text)
   1455            ;; XXX: The code below signals the error at the beginning
   1456            ;; of the paragraph containing the faulty object.  It is
   1457            ;; not very accurate but may be enough for now.
   1458            (list (org-element-contents-begin
   1459                                        (org-element-parent text))
   1460                  "Possibly incomplete citation markup")))))
   1461 
   1462 (defun org-lint-item-number (ast)
   1463   (org-element-map ast 'item
   1464     (lambda (item)
   1465       (unless (org-element-property :counter item)
   1466         (when-let* ((bullet (org-element-property :bullet item))
   1467                     (bullet-number
   1468                      (cond
   1469                       ((string-match "[A-Za-z]" bullet)
   1470 		       (- (string-to-char (upcase (match-string 0 bullet)))
   1471 			  64))
   1472                       ((string-match "[0-9]+" bullet)
   1473 		       (string-to-number (match-string 0 bullet)))))
   1474                     (true-number
   1475                      (org-list-get-item-number
   1476                       (org-element-begin item)
   1477                       (org-element-property :structure item)
   1478                       (org-list-prevs-alist (org-element-property :structure item))
   1479                       (org-list-parents-alist (org-element-property :structure item)))))
   1480           (unless (equal bullet-number (car (last true-number)))
   1481             (list
   1482              (org-element-begin item)
   1483              (format "Bullet counter \"%s\" is not the same with item position %d.  Consider adding manual [@%d] counter."
   1484                      bullet (car (last true-number)) bullet-number))))))))
   1485 
   1486 (defun org-lint-LaTeX-$ (ast)
   1487   "Report semi-obsolete $...$ LaTeX fragments.
   1488 AST is the buffer parse tree."
   1489   (org-element-map ast 'latex-fragment
   1490     (lambda (fragment)
   1491       (and (string-match-p "^[$][^$]" (org-element-property :value fragment))
   1492            (list (org-element-begin fragment)
   1493                  "Potentially confusing LaTeX fragment format.  Prefer using more reliable \\(...\\)")))))
   1494 (defun org-lint-LaTeX-$-ambiguous (_)
   1495   "Report LaTeX fragment-like text.
   1496 AST is the buffer parse tree."
   1497   (org-with-wide-buffer
   1498    (let ((ambiguous-latex-re (rx "$." digit))
   1499          report context)
   1500      (while (re-search-forward ambiguous-latex-re nil t)
   1501        (setq context (org-element-context))
   1502        (when (or (eq 'latex-fragment (org-element-type context))
   1503                  (memq 'latex-fragment (org-element-restriction context)))
   1504          (push
   1505           (list
   1506            (point)
   1507            "$ symbol potentially matching LaTeX fragment boundary.  Consider using \\dollar entity.")
   1508           report)))
   1509      report)))
   1510 (defun org-lint-timestamp-syntax (ast)
   1511   "Report malformed timestamps.
   1512 AST is the buffer parse tree."
   1513   (org-element-map ast 'timestamp
   1514     (lambda (timestamp)
   1515       (let ((expected (org-element-interpret-data timestamp))
   1516             (actual (buffer-substring-no-properties
   1517                      (org-element-property :begin timestamp)
   1518                      (org-element-property :end timestamp))))
   1519         (unless (equal expected actual)
   1520           (list (org-element-property :begin timestamp)
   1521                 (format "Potentially malformed timestamp %s.  Parsed as: %s" actual expected)))))))
   1522 (defun org-lint-inactive-planning (ast)
   1523   "Report inactive timestamp in SCHEDULED/DEADLINE.
   1524 AST is the buffer parse tree."
   1525   (org-element-map ast 'planning
   1526     (lambda (planning)
   1527       (let ((scheduled (org-element-property :scheduled planning))
   1528             (deadline (org-element-property :deadline planning)))
   1529         (cond
   1530          ((memq (org-element-property :type scheduled) '(inactive inactive-range))
   1531           (list (org-element-begin planning) "Inactive timestamp in SCHEDULED will not appear in agenda."))
   1532          ((memq (org-element-property :type deadline) '(inactive inactive-range))
   1533           (list (org-element-begin planning) "Inactive timestamp in DEADLINE will not appear in agenda."))
   1534          (t nil))))))
   1535 
   1536 (defvar org-beamer-frame-environment) ; defined in ox-beamer.el
   1537 (defun org-lint-beamer-frame (ast)
   1538   "Check for occurrences of begin or end frame."
   1539   (require 'ox-beamer)
   1540   (org-with-point-at ast
   1541     (goto-char (point-min))
   1542     (let (result)
   1543       (while (re-search-forward
   1544               (concat "\\\\\\(begin\\|end\\){" org-beamer-frame-environment "}") nil t)
   1545         (push (list (match-beginning 0) "Beamer frame name may cause error when exporting.  Consider customizing `org-beamer-frame-environment'.") result))
   1546       result)))
   1547 
   1548 
   1549 ;;; Checkers declaration
   1550 
   1551 (org-lint-add-checker 'misplaced-heading
   1552   "Report accidentally misplaced heading lines."
   1553   #'org-lint-misplaced-heading :trust 'low)
   1554 
   1555 (org-lint-add-checker 'duplicate-custom-id
   1556   "Report duplicates CUSTOM_ID properties"
   1557   #'org-lint-duplicate-custom-id
   1558   :categories '(link))
   1559 
   1560 (org-lint-add-checker 'duplicate-name
   1561   "Report duplicate NAME values"
   1562   #'org-lint-duplicate-name
   1563   :categories '(babel 'link))
   1564 
   1565 (org-lint-add-checker 'duplicate-target
   1566   "Report duplicate targets"
   1567   #'org-lint-duplicate-target
   1568   :categories '(link))
   1569 
   1570 (org-lint-add-checker 'duplicate-footnote-definition
   1571   "Report duplicate footnote definitions"
   1572   #'org-lint-duplicate-footnote-definition
   1573   :categories '(footnote))
   1574 
   1575 (org-lint-add-checker 'orphaned-affiliated-keywords
   1576   "Report orphaned affiliated keywords"
   1577   #'org-lint-orphaned-affiliated-keywords
   1578   :trust 'low)
   1579 
   1580 (org-lint-add-checker 'combining-keywords-with-affiliated
   1581   "Report independent keywords preceding affiliated keywords."
   1582   #'org-lint-regular-keyword-before-affiliated
   1583   :trust 'low)
   1584 
   1585 (org-lint-add-checker 'obsolete-affiliated-keywords
   1586   "Report obsolete affiliated keywords"
   1587   #'org-lint-obsolete-affiliated-keywords
   1588   :categories '(obsolete))
   1589 
   1590 (org-lint-add-checker 'deprecated-export-blocks
   1591   "Report deprecated export block syntax"
   1592   #'org-lint-deprecated-export-blocks
   1593   :trust 'low :categories '(obsolete export))
   1594 
   1595 (org-lint-add-checker 'deprecated-header-syntax
   1596   "Report deprecated Babel header syntax"
   1597   #'org-lint-deprecated-header-syntax
   1598   :trust 'low :categories '(obsolete babel))
   1599 
   1600 (org-lint-add-checker 'missing-language-in-src-block
   1601   "Report missing language in source blocks"
   1602   #'org-lint-missing-language-in-src-block
   1603   :categories '(babel))
   1604 
   1605 (org-lint-add-checker 'suspicious-language-in-src-block
   1606   "Report suspicious language in source blocks"
   1607   #'org-lint-suspicious-language-in-src-block
   1608   :trust 'low :categories '(babel))
   1609 
   1610 (org-lint-add-checker 'missing-backend-in-export-block
   1611   "Report missing backend in export blocks"
   1612   #'org-lint-missing-backend-in-export-block
   1613   :categories '(export))
   1614 
   1615 (org-lint-add-checker 'invalid-babel-call-block
   1616   "Report invalid Babel call blocks"
   1617   #'org-lint-invalid-babel-call-block
   1618   :categories '(babel))
   1619 
   1620 (org-lint-add-checker 'wrong-header-argument
   1621   "Report wrong babel headers"
   1622   #'org-lint-wrong-header-argument
   1623   :categories '(babel))
   1624 
   1625 (org-lint-add-checker 'wrong-header-value
   1626   "Report invalid value in babel headers"
   1627   #'org-lint-wrong-header-value
   1628   :categories '(babel) :trust 'low)
   1629 
   1630 (org-lint-add-checker 'named-result
   1631   "Report results evaluation with #+name keyword."
   1632   #'org-lint-named-result
   1633   :categories '(babel) :trust 'high)
   1634 
   1635 (org-lint-add-checker 'empty-header-argument
   1636   "Report empty values in babel headers"
   1637   #'org-lint-empty-header-argument
   1638   :categories '(babel) :trust 'low)
   1639 
   1640 (org-lint-add-checker 'deprecated-category-setup
   1641   "Report misuse of CATEGORY keyword"
   1642   #'org-lint-deprecated-category-setup
   1643   :categories '(obsolete))
   1644 
   1645 (org-lint-add-checker 'invalid-coderef-link
   1646   "Report \"coderef\" links with unknown destination"
   1647   #'org-lint-invalid-coderef-link
   1648   :categories '(link))
   1649 
   1650 (org-lint-add-checker 'invalid-custom-id-link
   1651   "Report \"custom-id\" links with unknown destination"
   1652   #'org-lint-invalid-custom-id-link
   1653   :categories '(link))
   1654 
   1655 (org-lint-add-checker 'invalid-fuzzy-link
   1656   "Report \"fuzzy\" links with unknown destination"
   1657   #'org-lint-invalid-fuzzy-link
   1658   :categories '(link))
   1659 
   1660 (org-lint-add-checker 'invalid-id-link
   1661   "Report \"id\" links with unknown destination"
   1662   #'org-lint-invalid-id-link
   1663   :categories '(link))
   1664 
   1665 (org-lint-add-checker 'trailing-bracket-after-link
   1666   "Report potentially confused trailing ']' after link."
   1667   #'org-lint-confusing-brackets
   1668   :categories '(link) :trust 'low)
   1669 
   1670 (org-lint-add-checker 'unclosed-brackets-in-link-description
   1671   "Report potentially confused trailing ']' after link."
   1672   #'org-lint-brackets-inside-description
   1673   :categories '(link) :trust 'low)
   1674 
   1675 (org-lint-add-checker 'link-to-local-file
   1676   "Report links to non-existent local files"
   1677   #'org-lint-link-to-local-file
   1678   :categories '(link) :trust 'low)
   1679 
   1680 (org-lint-add-checker 'non-existent-setupfile-parameter
   1681   "Report SETUPFILE keywords with non-existent file parameter"
   1682   #'org-lint-non-existent-setupfile-parameter
   1683   :trust 'low)
   1684 
   1685 (org-lint-add-checker 'wrong-include-link-parameter
   1686   "Report INCLUDE keywords with misleading link parameter"
   1687   #'org-lint-wrong-include-link-parameter
   1688   :categories '(export) :trust 'low)
   1689 
   1690 (org-lint-add-checker 'obsolete-include-markup
   1691   "Report obsolete markup in INCLUDE keyword"
   1692   #'org-lint-obsolete-include-markup
   1693   :categories '(obsolete export) :trust 'low)
   1694 
   1695 (org-lint-add-checker 'unknown-options-item
   1696   "Report unknown items in OPTIONS keyword"
   1697   #'org-lint-unknown-options-item
   1698   :categories '(export) :trust 'low)
   1699 
   1700 (org-lint-add-checker 'misspelled-export-option
   1701   "Report potentially misspelled export options in properties."
   1702   #'org-lint-export-option-keywords
   1703   :categories '(export) :trust 'low)
   1704 
   1705 (org-lint-add-checker 'invalid-macro-argument-and-template
   1706   "Report spurious macro arguments or invalid macro templates"
   1707   #'org-lint-invalid-macro-argument-and-template
   1708   :categories '(export) :trust 'low)
   1709 
   1710 (org-lint-add-checker 'special-property-in-properties-drawer
   1711   "Report special properties in properties drawers"
   1712   #'org-lint-special-property-in-properties-drawer
   1713   :categories '(properties))
   1714 
   1715 (org-lint-add-checker 'obsolete-properties-drawer
   1716   "Report obsolete syntax for properties drawers"
   1717   #'org-lint-obsolete-properties-drawer
   1718   :categories '(obsolete properties))
   1719 
   1720 (org-lint-add-checker 'invalid-effort-property
   1721   "Report invalid duration in EFFORT property"
   1722   #'org-lint-invalid-effort-property
   1723   :categories '(properties))
   1724 
   1725 (org-lint-add-checker 'invalid-id-property
   1726   "Report search string delimiter \"::\" in ID property"
   1727   #'org-lint-invalid-id-property
   1728   :categories '(properties))
   1729 
   1730 (org-lint-add-checker 'undefined-footnote-reference
   1731   "Report missing definition for footnote references"
   1732   #'org-lint-undefined-footnote-reference
   1733   :categories '(footnote))
   1734 
   1735 (org-lint-add-checker 'unreferenced-footnote-definition
   1736   "Report missing reference for footnote definitions"
   1737   #'org-lint-unreferenced-footnote-definition
   1738   :categories '(footnote))
   1739 
   1740 (org-lint-add-checker 'extraneous-element-in-footnote-section
   1741   "Report non-footnote definitions in footnote section"
   1742   #'org-lint-extraneous-element-in-footnote-section
   1743   :categories '(footnote))
   1744 
   1745 (org-lint-add-checker 'invalid-keyword-syntax
   1746   "Report probable invalid keywords"
   1747   #'org-lint-invalid-keyword-syntax
   1748   :trust 'low)
   1749 
   1750 (org-lint-add-checker 'invalid-image-alignment
   1751   "Report unsupported align attribute for keyword"
   1752   #'org-lint-invalid-image-alignment
   1753   :trust 'high)
   1754 
   1755 (org-lint-add-checker 'invalid-block
   1756   "Report invalid blocks"
   1757   #'org-lint-invalid-block
   1758   :trust 'low)
   1759 
   1760 (org-lint-add-checker 'mismatched-planning-repeaters
   1761   "Report mismatched repeaters in planning info line"
   1762   #'org-lint-mismatched-planning-repeaters
   1763   :trust 'low)
   1764 
   1765 (org-lint-add-checker 'misplaced-planning-info
   1766   "Report misplaced planning info line"
   1767   #'org-lint-misplaced-planning-info
   1768   :trust 'low)
   1769 
   1770 (org-lint-add-checker 'incomplete-drawer
   1771   "Report probable incomplete drawers"
   1772   #'org-lint-incomplete-drawer
   1773   :trust 'low)
   1774 
   1775 (org-lint-add-checker 'indented-diary-sexp
   1776   "Report probable indented diary-sexps"
   1777   #'org-lint-indented-diary-sexp
   1778   :trust 'low)
   1779 
   1780 (org-lint-add-checker 'quote-section
   1781   "Report obsolete QUOTE section"
   1782   #'org-lint-quote-section
   1783   :categories '(obsolete) :trust 'low)
   1784 
   1785 (org-lint-add-checker 'file-application
   1786   "Report obsolete \"file+application\" link"
   1787   #'org-lint-file-application
   1788   :categories '(link obsolete))
   1789 
   1790 (org-lint-add-checker 'percent-encoding-link-escape
   1791   "Report obsolete escape syntax in links"
   1792   #'org-lint-percent-encoding-link-escape
   1793   :categories '(link obsolete) :trust 'low)
   1794 
   1795 (org-lint-add-checker 'spurious-colons
   1796   "Report spurious colons in tags"
   1797   #'org-lint-spurious-colons
   1798   :categories '(tags))
   1799 
   1800 (org-lint-add-checker 'non-existent-bibliography
   1801   "Report invalid bibliography file"
   1802   #'org-lint-non-existent-bibliography
   1803   :categories '(cite))
   1804 
   1805 (org-lint-add-checker 'missing-print-bibliography
   1806   "Report missing \"print_bibliography\" keyword"
   1807   #'org-lint-missing-print-bibliography
   1808   :categories '(cite))
   1809 
   1810 (org-lint-add-checker 'invalid-cite-export-declaration
   1811   "Report invalid value for \"cite_export\" keyword"
   1812   #'org-lint-invalid-cite-export-declaration
   1813   :categories '(cite))
   1814 
   1815 (org-lint-add-checker 'incomplete-citation
   1816   "Report incomplete citation object"
   1817   #'org-lint-incomplete-citation
   1818   :categories '(cite) :trust 'low)
   1819 
   1820 (org-lint-add-checker 'item-number
   1821   "Report inconsistent item numbers in lists"
   1822   #'org-lint-item-number
   1823   :categories '(plain-list))
   1824 
   1825 (org-lint-add-checker 'LaTeX-$
   1826   "Report potentially confusing $...$ LaTeX markup."
   1827   #'org-lint-LaTeX-$
   1828   :categories '(markup))
   1829 (org-lint-add-checker 'LaTeX-$
   1830   "Report $ that might be treated as LaTeX fragment boundary."
   1831   #'org-lint-LaTeX-$-ambiguous
   1832   :categories '(markup) :trust 'low)
   1833 (org-lint-add-checker 'beamer-frame
   1834   "Report that frame text contains beamer frame environment."
   1835   #'org-lint-beamer-frame
   1836   :categories '(export) :trust 'low)
   1837 (org-lint-add-checker 'timestamp-syntax
   1838   "Report malformed timestamps."
   1839   #'org-lint-timestamp-syntax
   1840   :categories '(timestamp) :trust 'low)
   1841 (org-lint-add-checker 'planning-inactive
   1842   "Report inactive timestamps in SCHEDULED/DEADLINE."
   1843   #'org-lint-inactive-planning
   1844   :categories '(timestamp) :trust 'high)
   1845 
   1846 (provide 'org-lint)
   1847 
   1848 ;; Local variables:
   1849 ;; generated-autoload-file: "org-loaddefs.el"
   1850 ;; End:
   1851 
   1852 ;;; org-lint.el ends here