1
0
mirror of https://git.savannah.gnu.org/git/emacs/org-mode.git synced 2024-11-21 06:55:35 +00:00
org-mode/lisp/org-lint.el

1886 lines
69 KiB
EmacsLisp
Raw Normal View History

;;; org-lint.el --- Linting for Org documents -*- lexical-binding: t; -*-
;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
;; Author: Nicolas Goaziou <mail@nicolasgoaziou.fr>
;; Keywords: outlines, hypermedia, calendar, text
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements linting for Org syntax. The process is
;; started by calling `org-lint' command, which see.
;; New checkers are added by `org-lint-add-checker' function.
;; Internally, all checks are listed in `org-lint--checkers'.
;; Results are displayed in a special "*Org Lint*" buffer with
;; a dedicated major mode, derived from `tabulated-list-mode'.
;; In addition to the usual key-bindings inherited from it, "C-j" and
;; "TAB" display problematic line reported under point whereas "RET"
;; jumps to it. Also, "h" hides all reports similar to the current
;; one. Additionally, "i" removes them from subsequent reports.
;; Checks currently implemented report the following:
;; - duplicates CUSTOM_ID properties,
;; - duplicate NAME values,
;; - duplicate targets,
;; - duplicate footnote definitions,
;; - orphaned affiliated keywords,
;; - obsolete affiliated keywords,
;; - deprecated export block syntax,
;; - deprecated Babel header syntax,
;; - missing language in source blocks,
;; - missing backend in export blocks,
;; - invalid Babel call blocks,
;; - NAME values with a colon,
;; - wrong babel headers,
;; - invalid value in babel headers,
;; - misuse of CATEGORY keyword,
;; - "coderef" links with unknown destination,
;; - "custom-id" links with unknown destination,
;; - "fuzzy" links with unknown destination,
;; - "id" links with unknown destination,
;; - links to non-existent local files,
;; - SETUPFILE keywords with non-existent file parameter,
;; - INCLUDE keywords with misleading link parameter,
;; - obsolete markup in INCLUDE keyword,
;; - unknown items in OPTIONS keyword,
;; - spurious macro arguments or invalid macro templates,
;; - special properties in properties drawers,
;; - obsolete syntax for properties drawers,
;; - invalid duration in EFFORT property,
org-id.el: Add search strings, inherit parent IDs * lisp/ol.el (org-store-link): Refactor org-id links to use standard `org-store-link-functions'. (org-link-search): Create new headings at appropriate level. (org-link-precise-link-target): New function extracting logic to identify a precise link target, e.g. a heading, named object, or text search. (org-link-try-link-store-functions): Extract logic to call external link store functions. Pass them a new `interactive?' argument. * lisp/ol-bbdb.el (org-bbdb-store-link): * lisp/ol-bibtex.el (org-bibtex-store-link): * lisp/ol-docview.el (org-docview-store-link): * lisp/ol-eshell.el (org-eshell-store-link): * lisp/ol-eww.el (org-eww-store-link): * lisp/ol-gnus.el (org-gnus-store-link): * lisp/ol-info.el (org-info-store-link): * lisp/ol-irc.el (org-irc-store-link): * lisp/ol-man.el (org-man-store-link): * lisp/ol-mhe.el (org-mhe-store-link): * lisp/ol-rmail.el (org-rmail-store-link): Accept optional arg. * lisp/org-id.el (org-id-link-consider-parent-id): New option to allow a parent heading with an id to be considered as a link target. (org-id-link-use-context): New option to add context to org-id links. (org-id-get): Add optional `inherit' argument which considers parents' IDs if the current entry does not have one. (org-id-store-link): Consider IDs of parent headings as link targets when current heading has no ID and `org-id-link-consider-parent-id' is set. Add a search string to the link when enabled. (org-id-store-link-maybe): Function set as :store option for custom id link property. Move logic from `org-store-link' here to determine when an org-id link should be stored using `org-id-store-link'. (org-id-open): Recognise search strings after "::" in org-id links. * lisp/org-lint.el: Add checker for "::" in ID properties. * testing/lisp/test-ol.el: Add tests for `org-link-precise-link-target' and `org-id-store-link' functions, testing new options. * doc/org-manual.org: Update documentation about links. * etc/ORG-NEWS: Document changes and new options. These feature allows for more precise links when using org-id to link to org headings, without requiring every single headline to have an id. Link: https://list.orgmode.org/118435e8-0b20-46fd-af6a-88de8e19fac6@app.fastmail.com/
2023-11-19 14:52:05 +00:00
;; - invalid ID property with a double colon,
;; - missing definition for footnote references,
;; - missing reference for footnote definitions,
;; - non-footnote definitions in footnote section,
;; - probable invalid keywords,
;; - invalid blocks,
ox-icalendar: Add support for unscheduled and repeating TODOs * lisp/ox-icalendar.el (org-icalendar-todo-unscheduled-start): New customization to control the exported start time of unscheduled tasks. (org-icalendar--rrule): Helper function for RRULE export. (org-icalendar--vevent): Use the new helper function for RRULE. (org-icalendar--repeater-type): Helper function to get the repeater type, and display warning if not supported. (org-icalendar--vtodo): Change how unscheduled TODOs are handled using the new customization option. Export SCHEDULED and DEADLINE repeaters. In case of SCHEDULED repeater and a DEADLINE without repeater, treat DEADLINE as RRULE UNTIL. Emit a warning for tricky edge cases that are not yet implemented. * testing/lisp/test-ox-icalendar.el (test-ox-icalendar/todo-repeater-shared): Test for exporting shared SCHEDULED/DEADLINE repeater. (test-ox-icalendar/todo-repeating-deadline-warndays): Test using warning days as DTSTART of repeating deadline. (test-ox-icalendar/todo-repeater-until): Test using DEADLINE as RRULE UNTIL. (test-ox-icalendar/todo-repeater-until-utc): Test RRULE UNTIL is in UTC format when DTSTART is not in local time format. (test-ox-icalendar/warn-unsupported-repeater): Unit test to warn for unsupported repeater types. * lisp/org-lint.el (org-lint-mismatched-planning-repeaters): Add lint for mismatched SCHEDULED and DEADLINE repeaters. * testing/lisp/test-org-lint.el (test-org-lint/mismatched-planning-repeaters): Add test for linting of mismatched SCHEDULED and DEADLINE repeaters. * doc/org-manual.org (iCalendar Export): Add link to new variable `org-icalendar-todo-unscheduled-start'.
2023-06-11 14:50:20 +00:00
;; - mismatched repeaters in planning info line,
;; - misplaced planning info line,
;; - probable incomplete drawers,
;; - probable indented diary-sexps,
;; - obsolete QUOTE section,
;; - obsolete "file+application" link,
;; - obsolete escape syntax in links,
;; - spurious colons in tags,
;; - invalid bibliography file,
;; - missing "print_bibliography" keyword,
;; - invalid value for "cite_export" keyword,
;; - incomplete citation object.
;;; Code:
(require 'org-macs)
(org-assert-version)
(require 'cl-lib)
Move link-related core functions out of "org.el" * contrib/lisp/org-wl.el (org-wl-store-link-message): * lisp/Makefile (clean-install): * lisp/ob-core.el (org-link-bracket-re): (org-babel-open-src-block-result): (org-babel-read-element): (org-babel-read-link): (org-babel-result-end): * lisp/ob-tangle.el (org-link-bracket-re): (org-babel-tangle-single-block): (org-link-analytic-bracket-re): (org-babel-detangle): (org-babel-tangle-jump-to-org): * lisp/ol.el: * lisp/org-agenda.el (org-agenda-get-some-entry-text): (org-diary): (org-agenda-format-item): (org-agenda-open-link): (org-agenda-switch-to): (org-agenda-to-appt): * lisp/org-bbdb.el (org-bbdb-store-link): * lisp/org-bibtex.el (org-bibtex-store-link): * lisp/org-capture.el (org-capture-fill-template): * lisp/org-clock.el (org-clocktable-write-default): (org-clock-get-table-data): * lisp/org-compat.el (org-doi-server-url): (org-email-link-description-format): (org-make-link-description-function): (org-from-is-user-regexp): (org-descriptive-links): (org-url-hexify-p): (org-context-in-file-links): (org-keep-stored-link-after-insertion): (org-display-internal-link-with-indirect-buffer): (org-confirm-shell-link-function): (org-confirm-shell-link-not-regexp): (org-confirm-elisp-link-function): (org-confirm-elisp-link-not-regexp): (org-file-complete-link): (org-email-link-description): (org-make-link-string): (org-store-link-props): (org-add-link-props): (org-make-link-regexps): (org-angle-link-re): (org-plain-link-re): (org-bracket-link-regexp): (org-bracket-link-analytic-regexp): (org-any-link-re): * lisp/org-docview.el (org-docview-store-link): (org-docview-complete-link): * lisp/org-element.el (org-element-link-parser): * lisp/org-eshell.el (org-eshell-store-link): * lisp/org-eww.el (org-eww-store-link): (org-eww-copy-for-org-mode): * lisp/org-footnote.el (org-footnote-next-reference-or-definition): * lisp/org-gnus.el (org-gnus-article-link): (org-gnus-store-link): * lisp/org-id.el (org-id-store-link): * lisp/org-info.el (org-info-store-link): * lisp/org-irc.el (org-irc-erc-store-link): * lisp/org-mhe.el (org-mhe-store-link): * lisp/org-pcomplete.el (pcomplete/org-mode/searchhead): * lisp/org-protocol.el (org-protocol-do-capture): * lisp/org-rmail.el (org-rmail-store-link): * lisp/org-w3m.el (org-w3m-store-link): (org-w3m-copy-for-org-mode):
2018-11-26 23:04:41 +00:00
(require 'ob)
(require 'oc)
Move link-related core functions out of "org.el" * contrib/lisp/org-wl.el (org-wl-store-link-message): * lisp/Makefile (clean-install): * lisp/ob-core.el (org-link-bracket-re): (org-babel-open-src-block-result): (org-babel-read-element): (org-babel-read-link): (org-babel-result-end): * lisp/ob-tangle.el (org-link-bracket-re): (org-babel-tangle-single-block): (org-link-analytic-bracket-re): (org-babel-detangle): (org-babel-tangle-jump-to-org): * lisp/ol.el: * lisp/org-agenda.el (org-agenda-get-some-entry-text): (org-diary): (org-agenda-format-item): (org-agenda-open-link): (org-agenda-switch-to): (org-agenda-to-appt): * lisp/org-bbdb.el (org-bbdb-store-link): * lisp/org-bibtex.el (org-bibtex-store-link): * lisp/org-capture.el (org-capture-fill-template): * lisp/org-clock.el (org-clocktable-write-default): (org-clock-get-table-data): * lisp/org-compat.el (org-doi-server-url): (org-email-link-description-format): (org-make-link-description-function): (org-from-is-user-regexp): (org-descriptive-links): (org-url-hexify-p): (org-context-in-file-links): (org-keep-stored-link-after-insertion): (org-display-internal-link-with-indirect-buffer): (org-confirm-shell-link-function): (org-confirm-shell-link-not-regexp): (org-confirm-elisp-link-function): (org-confirm-elisp-link-not-regexp): (org-file-complete-link): (org-email-link-description): (org-make-link-string): (org-store-link-props): (org-add-link-props): (org-make-link-regexps): (org-angle-link-re): (org-plain-link-re): (org-bracket-link-regexp): (org-bracket-link-analytic-regexp): (org-any-link-re): * lisp/org-docview.el (org-docview-store-link): (org-docview-complete-link): * lisp/org-element.el (org-element-link-parser): * lisp/org-eshell.el (org-eshell-store-link): * lisp/org-eww.el (org-eww-store-link): (org-eww-copy-for-org-mode): * lisp/org-footnote.el (org-footnote-next-reference-or-definition): * lisp/org-gnus.el (org-gnus-article-link): (org-gnus-store-link): * lisp/org-id.el (org-id-store-link): * lisp/org-info.el (org-info-store-link): * lisp/org-irc.el (org-irc-erc-store-link): * lisp/org-mhe.el (org-mhe-store-link): * lisp/org-pcomplete.el (pcomplete/org-mode/searchhead): * lisp/org-protocol.el (org-protocol-do-capture): * lisp/org-rmail.el (org-rmail-store-link): * lisp/org-w3m.el (org-w3m-store-link): (org-w3m-copy-for-org-mode):
2018-11-26 23:04:41 +00:00
(require 'ol)
2020-07-02 17:36:34 +00:00
(require 'org-attach)
(require 'org-macro)
(require 'org-fold)
(require 'ox)
(require 'seq)
;;; Checkers structure
(cl-defstruct (org-lint-checker (:copier nil))
name summary function trust categories)
(defvar org-lint--checkers nil
"List of all available checkers.
This list is populated by `org-lint-add-checker' function.")
;;;###autoload
(defun org-lint-add-checker (name summary fun &rest props)
"Add a new checker for linter.
NAME is a unique check identifier, as a non-nil symbol. SUMMARY
is a short description of the check, as a string.
The check is done calling the function FUN with one mandatory
argument, the parse tree describing the current Org buffer. Such
function calls are wrapped within a `save-excursion' and point is
always at `point-min'. Its return value has to be an
alist (POSITION MESSAGE) where POSITION refer to the buffer
position of the error, as an integer, and MESSAGE is a one-line
string describing the error.
Optional argument PROPS provides additional information about the
checker. Currently, two properties are supported:
`:categories'
Categories relative to the check, as a list of symbol. They
are used for filtering when calling `org-lint'. Checkers
not explicitly associated to a category are collected in the
`default' one.
`:trust'
The trust level one can have in the check. It is either
`low' or `high', depending on the heuristics implemented and
the nature of the check. This has an indicative value only
and is displayed along reports."
(declare (indent 1))
;; Sanity checks.
(pcase name
(`nil (error "Name field is mandatory for checkers"))
((pred symbolp) nil)
(_ (error "Invalid type for name field")))
(unless (functionp fun)
(error "Checker field is expected to be a valid function"))
;; Install checker in `org-lint--checkers'; uniquify by name.
(setq org-lint--checkers
(cons (apply #'make-org-lint-checker
:name name
:summary summary
:function fun
props)
(seq-remove (lambda (c) (eq name (org-lint-checker-name c)))
org-lint--checkers))))
;;;###autoload
(defun org-lint-remove-checker (name &rest names)
"Remove checker(s) from linter.
NAME is the unique check identifier, as a non-nil symbol. NAMES
are additional check identifiers to be removed."
(let ((removelist (cons name names)))
(setq org-lint--checkers
(seq-remove (lambda (c) (memq (org-lint-checker-name c) removelist))
org-lint--checkers))))
;;; Reports UI
(defvar org-lint--report-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map tabulated-list-mode-map)
(define-key map (kbd "RET") 'org-lint--jump-to-source)
(define-key map (kbd "TAB") 'org-lint--show-source)
(define-key map (kbd "C-j") 'org-lint--show-source)
(define-key map (kbd "h") 'org-lint--hide-checker)
(define-key map (kbd "i") 'org-lint--ignore-checker)
map)
"Local keymap for `org-lint--report-mode' buffers.")
(define-derived-mode org-lint--report-mode tabulated-list-mode "OrgLint"
"Major mode used to display reports emitted during linting.
\\{org-lint--report-mode-map}"
(setf tabulated-list-format
`[("Line" 6
(lambda (a b)
(< (string-to-number (aref (cadr a) 0))
(string-to-number (aref (cadr b) 0))))
:right-align t)
("Trust" 5 t)
("Warning" 0 t)])
(tabulated-list-init-header))
(defun org-lint--generate-reports (buffer checkers)
"Generate linting report for BUFFER.
CHECKERS is the list of checkers used.
Return an alist (ID [LINE TRUST DESCRIPTION CHECKER]), suitable
for `tabulated-list-printer'."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(let ((ast (org-element-parse-buffer nil nil 'defer))
(id 0)
(last-line 1)
(last-pos 1))
;; Insert unique ID for each report. Replace buffer positions
;; with line numbers.
(mapcar
(lambda (report)
(list
(cl-incf id)
(apply #'vector
(cons
(progn
(goto-char (car report))
(forward-line 0)
(prog1 (propertize
(number-to-string
(cl-incf last-line
(count-lines last-pos (point))))
'org-lint-marker (car report))
(setf last-pos (point))))
(cdr report)))))
;; Insert trust level in generated reports. Also sort them
;; by buffer position in order to optimize lines computation.
(sort (cl-mapcan
(lambda (c)
(let ((trust (symbol-name (org-lint-checker-trust c))))
(mapcar
(lambda (report)
(list (copy-marker (car report)) trust (nth 1 report) c))
(save-excursion
(funcall (org-lint-checker-function c)
ast)))))
checkers)
#'car-less-than-car))))))
(defvar-local org-lint--source-buffer nil
"Source buffer associated to current report buffer.")
(defvar-local org-lint--local-checkers nil
"List of checkers used to build current report.")
(defun org-lint--refresh-reports ()
(setq tabulated-list-entries
(org-lint--generate-reports org-lint--source-buffer
org-lint--local-checkers))
(tabulated-list-print))
(defun org-lint--current-line ()
"Return current report line, as a number."
(string-to-number (aref (tabulated-list-get-entry) 0)))
(defun org-lint--current-marker ()
"Return current report marker."
(get-text-property 0 'org-lint-marker (aref (tabulated-list-get-entry) 0)))
(defun org-lint--current-checker (&optional entry)
"Return current report checker.
When optional argument ENTRY is non-nil, use this entry instead
of current one."
(aref (if entry (nth 1 entry) (tabulated-list-get-entry)) 3))
(defun org-lint--display-reports (source checkers)
"Display linting reports for buffer SOURCE.
CHECKERS is the list of checkers used."
(let ((buffer (get-buffer-create "*Org Lint*")))
(with-current-buffer buffer
(org-lint--report-mode)
(setf org-lint--source-buffer source)
(setf org-lint--local-checkers checkers)
(org-lint--refresh-reports)
(add-hook 'tabulated-list-revert-hook #'org-lint--refresh-reports nil t))
(pop-to-buffer buffer)))
(defun org-lint--jump-to-source ()
"Move to source line that generated the report at point."
(interactive)
(let ((mk (org-lint--current-marker)))
(switch-to-buffer-other-window org-lint--source-buffer)
(unless (<= (point-min) mk (point-max)) (widen))
(goto-char mk)
(org-fold-show-set-visibility 'local)
(recenter)))
(defun org-lint--show-source ()
"Show source line that generated the report at point."
(interactive)
(let ((buffer (current-buffer)))
(org-lint--jump-to-source)
(switch-to-buffer-other-window buffer)))
(defun org-lint--hide-checker ()
"Hide all reports from checker that generated the report at point."
(interactive)
(let ((c (org-lint--current-checker)))
(setf tabulated-list-entries
(cl-remove-if (lambda (e) (equal c (org-lint--current-checker e)))
tabulated-list-entries))
(tabulated-list-print)))
(defun org-lint--ignore-checker ()
"Ignore all reports from checker that generated the report at point.
Checker will also be ignored in all subsequent reports."
(interactive)
(setf org-lint--local-checkers
(remove (org-lint--current-checker) org-lint--local-checkers))
(org-lint--hide-checker))
;;; Main function
;;;###autoload
(defun org-lint (&optional arg)
"Check current Org buffer for syntax mistakes.
By default, run all checkers. With a `\\[universal-argument]' prefix ARG, \
select one
category of checkers only. With a `\\[universal-argument] \
\\[universal-argument]' prefix, run one precise
checker by its name.
ARG can also be a list of checker names, as symbols, to run."
(interactive "P")
(unless (derived-mode-p 'org-mode) (user-error "Not in an Org buffer"))
(when (called-interactively-p 'any)
(message "Org linting process starting..."))
(let ((checkers
(pcase arg
(`nil org-lint--checkers)
(`(4)
(let ((category
(completing-read
"Checker category: "
(mapcar #'org-lint-checker-categories org-lint--checkers)
nil t)))
(cl-remove-if-not
(lambda (c)
(assoc-string category (org-lint-checker-categories c)))
org-lint--checkers)))
(`(16)
(list
(let ((name (completing-read
"Checker name: "
(mapcar #'org-lint-checker-name org-lint--checkers)
nil t)))
(catch 'exit
(dolist (c org-lint--checkers)
(when (string= (org-lint-checker-name c) name)
(throw 'exit c)))))))
((pred consp)
(cl-remove-if-not (lambda (c) (memq (org-lint-checker-name c) arg))
org-lint--checkers))
(_ (user-error "Invalid argument `%S' for `org-lint'" arg)))))
(if (not (called-interactively-p 'any))
(org-lint--generate-reports (current-buffer) checkers)
(org-lint--display-reports (current-buffer) checkers)
(message "Org linting process completed"))))
;;; Checker functions
(defun org-lint--collect-duplicates
(ast type extract-key extract-position build-message)
"Helper function to collect duplicates in parse tree AST.
EXTRACT-KEY is a function extracting key. It is called with
a single argument: the element or object. Comparison is done
with `equal'.
EXTRACT-POSITION is a function returning position for the report.
It is called with two arguments, the object or element, and the
key.
BUILD-MESSAGE is a function creating the report message. It is
called with one argument, the key used for comparison."
(let* (keys
originals
reports
(make-report
(lambda (position value)
(push (list position (funcall build-message value)) reports))))
(org-element-map ast type
(lambda (datum)
(let ((key (funcall extract-key datum)))
(cond
((not key))
((assoc key keys) (cl-pushnew (assoc key keys) originals)
(funcall make-report (funcall extract-position datum key) key))
(t (push (cons key (funcall extract-position datum key)) keys))))))
(dolist (e originals reports) (funcall make-report (cdr e) (car e)))))
(defun org-lint-misplaced-heading (ast)
"Check for accidentally misplaced heading lines.
Example:
** Heading 1
** Heading 2** Oops heading 3
** Heading 4"
(org-with-point-at ast
(goto-char (point-min))
(let (result)
;; Heuristics for 2+ level heading not at bol.
(while (re-search-forward (rx (not (any "*\n\r ,")) ;; Not a bol; not escaped ,** heading; not " *** words"
"*" (1+ "*") " ") nil t)
;; Limit false-positive rate by only complaining about
;; ** Heading** Heading and
;; ** Oops heading
;; Paragraph** Oops heading
(when (org-element-type-p
(org-element-at-point)
'(paragraph headline))
(push (list (match-beginning 0) "Possibly misplaced heading line") result)))
result)))
(defun org-lint-duplicate-custom-id (ast)
(org-lint--collect-duplicates
ast
'node-property
(lambda (property)
(and (org-string-equal-ignore-case
"CUSTOM_ID" (org-element-property :key property))
(org-element-property :value property)))
(lambda (property _) (org-element-begin property))
(lambda (key) (format "Duplicate CUSTOM_ID property \"%s\"" key))))
(defun org-lint-duplicate-name (ast)
(org-lint--collect-duplicates
ast
org-element-all-elements
(lambda (datum) (org-element-property :name datum))
(lambda (datum name)
(goto-char (org-element-begin datum))
(re-search-forward
(format "^[ \t]*#\\+[A-Za-z]+:[ \t]*%s[ \t]*$" (regexp-quote name)))
(match-beginning 0))
(lambda (key) (format "Duplicate NAME \"%s\"" key))))
(defun org-lint-duplicate-target (ast)
(org-lint--collect-duplicates
ast
'target
(lambda (target) (split-string (org-element-property :value target)))
(lambda (target _) (org-element-begin target))
(lambda (key)
(format "Duplicate target <<%s>>" (mapconcat #'identity key " ")))))
(defun org-lint-duplicate-footnote-definition (ast)
(org-lint--collect-duplicates
ast
'footnote-definition
(lambda (definition) (org-element-property :label definition))
(lambda (definition _) (org-element-post-affiliated definition))
(lambda (key) (format "Duplicate footnote definition \"%s\"" key))))
(defun org-lint-orphaned-affiliated-keywords (ast)
;; Ignore orphan RESULTS keywords, which could be generated from
;; a source block returning no value.
(let ((keywords (cl-set-difference org-element-affiliated-keywords
'("RESULT" "RESULTS")
:test #'equal)))
(org-element-map ast 'keyword
(lambda (k)
(let ((key (org-element-property :key k)))
(and (or (let ((case-fold-search t))
Use `string-match-p' instead of `org-string-match-p' * contrib/lisp/org-contacts.el (org-contacts-filter): (org-contacts-complete-group): (org-contacts-complete-tags-props): * contrib/lisp/org-wl.el (org-wl-open): * contrib/lisp/ox-bibtex.el (org-bibtex-merge-contiguous-citations): * lisp/ob-core.el (org-babel-demarcate-block): * lisp/ob-processing.el (org-babel-processing-view-sketch): * lisp/ob-stan.el (org-babel-execute:stan): * lisp/org-agenda.el (org-agenda-get-category-icon): * lisp/org-clock.el (org-clock-into-drawer): * lisp/org-element.el (org-element-link-parser): * lisp/org-lint.el (org-lint-orphaned-affiliated-keywords): (org-lint-invalid-babel-call-block): (org-lint-colon-in-name): * lisp/org-list.el (org-list-item-body-column): * lisp/org-macro.el (org-macro-replace-all): * lisp/org-plot.el (org-plot/gnuplot-script): * lisp/org-table.el (org-table-export): (org-table-align): (org-table-get-range): (org-table-recalculate): (org-table-expand-lhs-ranges): (org-table-formula-substitute-names): (org-table-show-reference): (orgtbl-to-texinfo): (org-table-remote-reference-indirection): * lisp/org.el (org-make-link-string): (org--open-elisp-link): (org-open-at-point): (org-store-log-note): (org-cached-entry-get): (org--valid-property-p): (org-entry-properties): (org-buffer-property-keys): (org-insert-drawer): (org-display-inline-images): (org-in-commented-heading-p): * lisp/ox-ascii.el (org-ascii-keyword): * lisp/ox-beamer.el (org-beamer--format-frame): * lisp/ox-html.el (org-html-keyword): * lisp/ox-latex.el (org-latex--label): (org-latex-headline): (org-latex-item): (org-latex-keyword): (org-latex--inline-image): (org-latex-src-block): * lisp/ox-odt.el (org-odt-styles-dir): (org-odt-keyword): (org-odt--translate-latex-fragments): * lisp/ox-texinfo.el (org-texinfo-template): (org-texinfo-keyword): (org-texinfo-src-block): * lisp/ox.el (org-export-inline-image-p): (org-export-file-uri): * testing/lisp/test-org-table.el (test-org-table/to-generic): (test-org-table/to-latex): (test-org-table/to-html): (test-org-table/named-field): (test-org-table/named-column): (test-org-table/tab-indent): (test-org-table/first-rc): (test-org-table/last-rc): Use `string-match-p' instead of `org-string-match-p'.
2016-07-25 13:21:12 +00:00
(string-match-p "\\`ATTR_[-_A-Za-z0-9]+\\'" key))
(member key keywords))
(list (org-element-post-affiliated k)
(format "Orphaned affiliated keyword: \"%s\"" key))))))))
(defun org-lint-regular-keyword-before-affiliated (ast)
(org-element-map ast 'keyword
(lambda (keyword)
(when (= (org-element-post-blank keyword) 0)
(let ((next-element (org-with-point-at (org-element-end keyword)
(org-element-at-point))))
(when (< (org-element-begin next-element) (org-element-post-affiliated next-element))
;; A keyword followed without blank lines by an element with affiliated keywords.
;; The keyword may be confused with affiliated keywords.
(list (org-element-begin keyword)
(format "Independent keyword %s may be confused with affiliated keywords below"
(org-element-property :key keyword)))))))))
(defun org-lint-obsolete-affiliated-keywords (_)
(let ((regexp (format "^[ \t]*#\\+%s:"
(regexp-opt '("DATA" "LABEL" "RESNAME" "SOURCE"
"SRCNAME" "TBLNAME" "RESULT" "HEADERS")
t)))
reports)
(while (re-search-forward regexp nil t)
(let ((key (upcase (match-string-no-properties 1))))
(when (< (point)
(org-element-post-affiliated (org-element-at-point)))
(push
(list (line-beginning-position)
(format
"Obsolete affiliated keyword: \"%s\". Use \"%s\" instead"
key
(pcase key
("HEADERS" "HEADER")
("RESULT" "RESULTS")
(_ "NAME"))))
reports))))
reports))
(defun org-lint-deprecated-export-blocks (ast)
(let ((deprecated '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
"ODT" "ORG" "TEXINFO")))
(org-element-map ast 'special-block
(lambda (b)
(let ((type (org-element-property :type b)))
(when (member-ignore-case type deprecated)
(list
(org-element-post-affiliated b)
(format
"Deprecated syntax for export block. Use \"BEGIN_EXPORT %s\" \
instead"
type))))))))
(defun org-lint-deprecated-header-syntax (ast)
(let* ((deprecated-babel-properties
;; DIR is also used for attachments.
(delete "dir"
(mapcar (lambda (arg) (downcase (symbol-name (car arg))))
org-babel-common-header-args-w-values)))
(deprecated-re
(format "\\`%s[ \t]" (regexp-opt deprecated-babel-properties t))))
(org-element-map ast '(keyword node-property)
(lambda (datum)
(let ((key (org-element-property :key datum)))
(pcase (org-element-type datum)
(`keyword
(let ((value (org-element-property :value datum)))
(and (string= key "PROPERTY")
(string-match deprecated-re value)
(list (org-element-begin datum)
(format "Deprecated syntax for \"%s\". \
Use header-args instead"
(match-string-no-properties 1 value))))))
(`node-property
(and (member-ignore-case key deprecated-babel-properties)
(list
(org-element-begin datum)
(format "Deprecated syntax for \"%s\". \
Use :header-args: instead"
key))))))))))
(defun org-lint-missing-language-in-src-block (ast)
(org-element-map ast 'src-block
(lambda (b)
(unless (org-element-property :language b)
(list (org-element-post-affiliated b)
"Missing language in source block")))))
(defun org-lint-suspicious-language-in-src-block (ast)
(org-element-map ast 'src-block
(lambda (b)
Replace use of if-let and when-let, obsolete in Emacs 31.0.50 * lisp/ob-R.el (org-babel-R-associate-session): * lisp/ob-clojure.el (ob-clojure-nbb-command, ob-clojure-cli-command): * lisp/ob-core.el (org-babel-execute-src-block): * lisp/ob-exp.el (org-babel-exp-code): * lisp/ob-julia.el (org-babel-julia-associate-session): * lisp/ob-python.el (org-babel-python--python-util-comint-end-of-output-p): * lisp/ol.el (org-insert-link): * lisp/org-attach.el (org-attach): * lisp/org-clock.el (org-clock-select-task): * lisp/org-compat.el (fboundp): * lisp/org-element-ast.el (org-element-property-raw) (org-element-put-property, org-element-copy): * lisp/org-element.el (org-element-timestamp-interpreter) (org-element--cache-key): * lisp/org-goto.el (org-goto-location): * lisp/org-lint.el (org-lint-suspicious-language-in-src-block) (org-lint-export-option-keywords): * lisp/org-macs.el (org-mks): * lisp/org-persist.el: (org-persist-write:elisp): * lisp/org-table.el (org-table-fedit-finish, orgtbl--skip) (orgtbl--skipcols): * lisp/org.el (org-entry-get-with-inheritance): * lisp/ox-beamer.el (org-beamer--format-frame): * lisp/ox-html.el (org-html--reference, org-html--build-mathjax-config) (org-html-example-block): * lisp/ox-latex.el (org-latex-table-row): * lisp/ox.el (org-export--prune-tree) (org-export--blindly-expand-include): * testing/lisp/test-duplicates-detector.el (test-duplicates-detector--find-duplicates): * testing/lisp/test-ob-haskell-ghci.el (test-ob-haskell-ghci-checking-buffers): * testing/lisp/test-ob-python.el (test-ob-python/async-local-python-shell): * testing/lisp/test-ob-shell.el (test-ob-shell/bash-uses-assoc-arrays) (test-ob-shell/bash-uses-assoc-arrays-with-lists) (test-ob-shell/remote-with-stdin-or-cmdline): Replace use of if-let and when-let, obsolete in Emacs 31.0.50.
2024-10-24 09:04:40 +00:00
(when-let* ((lang (org-element-property :language b)))
(unless (or (functionp (intern (format "org-babel-execute:%s" lang)))
;; No babel backend, but there is corresponding
;; major mode.
(fboundp (org-src-get-lang-mode lang)))
(list (org-element-property :post-affiliated b)
(format "Unknown source block language: '%s'" lang)))))))
(defun org-lint-missing-backend-in-export-block (ast)
(org-element-map ast 'export-block
(lambda (b)
(unless (org-element-property :type b)
(list (org-element-post-affiliated b)
"Missing backend in export block")))))
(defun org-lint-invalid-babel-call-block (ast)
(org-element-map ast 'babel-call
(lambda (b)
(cond
((not (org-element-property :call b))
(list (org-element-post-affiliated b)
"Invalid syntax in babel call block"))
((let ((h (org-element-property :end-header b)))
Use `string-match-p' instead of `org-string-match-p' * contrib/lisp/org-contacts.el (org-contacts-filter): (org-contacts-complete-group): (org-contacts-complete-tags-props): * contrib/lisp/org-wl.el (org-wl-open): * contrib/lisp/ox-bibtex.el (org-bibtex-merge-contiguous-citations): * lisp/ob-core.el (org-babel-demarcate-block): * lisp/ob-processing.el (org-babel-processing-view-sketch): * lisp/ob-stan.el (org-babel-execute:stan): * lisp/org-agenda.el (org-agenda-get-category-icon): * lisp/org-clock.el (org-clock-into-drawer): * lisp/org-element.el (org-element-link-parser): * lisp/org-lint.el (org-lint-orphaned-affiliated-keywords): (org-lint-invalid-babel-call-block): (org-lint-colon-in-name): * lisp/org-list.el (org-list-item-body-column): * lisp/org-macro.el (org-macro-replace-all): * lisp/org-plot.el (org-plot/gnuplot-script): * lisp/org-table.el (org-table-export): (org-table-align): (org-table-get-range): (org-table-recalculate): (org-table-expand-lhs-ranges): (org-table-formula-substitute-names): (org-table-show-reference): (orgtbl-to-texinfo): (org-table-remote-reference-indirection): * lisp/org.el (org-make-link-string): (org--open-elisp-link): (org-open-at-point): (org-store-log-note): (org-cached-entry-get): (org--valid-property-p): (org-entry-properties): (org-buffer-property-keys): (org-insert-drawer): (org-display-inline-images): (org-in-commented-heading-p): * lisp/ox-ascii.el (org-ascii-keyword): * lisp/ox-beamer.el (org-beamer--format-frame): * lisp/ox-html.el (org-html-keyword): * lisp/ox-latex.el (org-latex--label): (org-latex-headline): (org-latex-item): (org-latex-keyword): (org-latex--inline-image): (org-latex-src-block): * lisp/ox-odt.el (org-odt-styles-dir): (org-odt-keyword): (org-odt--translate-latex-fragments): * lisp/ox-texinfo.el (org-texinfo-template): (org-texinfo-keyword): (org-texinfo-src-block): * lisp/ox.el (org-export-inline-image-p): (org-export-file-uri): * testing/lisp/test-org-table.el (test-org-table/to-generic): (test-org-table/to-latex): (test-org-table/to-html): (test-org-table/named-field): (test-org-table/named-column): (test-org-table/tab-indent): (test-org-table/first-rc): (test-org-table/last-rc): Use `string-match-p' instead of `org-string-match-p'.
2016-07-25 13:21:12 +00:00
(and h (string-match-p "\\`\\[.*\\]\\'" h)))
(list
(org-element-post-affiliated b)
"Babel call's end header must not be wrapped within brackets"))))))
(defun org-lint-deprecated-category-setup (ast)
(org-element-map ast 'keyword
(let (category-flag)
(lambda (k)
(cond
((not (string= (org-element-property :key k) "CATEGORY")) nil)
(category-flag
(list (org-element-post-affiliated k)
"Spurious CATEGORY keyword. Set :CATEGORY: property instead"))
(t (setf category-flag t) nil))))))
(defun org-lint-invalid-coderef-link (ast)
(let ((info (list :parse-tree ast)))
(org-element-map ast 'link
(lambda (link)
(let ((ref (org-element-property :path link)))
(and (equal (org-element-property :type link) "coderef")
(not (ignore-errors (org-export-resolve-coderef ref info)))
(list (org-element-begin link)
(format "Unknown coderef \"%s\"" ref))))))))
(defun org-lint-invalid-custom-id-link (ast)
(let ((info (list :parse-tree ast)))
(org-element-map ast 'link
(lambda (link)
(and (equal (org-element-property :type link) "custom-id")
(not (ignore-errors (org-export-resolve-id-link link info)))
(list (org-element-begin link)
(format "Unknown custom ID \"%s\""
(org-element-property :path link))))))))
(defun org-lint-invalid-fuzzy-link (ast)
(let ((info (list :parse-tree ast)))
(org-element-map ast 'link
(lambda (link)
(and (equal (org-element-property :type link) "fuzzy")
(not (ignore-errors (org-export-resolve-fuzzy-link link info)))
(list (org-element-begin link)
(format "Unknown fuzzy location \"%s\""
(let ((path (org-element-property :path link)))
(if (string-prefix-p "*" path)
(substring path 1)
path)))))))))
(defun org-lint-invalid-id-link (ast)
(let ((id-locations-updated nil))
(org-element-map ast 'link
(lambda (link)
(let ((id (org-element-property :path link)))
(and (equal (org-element-property :type link) "id")
(progn
(unless id-locations-updated
(org-id-update-id-locations nil t)
(setq id-locations-updated t))
t)
;; The locations are up-to-date with file changes after
;; the call to `org-id-update-id-locations'. We do not
;; need to double-check if recorded ID is still present
;; in the file.
(not (org-id-find-id-file id))
(list (org-element-begin link)
(format "Unknown ID \"%s\"" id))))))))
(defun org-lint-confusing-brackets (ast)
(org-element-map ast 'link
(lambda (link)
(org-with-wide-buffer
(when (eq (char-after (org-element-end link)) ?\])
(list (org-element-begin link)
(format "Trailing ']' after link end")))))))
(defun org-lint-brackets-inside-description (ast)
(org-element-map ast 'link
(lambda (link)
(when (org-element-contents-begin link)
(org-with-point-at link
(goto-char (org-element-contents-begin link))
(let ((count 0))
(while (re-search-forward (rx (or ?\] ?\[)) (org-element-contents-end link) t)
(if (equal (match-string 0) "[") (cl-incf count) (cl-decf count)))
(when (> count 0)
(list (org-element-begin link)
(format "No closing ']' matches '[' in link description: %s"
(buffer-substring-no-properties
(org-element-contents-begin link)
(org-element-contents-end link)))))))))))
(defun org-lint-special-property-in-properties-drawer (ast)
(org-element-map ast 'node-property
(lambda (p)
(let ((key (org-element-property :key p)))
(and (member-ignore-case key org-special-properties)
(list (org-element-begin p)
(format
"Special property \"%s\" found in a properties drawer"
key)))))))
(defun org-lint-obsolete-properties-drawer (ast)
(org-element-map ast 'drawer
(lambda (d)
(when (equal (org-element-property :drawer-name d) "PROPERTIES")
(let ((headline? (org-element-lineage d 'headline))
(before
(mapcar #'org-element-type
(assq d (reverse (org-element-contents
(org-element-parent d)))))))
(list (org-element-post-affiliated d)
(if (or (and headline? (member before '(nil (planning))))
(and (null headline?) (member before '(nil (comment)))))
"Incorrect contents for PROPERTIES drawer"
"Incorrect location for PROPERTIES drawer")))))))
(defun org-lint-invalid-effort-property (ast)
(org-element-map ast 'node-property
(lambda (p)
(when (equal "EFFORT" (org-element-property :key p))
(let ((value (org-element-property :value p)))
(and (org-string-nw-p value)
(not (org-duration-p value))
(list (org-element-begin p)
(format "Invalid effort duration format: %S" value))))))))
org-id.el: Add search strings, inherit parent IDs * lisp/ol.el (org-store-link): Refactor org-id links to use standard `org-store-link-functions'. (org-link-search): Create new headings at appropriate level. (org-link-precise-link-target): New function extracting logic to identify a precise link target, e.g. a heading, named object, or text search. (org-link-try-link-store-functions): Extract logic to call external link store functions. Pass them a new `interactive?' argument. * lisp/ol-bbdb.el (org-bbdb-store-link): * lisp/ol-bibtex.el (org-bibtex-store-link): * lisp/ol-docview.el (org-docview-store-link): * lisp/ol-eshell.el (org-eshell-store-link): * lisp/ol-eww.el (org-eww-store-link): * lisp/ol-gnus.el (org-gnus-store-link): * lisp/ol-info.el (org-info-store-link): * lisp/ol-irc.el (org-irc-store-link): * lisp/ol-man.el (org-man-store-link): * lisp/ol-mhe.el (org-mhe-store-link): * lisp/ol-rmail.el (org-rmail-store-link): Accept optional arg. * lisp/org-id.el (org-id-link-consider-parent-id): New option to allow a parent heading with an id to be considered as a link target. (org-id-link-use-context): New option to add context to org-id links. (org-id-get): Add optional `inherit' argument which considers parents' IDs if the current entry does not have one. (org-id-store-link): Consider IDs of parent headings as link targets when current heading has no ID and `org-id-link-consider-parent-id' is set. Add a search string to the link when enabled. (org-id-store-link-maybe): Function set as :store option for custom id link property. Move logic from `org-store-link' here to determine when an org-id link should be stored using `org-id-store-link'. (org-id-open): Recognise search strings after "::" in org-id links. * lisp/org-lint.el: Add checker for "::" in ID properties. * testing/lisp/test-ol.el: Add tests for `org-link-precise-link-target' and `org-id-store-link' functions, testing new options. * doc/org-manual.org: Update documentation about links. * etc/ORG-NEWS: Document changes and new options. These feature allows for more precise links when using org-id to link to org headings, without requiring every single headline to have an id. Link: https://list.orgmode.org/118435e8-0b20-46fd-af6a-88de8e19fac6@app.fastmail.com/
2023-11-19 14:52:05 +00:00
(defun org-lint-invalid-id-property (ast)
(org-element-map ast 'node-property
(lambda (p)
(when (equal "ID" (org-element-property :key p))
(let ((value (org-element-property :value p)))
(and (org-string-nw-p value)
(string-match-p "::" value)
(list (org-element-begin p)
(format "IDs should not include \"::\": %S" value))))))))
(defun org-lint-link-to-local-file (ast)
(org-element-map ast 'link
(lambda (l)
(let ((type (org-element-property :type l)))
(pcase type
((or "attachment" "file")
(let* ((path (org-element-property :path l))
(file (if (string= type "file")
path
(org-with-point-at (org-element-begin l)
(org-attach-expand path)))))
(setq file (substitute-env-in-file-name file))
(and (not (file-remote-p file))
(not (file-exists-p file))
(list (org-element-begin l)
(format (if (org-element-lineage l 'link)
"Link to non-existent image file %S \
in description"
"Link to non-existent local file %S")
file)))))
(_ nil))))))
(defun org-lint-non-existent-setupfile-parameter (ast)
(org-element-map ast 'keyword
(lambda (k)
(when (equal (org-element-property :key k) "SETUPFILE")
(let ((file (org-unbracket-string
"\"" "\""
(org-element-property :value k))))
(and (not (org-url-p file))
(not (file-remote-p file))
(not (file-exists-p file))
(list (org-element-begin k)
(format "Non-existent setup file %S" file))))))))
(defun org-lint-wrong-include-link-parameter (ast)
(org-element-map ast 'keyword
(lambda (k)
(when (equal (org-element-property :key k) "INCLUDE")
(let* ((value (org-element-property :value k))
(path
(and (string-match "^\\(\".+?\"\\|\\S-+\\)[ \t]*" value)
(save-match-data
(org-strip-quotes (match-string 1 value))))))
(if (not path)
(list (org-element-post-affiliated k)
"Missing location argument in INCLUDE keyword")
(let* ((file (org-string-nw-p
(if (string-match "::\\(.*\\)\\'" path)
(substring path 0 (match-beginning 0))
path)))
(search (and (not (equal file path))
(org-string-nw-p (match-string 1 path)))))
(unless (org-url-p file)
(if (and file
(not (file-remote-p file))
(not (file-exists-p file)))
(list (org-element-post-affiliated k)
"Non-existent file argument in INCLUDE keyword")
(let* ((visiting (if file (find-buffer-visiting file)
(current-buffer)))
(buffer (or visiting (find-file-noselect file)))
(org-link-search-must-match-exact-headline t))
(unwind-protect
(with-current-buffer buffer
(org-with-wide-buffer
(when (and search
(not (ignore-errors
(org-link-search search nil t))))
(list (org-element-post-affiliated k)
(format
"Invalid search part \"%s\" in INCLUDE keyword"
search)))))
(unless visiting (kill-buffer buffer)))))))))))))
(defun org-lint-obsolete-include-markup (ast)
(let ((regexp (format "\\`\\(?:\".+\"\\|\\S-+\\)[ \t]+%s"
(regexp-opt
'("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
"ODT" "ORG" "TEXINFO")
t))))
(org-element-map ast 'keyword
(lambda (k)
(when (equal (org-element-property :key k) "INCLUDE")
(let ((case-fold-search t)
(value (org-element-property :value k)))
(when (string-match regexp value)
(let ((markup (match-string-no-properties 1 value)))
(list (org-element-post-affiliated k)
(format "Obsolete markup \"%s\" in INCLUDE keyword. \
Use \"export %s\" instead"
markup
markup))))))))))
(defun org-lint-unknown-options-item (ast)
(let ((allowed (delq nil
(append
(mapcar (lambda (o) (nth 2 o)) org-export-options-alist)
(cl-mapcan
(lambda (b)
(mapcar (lambda (o) (nth 2 o))
(org-export-backend-options b)))
org-export-registered-backends))))
reports)
(org-element-map ast 'keyword
(lambda (k)
(when (string= (org-element-property :key k) "OPTIONS")
(let ((value (org-element-property :value k))
(start 0))
(while (string-match "\\(.+?\\):\\((.*?)\\|\\S-+\\)?[ \t]*"
value
start)
(setf start (match-end 0))
(let ((item (match-string 1 value)))
(unless (member item allowed)
(push (list (org-element-post-affiliated k)
(format "Unknown OPTIONS item \"%s\"" item))
reports))
(unless (match-string 2 value)
(push (list (org-element-post-affiliated k)
(format "Missing value for option item %S" item))
reports))))))))
reports))
(defun org-lint-export-option-keywords (ast)
"Check for options keyword properties without EXPORT in AST."
(require 'ox)
(let (options reports common-options options-alist)
(dolist (opt org-export-options-alist)
(when (stringp (nth 1 opt))
(cl-pushnew (nth 1 opt) common-options :test #'equal)))
(dolist (backend org-export-registered-backends)
(dolist (opt (org-export-backend-options backend))
(when (stringp (nth 1 opt))
(cl-pushnew (or (org-export-backend-name backend) 'anonymous)
(alist-get (nth 1 opt) options-alist nil nil #'equal))
(cl-pushnew (nth 1 opt) options :test #'equal))))
(setq options-alist (nreverse options-alist))
(org-element-map ast 'node-property
(lambda (node)
(let ((prop (org-element-property :key node)))
(when (and (or (member prop options) (member prop common-options))
(not (member prop org-default-properties)))
(push (list (org-element-post-affiliated node)
(format "Potentially misspelled %sexport option \"%s\"%s. Consider \"EXPORT_%s\"."
(when (member prop common-options)
"global ")
prop
Replace use of if-let and when-let, obsolete in Emacs 31.0.50 * lisp/ob-R.el (org-babel-R-associate-session): * lisp/ob-clojure.el (ob-clojure-nbb-command, ob-clojure-cli-command): * lisp/ob-core.el (org-babel-execute-src-block): * lisp/ob-exp.el (org-babel-exp-code): * lisp/ob-julia.el (org-babel-julia-associate-session): * lisp/ob-python.el (org-babel-python--python-util-comint-end-of-output-p): * lisp/ol.el (org-insert-link): * lisp/org-attach.el (org-attach): * lisp/org-clock.el (org-clock-select-task): * lisp/org-compat.el (fboundp): * lisp/org-element-ast.el (org-element-property-raw) (org-element-put-property, org-element-copy): * lisp/org-element.el (org-element-timestamp-interpreter) (org-element--cache-key): * lisp/org-goto.el (org-goto-location): * lisp/org-lint.el (org-lint-suspicious-language-in-src-block) (org-lint-export-option-keywords): * lisp/org-macs.el (org-mks): * lisp/org-persist.el: (org-persist-write:elisp): * lisp/org-table.el (org-table-fedit-finish, orgtbl--skip) (orgtbl--skipcols): * lisp/org.el (org-entry-get-with-inheritance): * lisp/ox-beamer.el (org-beamer--format-frame): * lisp/ox-html.el (org-html--reference, org-html--build-mathjax-config) (org-html-example-block): * lisp/ox-latex.el (org-latex-table-row): * lisp/ox.el (org-export--prune-tree) (org-export--blindly-expand-include): * testing/lisp/test-duplicates-detector.el (test-duplicates-detector--find-duplicates): * testing/lisp/test-ob-haskell-ghci.el (test-ob-haskell-ghci-checking-buffers): * testing/lisp/test-ob-python.el (test-ob-python/async-local-python-shell): * testing/lisp/test-ob-shell.el (test-ob-shell/bash-uses-assoc-arrays) (test-ob-shell/bash-uses-assoc-arrays-with-lists) (test-ob-shell/remote-with-stdin-or-cmdline): Replace use of if-let and when-let, obsolete in Emacs 31.0.50.
2024-10-24 09:04:40 +00:00
(if-let* ((backends
(and (not (member prop common-options))
(cdr (assoc-string prop options-alist)))))
(format
" in %S export %s"
(if (= 1 (length backends)) (car backends) backends)
(if (> (length backends) 1) "backends" "backend"))
"")
prop))
reports)))))
reports))
(defun org-lint-invalid-macro-argument-and-template (ast)
(let* ((reports nil)
(extract-placeholders
(lambda (template)
(let ((start 0)
args)
(while (string-match "\\$\\([1-9][0-9]*\\)" template start)
(setf start (match-end 0))
(push (string-to-number (match-string 1 template)) args))
(sort (org-uniquify args) #'<))))
(check-arity
(lambda (arity macro)
(let* ((name (org-element-property :key macro))
(pos (org-element-begin macro))
(args (org-element-property :args macro))
(l (length args)))
(cond
((< l (1- (car arity)))
(push (list pos (format "Missing arguments in macro %S" name))
reports))
((< l (car arity))
(push (list pos (format "Missing argument in macro %S" name))
reports))
((> l (1+ (cdr arity)))
(push (let ((spurious-args (nthcdr (cdr arity) args)))
(list pos
(format "Spurious arguments in macro %S: %s"
name
(mapconcat #'org-trim spurious-args ", "))))
reports))
((> l (cdr arity))
(push (list pos
(format "Spurious argument in macro %S: %s"
name
(org-last args)))
reports))
(t nil))))))
;; Check arguments for macro templates.
(org-element-map ast 'keyword
(lambda (k)
(when (string= (org-element-property :key k) "MACRO")
(let* ((value (org-element-property :value k))
(name (and (string-match "^\\S-+" value)
(match-string 0 value)))
(template (and name
(org-trim (substring value (match-end 0))))))
(cond
((not name)
(push (list (org-element-post-affiliated k)
"Missing name in MACRO keyword")
reports))
((not (org-string-nw-p template))
(push (list (org-element-post-affiliated k)
"Missing template in macro \"%s\"" name)
reports))
(t
(unless (let ((args (funcall extract-placeholders template)))
(equal (number-sequence 1 (or (org-last args) 0)) args))
(push (list (org-element-post-affiliated k)
(format "Unused placeholders in macro \"%s\""
name))
reports))))))))
;; Check arguments for macros.
(org-macro-initialize-templates)
(let ((templates (append
(mapcar (lambda (m) (cons m "$1"))
'("author" "date" "email" "title" "results"))
org-macro-templates)))
(org-element-map ast 'macro
(lambda (macro)
(let* ((name (org-element-property :key macro))
(template (cdr (assoc-string name templates t))))
(pcase template
(`nil
(push (list (org-element-begin macro)
(format "Undefined macro %S" name))
reports))
((guard (string= name "keyword"))
(funcall check-arity '(1 . 1) macro))
((guard (string= name "modification-time"))
(funcall check-arity '(1 . 2) macro))
((guard (string= name "n"))
(funcall check-arity '(0 . 2) macro))
((guard (string= name "property"))
(funcall check-arity '(1 . 2) macro))
((guard (string= name "time"))
(funcall check-arity '(1 . 1) macro))
((pred functionp)) ;ignore (eval ...) templates
(_
(let* ((arg-numbers (funcall extract-placeholders template))
(arity (if (null arg-numbers)
'(0 . 0)
(let ((m (apply #'max arg-numbers)))
(cons m m)))))
(funcall check-arity arity macro))))))))
reports))
(defun org-lint-undefined-footnote-reference (ast)
(let ((definitions
(org-element-map ast '(footnote-definition footnote-reference)
(lambda (f)
(and (or (org-element-type-p f 'footnote-definition)
(eq 'inline (org-element-property :type f)))
(org-element-property :label f))))))
(org-element-map ast 'footnote-reference
(lambda (f)
(let ((label (org-element-property :label f)))
(and (eq 'standard (org-element-property :type f))
(not (member label definitions))
(list (org-element-begin f)
(format "Missing definition for footnote [%s]"
label))))))))
(defun org-lint-unreferenced-footnote-definition (ast)
(let ((references (org-element-map ast 'footnote-reference
(lambda (f) (org-element-property :label f)))))
(org-element-map ast 'footnote-definition
(lambda (f)
(let ((label (org-element-property :label f)))
(and label
(not (member label references))
(list (org-element-post-affiliated f)
(format "No reference for footnote definition [%s]"
label))))))))
ox-icalendar: Add support for unscheduled and repeating TODOs * lisp/ox-icalendar.el (org-icalendar-todo-unscheduled-start): New customization to control the exported start time of unscheduled tasks. (org-icalendar--rrule): Helper function for RRULE export. (org-icalendar--vevent): Use the new helper function for RRULE. (org-icalendar--repeater-type): Helper function to get the repeater type, and display warning if not supported. (org-icalendar--vtodo): Change how unscheduled TODOs are handled using the new customization option. Export SCHEDULED and DEADLINE repeaters. In case of SCHEDULED repeater and a DEADLINE without repeater, treat DEADLINE as RRULE UNTIL. Emit a warning for tricky edge cases that are not yet implemented. * testing/lisp/test-ox-icalendar.el (test-ox-icalendar/todo-repeater-shared): Test for exporting shared SCHEDULED/DEADLINE repeater. (test-ox-icalendar/todo-repeating-deadline-warndays): Test using warning days as DTSTART of repeating deadline. (test-ox-icalendar/todo-repeater-until): Test using DEADLINE as RRULE UNTIL. (test-ox-icalendar/todo-repeater-until-utc): Test RRULE UNTIL is in UTC format when DTSTART is not in local time format. (test-ox-icalendar/warn-unsupported-repeater): Unit test to warn for unsupported repeater types. * lisp/org-lint.el (org-lint-mismatched-planning-repeaters): Add lint for mismatched SCHEDULED and DEADLINE repeaters. * testing/lisp/test-org-lint.el (test-org-lint/mismatched-planning-repeaters): Add test for linting of mismatched SCHEDULED and DEADLINE repeaters. * doc/org-manual.org (iCalendar Export): Add link to new variable `org-icalendar-todo-unscheduled-start'.
2023-06-11 14:50:20 +00:00
(defun org-lint-mismatched-planning-repeaters (ast)
(org-element-map ast 'planning
(lambda (e)
(let* ((scheduled (org-element-property :scheduled e))
(deadline (org-element-property :deadline e))
(scheduled-repeater-type (org-element-property
:repeater-type scheduled))
(deadline-repeater-type (org-element-property
:repeater-type deadline))
(scheduled-repeater-value (org-element-property
:repeater-value scheduled))
(deadline-repeater-value (org-element-property
:repeater-value deadline)))
(when (and scheduled deadline
(memq scheduled-repeater-type '(cumulate catch-up))
(memq deadline-repeater-type '(cumulate catch-up))
(> scheduled-repeater-value 0)
(> deadline-repeater-value 0)
(not
(and
(eq scheduled-repeater-type deadline-repeater-type)
(eq (org-element-property :repeater-unit scheduled)
(org-element-property :repeater-unit deadline))
(eql scheduled-repeater-value deadline-repeater-value))))
(list
(org-element-property :begin e)
"Different repeaters in SCHEDULED and DEADLINE timestamps."))))))
(defun org-lint-misplaced-planning-info (_)
(let ((case-fold-search t)
reports)
(while (re-search-forward org-planning-line-re nil t)
(unless (org-element-type-p
(org-element-at-point)
'(comment-block example-block export-block planning
src-block verse-block))
(push (list (line-beginning-position) "Misplaced planning info line")
reports)))
reports))
(defun org-lint-incomplete-drawer (_)
(let (reports)
(while (re-search-forward org-drawer-regexp nil t)
(let ((name (org-trim (match-string-no-properties 0)))
(element (org-element-at-point)))
(pcase (org-element-type element)
(`drawer
;; Find drawer opening lines within non-empty drawers.
(let ((end (org-element-contents-end element)))
(when end
(while (re-search-forward org-drawer-regexp end t)
(let ((n (org-trim (match-string-no-properties 0))))
(push (list (line-beginning-position)
(format "Possible misleading drawer entry %S" n))
reports))))
(goto-char (org-element-end element))))
(`property-drawer
(goto-char (org-element-end element)))
((or `comment-block `example-block `export-block `src-block
`verse-block)
nil)
(_
;; Find drawer opening lines outside of any drawer.
(push (list (line-beginning-position)
(format "Possible incomplete drawer %S" name))
reports)))))
reports))
(defun org-lint-indented-diary-sexp (_)
(let (reports)
(while (re-search-forward "^[ \t]+%%(" nil t)
(unless (org-element-type-p
(org-element-at-point)
'(comment-block diary-sexp example-block export-block
src-block verse-block))
(push (list (line-beginning-position) "Possible indented diary-sexp")
reports)))
reports))
(defun org-lint-invalid-block (_)
(let ((case-fold-search t)
(regexp "^[ \t]*#\\+\\(BEGIN\\|END\\)\\(?::\\|_[^[:space:]]*\\)?[ \t]*")
reports)
(while (re-search-forward regexp nil t)
(let ((name (org-trim (buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(cond
((and (string-prefix-p "END" (match-string 1) t)
(not (eolp)))
(push (list (line-beginning-position)
(format "Invalid block closing line \"%s\"" name))
reports))
((not (org-element-type-p
(org-element-at-point)
'(center-block comment-block dynamic-block example-block
export-block quote-block special-block
src-block verse-block)))
(push (list (line-beginning-position)
(format "Possible incomplete block \"%s\""
name))
reports)))))
reports))
(defun org-lint-invalid-keyword-syntax (_)
(let ((regexp "^[ \t]*#\\+\\([^[:space:]:]*\\)\\(?: \\|$\\)")
(exception-re
(format "[ \t]*#\\+%s\\(\\[.*\\]\\)?:\\(?: \\|$\\)"
(regexp-opt org-element-dual-keywords)))
reports)
(while (re-search-forward regexp nil t)
(let ((name (match-string-no-properties 1)))
(unless (or (string-prefix-p "BEGIN" name t)
(string-prefix-p "END" name t)
(save-excursion
(forward-line 0)
(let ((case-fold-search t)) (looking-at exception-re))))
(push (list (match-beginning 0)
(format "Possible missing colon in keyword \"%s\"" name))
reports))))
reports))
(defun org-lint-invalid-image-alignment (ast)
(apply
#'nconc
(org-element-map ast 'paragraph
(lambda (p)
(let ((center-re ":center[[:space:]]+\\(\\S-+\\)")
(align-re ":align[[:space:]]+\\(\\S-+\\)")
(keyword-string
(car-safe (org-element-property :attr_org p)))
reports)
(when keyword-string
(when (and (string-match align-re keyword-string)
(not (member (match-string 1 keyword-string)
'("left" "center" "right"))))
(push
(list (org-element-begin p)
(format
"\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":align\"."
(match-string 1 keyword-string)))
reports))
(when (and (string-match center-re keyword-string)
(not (equal (match-string 1 keyword-string) "t")))
(push
(list (org-element-begin p)
(format
"\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":center\"."
(match-string 1 keyword-string)))
reports)))
reports)))))
(defun org-lint-extraneous-element-in-footnote-section (ast)
(org-element-map ast 'headline
(lambda (h)
(and (org-element-property :footnote-section-p h)
(org-element-map (org-element-contents h)
(cl-remove-if
(lambda (e)
(memq e '(comment comment-block footnote-definition
property-drawer section)))
org-element-all-elements)
(lambda (e)
(not (and (org-element-type-p e 'headline)
(org-element-property :commentedp e))))
nil t '(footnote-definition property-drawer))
(list (org-element-begin h)
"Extraneous elements in footnote section are not exported")))))
(defun org-lint-quote-section (ast)
(org-element-map ast '(headline inlinetask)
(lambda (h)
(let ((title (org-element-property :raw-value h)))
(and (or (string-prefix-p "QUOTE " title)
(string-prefix-p (concat org-comment-string " QUOTE ") title))
(list (org-element-begin h)
"Deprecated QUOTE section"))))))
(defun org-lint-file-application (ast)
(org-element-map ast 'link
(lambda (l)
(let ((app (org-element-property :application l)))
(and app
(list (org-element-begin l)
(format "Deprecated \"file+%s\" link type" app)))))))
(defun org-lint-percent-encoding-link-escape (ast)
(org-element-map ast 'link
(lambda (l)
(when (eq 'bracket (org-element-property :format l))
(let* ((uri (org-element-property :path l))
(start 0)
(obsolete-flag
(catch :obsolete
(while (string-match "%\\(..\\)?" uri start)
(setq start (match-end 0))
(unless (member (match-string 1 uri) '("25" "5B" "5D" "20"))
(throw :obsolete nil)))
(string-match-p "%" uri))))
(when obsolete-flag
(list (org-element-begin l)
"Link escaped with obsolete percent-encoding syntax")))))))
(defun org-lint-wrong-header-argument (ast)
(let* ((reports)
(verify
(lambda (datum language headers)
(let ((allowed
;; If LANGUAGE is specified, restrict allowed
;; headers to both LANGUAGE-specific and default
;; ones. Otherwise, accept headers from any loaded
;; language.
(append
org-babel-header-arg-names
(cl-mapcan
(lambda (l)
(let ((v (intern (format "org-babel-header-args:%s" l))))
(and (boundp v) (mapcar #'car (symbol-value v)))))
(if language (list language)
(mapcar #'car org-babel-load-languages))))))
(dolist (header headers)
(let ((h (symbol-name (car header)))
(p (or (org-element-post-affiliated datum)
(org-element-begin datum))))
(cond
((not (string-prefix-p ":" h))
(push
(list p
(format "Missing colon in header argument \"%s\"" h))
reports))
((assoc-string (substring h 1) allowed))
(t (push (list p (format "Unknown header argument \"%s\"" h))
reports)))))))))
(org-element-map ast '(babel-call inline-babel-call inline-src-block keyword
node-property src-block)
(lambda (datum)
(pcase (org-element-type datum)
((or `babel-call `inline-babel-call)
(funcall verify
datum
nil
(cl-mapcan #'org-babel-parse-header-arguments
(list
(org-element-property :inside-header datum)
(org-element-property :end-header datum)))))
(`inline-src-block
(funcall verify
datum
(org-element-property :language datum)
(org-babel-parse-header-arguments
(org-element-property :parameters datum))))
(`keyword
(when (string= (org-element-property :key datum) "PROPERTY")
(let ((value (org-element-property :value datum)))
(when (or (string-match "\\`header-args\\(?::\\(\\S-+\\)\\)?\\+ *"
value)
(string-match "\\`header-args\\(?::\\(\\S-+\\)\\)? *"
value))
(funcall verify
datum
(match-string 1 value)
(org-babel-parse-header-arguments
(substring value (match-end 0))))))))
(`node-property
(let ((key (org-element-property :key datum)))
(when (let ((case-fold-search t))
(or (string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?\\+"
key)
(string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?"
key)))
(funcall verify
datum
(match-string 1 key)
(org-babel-parse-header-arguments
(org-element-property :value datum))))))
(`src-block
(funcall verify
datum
(org-element-property :language datum)
(cl-mapcan #'org-babel-parse-header-arguments
(cons (org-element-property :parameters datum)
(org-element-property :header datum))))))))
reports))
(defun org-lint-empty-header-argument (ast)
(let* (reports)
(org-element-map ast '(babel-call inline-babel-call inline-src-block src-block)
(lambda (datum)
(let ((headers
(pcase (org-element-type datum)
((or `babel-call `inline-babel-call)
(cl-mapcan
(lambda (header) (org-babel-parse-header-arguments header 'no-eval))
(list
(org-element-property :inside-header datum)
(org-element-property :end-header datum))))
(`inline-src-block
(org-babel-parse-header-arguments
(org-element-property :parameters datum)
'no-eval))
(`src-block
(cl-mapcan
(lambda (header) (org-babel-parse-header-arguments header 'no-eval))
(cons (org-element-property :parameters datum)
(org-element-property :header datum)))))))
(dolist (header headers)
(when (not (cdr header))
(push
(list
(or (org-element-post-affiliated datum)
(org-element-begin datum))
(format "Empty value in header argument \"%s\"" (symbol-name (car header))))
reports))))))
reports))
(defun org-lint-wrong-header-value (ast)
(let (reports)
(org-element-map ast
'(babel-call inline-babel-call inline-src-block src-block)
(lambda (datum)
(let* ((type (org-element-type datum))
(language (org-element-property :language datum))
(allowed-header-values
(append (and language
(let ((v (intern (concat "org-babel-header-args:"
language))))
(and (boundp v) (symbol-value v))))
org-babel-common-header-args-w-values))
(datum-header-values
(org-babel-parse-header-arguments
(org-trim
(pcase type
(`src-block
(mapconcat
#'identity
(cons (org-element-property :parameters datum)
(org-element-property :header datum))
" "))
(`inline-src-block
(or (org-element-property :parameters datum) ""))
(_
(concat
(org-element-property :inside-header datum)
" "
(org-element-property :end-header datum))))))))
(dolist (header datum-header-values)
(let ((allowed-values
(cdr (assoc-string (substring (symbol-name (car header)) 1)
allowed-header-values))))
(unless (memq allowed-values '(:any nil))
(let ((values (cdr header))
groups-alist)
(dolist (v (if (stringp values) (split-string values)
(list values)))
(let ((valid-value nil))
(catch 'exit
(dolist (group allowed-values)
(cond
((not (funcall
(if (stringp v) #'assoc-string #'assoc)
v group))
(when (memq :any group)
(setf valid-value t)
(push (cons group v) groups-alist)))
((assq group groups-alist)
(push
(list
(or (org-element-post-affiliated datum)
(org-element-begin datum))
(format
"Forbidden combination in header \"%s\": %s, %s"
(car header)
(cdr (assq group groups-alist))
v))
reports)
(throw 'exit nil))
(t (push (cons group v) groups-alist)
(setf valid-value t))))
(unless valid-value
(push
(list
(or (org-element-post-affiliated datum)
(org-element-begin datum))
(format "Unknown value \"%s\" for header \"%s\""
v
(car header)))
reports))))))))))))
reports))
(defun org-lint-named-result (ast)
(org-element-map ast org-element-all-elements
(lambda (el)
(when-let* ((result (org-element-property :results el))
(result-name (org-element-property :name el))
(origin-block
(if (org-string-nw-p (car result))
(condition-case _
(org-export-resolve-link (car result) `(:parse-tree ,ast))
(org-link-broken nil))
(org-export-get-previous-element el nil))))
(when (org-element-type-p origin-block 'src-block)
(list (org-element-begin el)
(format "Links to \"%s\" will not be valid during export unless the parent source block has :exports results or both" result-name)))))))
(defun org-lint-spurious-colons (ast)
(org-element-map ast '(headline inlinetask)
(lambda (h)
(when (member "" (org-element-property :tags h))
(list (org-element-begin h)
"Tags contain a spurious colon")))))
(defun org-lint-non-existent-bibliography (ast)
(org-element-map ast 'keyword
(lambda (k)
(when (equal "BIBLIOGRAPHY" (org-element-property :key k))
(let ((file (org-strip-quotes (org-element-property :value k))))
(and (not (file-remote-p file))
(not (file-exists-p file))
(list (org-element-begin k)
(format "Non-existent bibliography %S" file))))))))
(defun org-lint-missing-print-bibliography (ast)
(and (org-element-map ast 'citation #'identity nil t)
(not (org-element-map ast 'keyword
(lambda (k)
(equal "PRINT_BIBLIOGRAPHY" (org-element-property :key k)))
nil t))
(list
(list (point-max) "Possibly missing \"PRINT_BIBLIOGRAPHY\" keyword"))))
(defun org-lint-invalid-cite-export-declaration (ast)
(org-element-map ast 'keyword
(lambda (k)
(when (equal "CITE_EXPORT" (org-element-property :key k))
(let ((value (org-element-property :value k))
(source (org-element-begin k)))
(if (equal value "")
(list source "Missing export processor name")
(condition-case _
(pcase (org-cite-read-processor-declaration value)
(`(,(and (pred symbolp) name)
,(pred string-or-null-p)
,(pred string-or-null-p))
(unless (or (org-cite-get-processor name)
(progn
(org-cite-try-load-processor name)
(org-cite-get-processor name)))
(list source (format "Unknown cite export processor %S" name))))
(_
(list source "Invalid cite export processor declaration")))
(error
(list source "Invalid cite export processor declaration")))))))))
(defun org-lint-incomplete-citation (ast)
(org-element-map ast 'plain-text
(lambda (text)
(and (string-match-p org-element-citation-prefix-re text)
;; XXX: The code below signals the error at the beginning
;; of the paragraph containing the faulty object. It is
;; not very accurate but may be enough for now.
(list (org-element-contents-begin
(org-element-parent text))
"Possibly incomplete citation markup")))))
(defun org-lint-item-number (ast)
(org-element-map ast 'item
(lambda (item)
(unless (org-element-property :counter item)
(when-let* ((bullet (org-element-property :bullet item))
(bullet-number
(cond
((string-match "[A-Za-z]" bullet)
(- (string-to-char (upcase (match-string 0 bullet)))
64))
((string-match "[0-9]+" bullet)
(string-to-number (match-string 0 bullet)))))
(true-number
(org-list-get-item-number
(org-element-begin item)
(org-element-property :structure item)
(org-list-prevs-alist (org-element-property :structure item))
(org-list-parents-alist (org-element-property :structure item)))))
(unless (equal bullet-number (car (last true-number)))
(list
(org-element-begin item)
(format "Bullet counter \"%s\" is not the same with item position %d. Consider adding manual [@%d] counter."
bullet (car (last true-number)) bullet-number))))))))
(defun org-lint-LaTeX-$ (ast)
"Report semi-obsolete $...$ LaTeX fragments.
AST is the buffer parse tree."
(org-element-map ast 'latex-fragment
(lambda (fragment)
(and (string-match-p "^[$][^$]" (org-element-property :value fragment))
(list (org-element-begin fragment)
"Potentially confusing LaTeX fragment format. Prefer using more reliable \\(...\\)")))))
(defun org-lint-LaTeX-$-ambiguous (_)
"Report LaTeX fragment-like text.
AST is the buffer parse tree."
(org-with-wide-buffer
(let ((ambiguous-latex-re (rx "$." digit))
report context)
(while (re-search-forward ambiguous-latex-re nil t)
(setq context (org-element-context))
(when (or (eq 'latex-fragment (org-element-type context))
(memq 'latex-fragment (org-element-restriction context)))
(push
(list
(point)
"$ symbol potentially matching LaTeX fragment boundary. Consider using \\dollar entity.")
report)))
report)))
(defun org-lint-timestamp-syntax (ast)
"Report malformed timestamps.
AST is the buffer parse tree."
(org-element-map ast 'timestamp
(lambda (timestamp)
(let ((expected (org-element-interpret-data timestamp))
(actual (buffer-substring-no-properties
(org-element-property :begin timestamp)
(org-element-property :end timestamp))))
(unless (equal expected actual)
(list (org-element-property :begin timestamp)
(format "Potentially malformed timestamp %s. Parsed as: %s" actual expected)))))))
(defun org-lint-clock-syntax (ast)
"Report malformed clocks.
AST is the buffer parse tree."
(org-element-map ast 'clock
(lambda (clock)
(let ((expected (string-trim-right (org-element-interpret-data clock)))
(actual (string-trim
(buffer-substring-no-properties
(org-element-property :begin clock)
(org-element-property :end clock)))))
(unless (equal expected actual)
(list (org-element-property :begin clock)
(format "Potentially malformed CLOCK: line
%s
Parsed as: %s" actual expected)))))))
(defun org-lint-inactive-planning (ast)
"Report inactive timestamp in SCHEDULED/DEADLINE.
AST is the buffer parse tree."
(org-element-map ast 'planning
(lambda (planning)
(let ((scheduled (org-element-property :scheduled planning))
(deadline (org-element-property :deadline planning)))
(cond
((memq (org-element-property :type scheduled) '(inactive inactive-range))
(list (org-element-begin planning) "Inactive timestamp in SCHEDULED will not appear in agenda."))
((memq (org-element-property :type deadline) '(inactive inactive-range))
(list (org-element-begin planning) "Inactive timestamp in DEADLINE will not appear in agenda."))
(t nil))))))
(defvar org-beamer-frame-environment) ; defined in ox-beamer.el
(defun org-lint-beamer-frame (ast)
"Check for occurrences of begin or end frame."
(require 'ox-beamer)
(org-with-point-at ast
(goto-char (point-min))
(let (result)
(while (re-search-forward
(concat "\\\\\\(begin\\|end\\){" org-beamer-frame-environment "}") nil t)
(push (list (match-beginning 0) "Beamer frame name may cause error when exporting. Consider customizing `org-beamer-frame-environment'.") result))
result)))
;;; Checkers declaration
(org-lint-add-checker 'misplaced-heading
"Report accidentally misplaced heading lines."
#'org-lint-misplaced-heading :trust 'low)
(org-lint-add-checker 'duplicate-custom-id
"Report duplicates CUSTOM_ID properties"
#'org-lint-duplicate-custom-id
:categories '(link))
(org-lint-add-checker 'duplicate-name
"Report duplicate NAME values"
#'org-lint-duplicate-name
:categories '(babel 'link))
(org-lint-add-checker 'duplicate-target
"Report duplicate targets"
#'org-lint-duplicate-target
:categories '(link))
(org-lint-add-checker 'duplicate-footnote-definition
"Report duplicate footnote definitions"
#'org-lint-duplicate-footnote-definition
:categories '(footnote))
(org-lint-add-checker 'orphaned-affiliated-keywords
"Report orphaned affiliated keywords"
#'org-lint-orphaned-affiliated-keywords
:trust 'low)
(org-lint-add-checker 'combining-keywords-with-affiliated
"Report independent keywords preceding affiliated keywords."
#'org-lint-regular-keyword-before-affiliated
:trust 'low)
(org-lint-add-checker 'obsolete-affiliated-keywords
"Report obsolete affiliated keywords"
#'org-lint-obsolete-affiliated-keywords
:categories '(obsolete))
(org-lint-add-checker 'deprecated-export-blocks
"Report deprecated export block syntax"
#'org-lint-deprecated-export-blocks
:trust 'low :categories '(obsolete export))
(org-lint-add-checker 'deprecated-header-syntax
"Report deprecated Babel header syntax"
#'org-lint-deprecated-header-syntax
:trust 'low :categories '(obsolete babel))
(org-lint-add-checker 'missing-language-in-src-block
"Report missing language in source blocks"
#'org-lint-missing-language-in-src-block
:categories '(babel))
(org-lint-add-checker 'suspicious-language-in-src-block
"Report suspicious language in source blocks"
#'org-lint-suspicious-language-in-src-block
:trust 'low :categories '(babel))
(org-lint-add-checker 'missing-backend-in-export-block
"Report missing backend in export blocks"
#'org-lint-missing-backend-in-export-block
:categories '(export))
(org-lint-add-checker 'invalid-babel-call-block
"Report invalid Babel call blocks"
#'org-lint-invalid-babel-call-block
:categories '(babel))
(org-lint-add-checker 'wrong-header-argument
"Report wrong babel headers"
#'org-lint-wrong-header-argument
:categories '(babel))
(org-lint-add-checker 'wrong-header-value
"Report invalid value in babel headers"
#'org-lint-wrong-header-value
:categories '(babel) :trust 'low)
(org-lint-add-checker 'named-result
"Report results evaluation with #+name keyword."
#'org-lint-named-result
:categories '(babel) :trust 'high)
(org-lint-add-checker 'empty-header-argument
"Report empty values in babel headers"
#'org-lint-empty-header-argument
:categories '(babel) :trust 'low)
(org-lint-add-checker 'deprecated-category-setup
"Report misuse of CATEGORY keyword"
#'org-lint-deprecated-category-setup
:categories '(obsolete))
(org-lint-add-checker 'invalid-coderef-link
"Report \"coderef\" links with unknown destination"
#'org-lint-invalid-coderef-link
:categories '(link))
(org-lint-add-checker 'invalid-custom-id-link
"Report \"custom-id\" links with unknown destination"
#'org-lint-invalid-custom-id-link
:categories '(link))
(org-lint-add-checker 'invalid-fuzzy-link
"Report \"fuzzy\" links with unknown destination"
#'org-lint-invalid-fuzzy-link
:categories '(link))
(org-lint-add-checker 'invalid-id-link
"Report \"id\" links with unknown destination"
#'org-lint-invalid-id-link
:categories '(link))
(org-lint-add-checker 'trailing-bracket-after-link
"Report potentially confused trailing ']' after link."
#'org-lint-confusing-brackets
:categories '(link) :trust 'low)
(org-lint-add-checker 'unclosed-brackets-in-link-description
"Report potentially confused trailing ']' after link."
#'org-lint-brackets-inside-description
:categories '(link) :trust 'low)
(org-lint-add-checker 'link-to-local-file
"Report links to non-existent local files"
#'org-lint-link-to-local-file
:categories '(link) :trust 'low)
(org-lint-add-checker 'non-existent-setupfile-parameter
"Report SETUPFILE keywords with non-existent file parameter"
#'org-lint-non-existent-setupfile-parameter
:trust 'low)
(org-lint-add-checker 'wrong-include-link-parameter
"Report INCLUDE keywords with misleading link parameter"
#'org-lint-wrong-include-link-parameter
:categories '(export) :trust 'low)
(org-lint-add-checker 'obsolete-include-markup
"Report obsolete markup in INCLUDE keyword"
#'org-lint-obsolete-include-markup
:categories '(obsolete export) :trust 'low)
(org-lint-add-checker 'unknown-options-item
"Report unknown items in OPTIONS keyword"
#'org-lint-unknown-options-item
:categories '(export) :trust 'low)
(org-lint-add-checker 'misspelled-export-option
"Report potentially misspelled export options in properties."
#'org-lint-export-option-keywords
:categories '(export) :trust 'low)
(org-lint-add-checker 'invalid-macro-argument-and-template
"Report spurious macro arguments or invalid macro templates"
#'org-lint-invalid-macro-argument-and-template
:categories '(export) :trust 'low)
(org-lint-add-checker 'special-property-in-properties-drawer
"Report special properties in properties drawers"
#'org-lint-special-property-in-properties-drawer
:categories '(properties))
(org-lint-add-checker 'obsolete-properties-drawer
"Report obsolete syntax for properties drawers"
#'org-lint-obsolete-properties-drawer
:categories '(obsolete properties))
(org-lint-add-checker 'invalid-effort-property
"Report invalid duration in EFFORT property"
#'org-lint-invalid-effort-property
:categories '(properties))
org-id.el: Add search strings, inherit parent IDs * lisp/ol.el (org-store-link): Refactor org-id links to use standard `org-store-link-functions'. (org-link-search): Create new headings at appropriate level. (org-link-precise-link-target): New function extracting logic to identify a precise link target, e.g. a heading, named object, or text search. (org-link-try-link-store-functions): Extract logic to call external link store functions. Pass them a new `interactive?' argument. * lisp/ol-bbdb.el (org-bbdb-store-link): * lisp/ol-bibtex.el (org-bibtex-store-link): * lisp/ol-docview.el (org-docview-store-link): * lisp/ol-eshell.el (org-eshell-store-link): * lisp/ol-eww.el (org-eww-store-link): * lisp/ol-gnus.el (org-gnus-store-link): * lisp/ol-info.el (org-info-store-link): * lisp/ol-irc.el (org-irc-store-link): * lisp/ol-man.el (org-man-store-link): * lisp/ol-mhe.el (org-mhe-store-link): * lisp/ol-rmail.el (org-rmail-store-link): Accept optional arg. * lisp/org-id.el (org-id-link-consider-parent-id): New option to allow a parent heading with an id to be considered as a link target. (org-id-link-use-context): New option to add context to org-id links. (org-id-get): Add optional `inherit' argument which considers parents' IDs if the current entry does not have one. (org-id-store-link): Consider IDs of parent headings as link targets when current heading has no ID and `org-id-link-consider-parent-id' is set. Add a search string to the link when enabled. (org-id-store-link-maybe): Function set as :store option for custom id link property. Move logic from `org-store-link' here to determine when an org-id link should be stored using `org-id-store-link'. (org-id-open): Recognise search strings after "::" in org-id links. * lisp/org-lint.el: Add checker for "::" in ID properties. * testing/lisp/test-ol.el: Add tests for `org-link-precise-link-target' and `org-id-store-link' functions, testing new options. * doc/org-manual.org: Update documentation about links. * etc/ORG-NEWS: Document changes and new options. These feature allows for more precise links when using org-id to link to org headings, without requiring every single headline to have an id. Link: https://list.orgmode.org/118435e8-0b20-46fd-af6a-88de8e19fac6@app.fastmail.com/
2023-11-19 14:52:05 +00:00
(org-lint-add-checker 'invalid-id-property
"Report search string delimiter \"::\" in ID property"
#'org-lint-invalid-id-property
:categories '(properties))
(org-lint-add-checker 'undefined-footnote-reference
"Report missing definition for footnote references"
#'org-lint-undefined-footnote-reference
:categories '(footnote))
(org-lint-add-checker 'unreferenced-footnote-definition
"Report missing reference for footnote definitions"
#'org-lint-unreferenced-footnote-definition
:categories '(footnote))
(org-lint-add-checker 'extraneous-element-in-footnote-section
"Report non-footnote definitions in footnote section"
#'org-lint-extraneous-element-in-footnote-section
:categories '(footnote))
(org-lint-add-checker 'invalid-keyword-syntax
"Report probable invalid keywords"
#'org-lint-invalid-keyword-syntax
:trust 'low)
(org-lint-add-checker 'invalid-image-alignment
"Report unsupported align attribute for keyword"
#'org-lint-invalid-image-alignment
:trust 'high)
(org-lint-add-checker 'invalid-block
"Report invalid blocks"
#'org-lint-invalid-block
:trust 'low)
ox-icalendar: Add support for unscheduled and repeating TODOs * lisp/ox-icalendar.el (org-icalendar-todo-unscheduled-start): New customization to control the exported start time of unscheduled tasks. (org-icalendar--rrule): Helper function for RRULE export. (org-icalendar--vevent): Use the new helper function for RRULE. (org-icalendar--repeater-type): Helper function to get the repeater type, and display warning if not supported. (org-icalendar--vtodo): Change how unscheduled TODOs are handled using the new customization option. Export SCHEDULED and DEADLINE repeaters. In case of SCHEDULED repeater and a DEADLINE without repeater, treat DEADLINE as RRULE UNTIL. Emit a warning for tricky edge cases that are not yet implemented. * testing/lisp/test-ox-icalendar.el (test-ox-icalendar/todo-repeater-shared): Test for exporting shared SCHEDULED/DEADLINE repeater. (test-ox-icalendar/todo-repeating-deadline-warndays): Test using warning days as DTSTART of repeating deadline. (test-ox-icalendar/todo-repeater-until): Test using DEADLINE as RRULE UNTIL. (test-ox-icalendar/todo-repeater-until-utc): Test RRULE UNTIL is in UTC format when DTSTART is not in local time format. (test-ox-icalendar/warn-unsupported-repeater): Unit test to warn for unsupported repeater types. * lisp/org-lint.el (org-lint-mismatched-planning-repeaters): Add lint for mismatched SCHEDULED and DEADLINE repeaters. * testing/lisp/test-org-lint.el (test-org-lint/mismatched-planning-repeaters): Add test for linting of mismatched SCHEDULED and DEADLINE repeaters. * doc/org-manual.org (iCalendar Export): Add link to new variable `org-icalendar-todo-unscheduled-start'.
2023-06-11 14:50:20 +00:00
(org-lint-add-checker 'mismatched-planning-repeaters
"Report mismatched repeaters in planning info line"
#'org-lint-mismatched-planning-repeaters
:trust 'low)
(org-lint-add-checker 'misplaced-planning-info
"Report misplaced planning info line"
#'org-lint-misplaced-planning-info
:trust 'low)
(org-lint-add-checker 'incomplete-drawer
"Report probable incomplete drawers"
#'org-lint-incomplete-drawer
:trust 'low)
(org-lint-add-checker 'indented-diary-sexp
"Report probable indented diary-sexps"
#'org-lint-indented-diary-sexp
:trust 'low)
(org-lint-add-checker 'quote-section
"Report obsolete QUOTE section"
#'org-lint-quote-section
:categories '(obsolete) :trust 'low)
(org-lint-add-checker 'file-application
"Report obsolete \"file+application\" link"
#'org-lint-file-application
:categories '(link obsolete))
(org-lint-add-checker 'percent-encoding-link-escape
"Report obsolete escape syntax in links"
#'org-lint-percent-encoding-link-escape
:categories '(link obsolete) :trust 'low)
(org-lint-add-checker 'spurious-colons
"Report spurious colons in tags"
#'org-lint-spurious-colons
:categories '(tags))
(org-lint-add-checker 'non-existent-bibliography
"Report invalid bibliography file"
#'org-lint-non-existent-bibliography
:categories '(cite))
(org-lint-add-checker 'missing-print-bibliography
"Report missing \"print_bibliography\" keyword"
#'org-lint-missing-print-bibliography
:categories '(cite))
(org-lint-add-checker 'invalid-cite-export-declaration
"Report invalid value for \"cite_export\" keyword"
#'org-lint-invalid-cite-export-declaration
:categories '(cite))
(org-lint-add-checker 'incomplete-citation
"Report incomplete citation object"
#'org-lint-incomplete-citation
:categories '(cite) :trust 'low)
(org-lint-add-checker 'item-number
"Report inconsistent item numbers in lists"
#'org-lint-item-number
:categories '(plain-list))
(org-lint-add-checker 'LaTeX-$
"Report potentially confusing $...$ LaTeX markup."
#'org-lint-LaTeX-$
:categories '(markup))
(org-lint-add-checker 'LaTeX-$
"Report $ that might be treated as LaTeX fragment boundary."
#'org-lint-LaTeX-$-ambiguous
:categories '(markup) :trust 'low)
(org-lint-add-checker 'beamer-frame
"Report that frame text contains beamer frame environment."
#'org-lint-beamer-frame
:categories '(export) :trust 'low)
(org-lint-add-checker 'timestamp-syntax
"Report malformed timestamps."
#'org-lint-timestamp-syntax
:categories '(timestamp) :trust 'low)
(org-lint-add-checker 'clock-syntax
"Report malformed clocks."
#'org-lint-clock-syntax
:categories '(timestamp) :trust 'low)
(org-lint-add-checker 'planning-inactive
"Report inactive timestamps in SCHEDULED/DEADLINE."
#'org-lint-inactive-planning
:categories '(timestamp) :trust 'high)
(provide 'org-lint)
Tiny formatting fixes * lisp/ox.el (org-export-table-dimensions): * lisp/ox-texinfo.el (org-texinfo-template): * lisp/ox-md.el (org-md-link): * lisp/ox-icalendar.el (org-icalendar-use-UTC-date-time-p): * lisp/ox-ascii.el (org-ascii-fixed-width): * lisp/org.el (org-context): * lisp/org-table.el (org-table-eval-formula) (org-table-export): * lisp/org-refile.el: * lisp/org-plot.el (org-plot/gnuplot-to-grid-data): * lisp/org-num.el (org-num): * lisp/org-mouse.el (org-mouse-popup-global-menu) (org-mouse-context-menu): * lisp/org-macro.el (org-macro): * lisp/org-lint.el (org-lint): * lisp/org-keys.el (org-keys): * lisp/org-duration.el: * lisp/org-clock.el (org-clock-get-last-clock-out-time) (org-clock-update-mode-line, org-find-open-clocks): * lisp/org-agenda.el (org-diary) (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item) (org-agenda-highlight-todo, org-cmp-alpha) (org-agenda-filter-by-category): * lisp/ol.el (org-link-expand-abbrev, ol): * lisp/ol-docview.el (ol-docview): * lisp/ol-bibtex.el (org-execute-file-search-in-bibtex) (org-bibtex, org-bibtex-read): * lisp/ol-bbdb.el (org-bbdb-anniversary-description): * lisp/ob-tangle.el (org-babel-tangle-jump-to-org): * lisp/ob-table.el (org-babel-table-truncate-at-newline): * lisp/ob-stan.el: * lisp/ob-sqlite.el (org-babel-sqlite-table-or-scalar): * lisp/ob-sql.el: * lisp/ob-shen.el: * lisp/ob-shell.el (org-babel-prep-session:shell) (org-babel-prep-session:shell): * lisp/ob-sed.el (org-babel-execute:sed) (org-babel-execute:sed): * lisp/ob-screen.el: * lisp/ob-sass.el: * lisp/ob-ruby.el (org-babel-prep-session:ruby) (org-babel-prep-session:ruby): * lisp/ob-ref.el (org-babel-ref-resolve, ob-ref): * lisp/ob-python.el (org-babel-prep-session:python) (org-babel-prep-session:python): * lisp/ob-plantuml.el: * lisp/ob-picolisp.el: * lisp/ob-perl.el: * lisp/ob-org.el: * lisp/ob-octave.el (org-babel-prep-session:octave) (org-babel-prep-session:octave) (org-babel-octave-evaluate-session): * lisp/ob-ocaml.el: * lisp/ob-mscgen.el (org-babel-execute:mscgen) (org-babel-execute:mscgen): * lisp/ob-maxima.el: (ob-maxima): * lisp/ob-matlab.el: * lisp/ob-makefile.el: * lisp/ob-lua.el (org-babel-prep-session:lua) (org-babel-prep-session:lua): * lisp/ob-lisp.el: * lisp/ob-ledger.el: * lisp/ob-latex.el (org-babel-expand-body:latex) (org-babel-expand-body:latex, ob-latex): * lisp/ob-js.el: * lisp/ob-java.el: * lisp/ob-io.el (org-babel-prep-session:io) (org-babel-prep-session:io): * lisp/ob-hledger.el: * lisp/ob-haskell.el: * lisp/ob-groovy.el (org-babel-groovy-wrapper-method) (org-babel-groovy-evaluate): * lisp/ob-gnuplot.el: * lisp/ob-fortran.el (org-babel-expand-body:fortran) (org-babel-expand-body:fortran): * lisp/ob-forth.el (org-babel-forth-session-execute): * lisp/ob-exp.el (ob-exp): * lisp/ob-eval.el: * lisp/ob-emacs-lisp.el: * lisp/ob-ebnf.el: * lisp/ob-dot.el: * lisp/ob-ditaa.el: * lisp/ob-css.el: * lisp/ob-core.el (org-babel-put-rownames): * lisp/ob-coq.el: * lisp/ob-comint.el: * lisp/ob-calc.el: * lisp/ob-awk.el: * lisp/ob-asymptote.el: * lisp/ob-abc.el: * lisp/ob-R.el (org-babel-prep-session:R): Formatting fixes.
2020-02-18 21:57:37 +00:00
;; Local variables:
;; generated-autoload-file: "org-loaddefs.el"
;; End:
;;; org-lint.el ends here