branch: elpa/flycheck
commit 367551e632f534edb67b6cc1892e92a04e7c30c5
Author: takeokunn <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Add org-lint checker for Org mode files
Implements org-lint integration to provide real-time Org mode syntax
and style checking. The checker runs org-lint in an Emacs subprocess,
detecting issues such as invalid links, dead links, duplicate IDs, and
other common Org mode problems.
Key features:
- Secure subprocess execution with minimal variable inheritance
- Automatic enablement when org-lint is available (Org mode 9.0+)
- Works with standard Org installations without additional configuration
- Comprehensive error handling with graceful degradation
Security considerations:
- Excludes load-path to prevent code injection and information disclosure
- Uses org-id-locations-file instead of org-id-locations to avoid ARG_MAX
- Follows security-first design principles
Fixes #1757
---
CHANGES.rst | 3 ++
doc/languages.rst | 38 +++++++++++++++++++
flycheck.el | 70 +++++++++++++++++++++++++++++++++++
test/flycheck-test.el | 24 ++++++++++++
test/resources/language/org/lint.org | 40 ++++++++++++++++++++
test/resources/language/org/valid.org | 20 ++++++++++
6 files changed, 195 insertions(+)
diff --git a/CHANGES.rst b/CHANGES.rst
index d5c1c8d378b..3e7b6128361 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,9 @@ New Features
- [#2132]: Add the ``flycheck-shellcheck-infer-shell`` option to the
``sh-shellcheck`` checker.
- [#2139]: Add compatibility with Proselint 0.16.
+- [#1757]: Add :ref:`org-lint <flycheck-checkers-org-lint>` checker for Org
mode files.
+ The checker uses Emacs' built-in ``org-lint`` command to detect issues such
+ as invalid links, dead links, and duplicate IDs.
-----------
Bugs fixed
diff --git a/doc/languages.rst b/doc/languages.rst
index 8946f8476eb..4779170f8d9 100644
--- a/doc/languages.rst
+++ b/doc/languages.rst
@@ -910,6 +910,44 @@ to view the docstring of the syntax checker. Likewise,
you may use
.. _statix: https://github.com/nerdypepper/statix
+.. supported-language:: Org mode
+
+ Flycheck supports checking Org mode files with the built-in ``org-lint``
command.
+
+ .. syntax-checker:: org-lint
+
+ .. _flycheck-checkers-org-lint:
+
+ An Org mode syntax and style checker using ``org-lint``.
+
+ The checker runs ``org-lint`` in an Emacs subprocess to detect issues
such
+ as:
+
+ - Invalid links
+ - Dead links
+ - Duplicate IDs
+ - Invalid keywords
+ - Special characters in links
+ - And more...
+
+ The checker automatically inherits your Org mode configuration, including
+ ``org-directory`` and ``org-id-locations-file``. Note that ``load-path``
is
+ not inherited for security reasons to prevent potential code injection.
+ Org should be installed in a standard location that Emacs can find
without
+ a custom ``load-path``.
+
+ The checker is enabled by default when ``org-lint`` is available (Org
mode
+ 9.0 or later).
+
+ See the ``org-lint`` documentation in Org mode for details about the
checks
+ performed.
+
+ .. note::
+
+ The checker does not inherit ``org-id-locations`` because this
variable
+ can contain thousands of entries and exceed shell argument limits. The
+ ``org-id-locations-file`` is used instead.
+
.. supported-language:: Opam
.. syntax-checker:: opam
diff --git a/flycheck.el b/flycheck.el
index b6aa9ebfa58..a4ff4a23fd1 100644
--- a/flycheck.el
+++ b/flycheck.el
@@ -185,6 +185,7 @@
nix
nix-linter
opam
+ org-lint
perl
perl-perlcritic
perl-perlimports
@@ -8923,6 +8924,24 @@ Variables are taken from
`flycheck-emacs-lisp-checkdoc-variables'."
,@(seq-map (lambda (opt) `(setq-default ,opt ',(symbol-value opt)))
(seq-filter #'boundp flycheck-emacs-lisp-checkdoc-variables))))
+(defconst flycheck-org-lint-variables
+ '(org-directory
+ org-id-locations-file) ; File path only, not contents
+ "Variables inherited by the org-lint subprocess.
+
+Note: We do NOT include `load-path' to prevent potential code injection
+and information disclosure. Org should be installed in a standard
+location that Emacs can find without a custom load-path.
+
+We also do not include `org-id-locations' because it can contain
+thousands of entries and exceed shell argument limits (ARG_MAX).")
+
+(defun flycheck-org-lint-variables-form ()
+ "Make a sexp to pass relevant variables to an org-lint subprocess."
+ `(progn
+ ,@(seq-map (lambda (opt) `(setq-default ,opt ',(symbol-value opt)))
+ (seq-filter #'boundp flycheck-org-lint-variables))))
+
(flycheck-define-checker emacs-lisp-checkdoc
"An Emacs Lisp style checker using CheckDoc.
@@ -8937,10 +8956,61 @@ The checker runs `checkdoc-current-buffer'."
:modes (emacs-lisp-mode)
:enabled flycheck--emacs-lisp-checkdoc-enabled-p)
+(defconst flycheck-org-lint-form
+ (flycheck-prepare-emacs-lisp-form
+ (with-demoted-errors "Org-lint error: %S"
+ (require 'org)
+ (let ((source (car command-line-args-left))
+ (process-default-directory default-directory))
+ (with-temp-buffer
+ (insert-file-contents source 'visit)
+ (setq buffer-file-name source)
+ (setq default-directory process-default-directory)
+ (delay-mode-hooks (org-mode))
+ (setq delayed-mode-hooks nil)
+ (dolist (err (org-lint))
+ (pcase err
+ (`(,_n [,line ,_trust ,desc ,_checker])
+ (princ (format "%s:%s: %s\n" source line desc)))
+ (_
+ (princ (format "%s:1: Unexpected org-lint format: %S\n" source
err))))))))))
+
+(defun flycheck-org-lint-available-p ()
+ "Check if org-lint is available."
+ (and (fboundp 'org-lint)
+ (require 'org nil 'no-error)))
+
(dolist (checker '(emacs-lisp emacs-lisp-checkdoc))
(setf (car (flycheck-checker-get checker 'command))
flycheck-this-emacs-executable))
+(flycheck-define-checker org-lint
+ "An Org mode syntax checker using `org-lint'.
+
+The checker runs `org-lint' in an Emacs subprocess."
+ :command ("emacs" (eval flycheck-emacs-args)
+ "--eval" (eval (flycheck-sexp-to-string
+ (flycheck-org-lint-variables-form)))
+ "--eval" (eval flycheck-org-lint-form)
+ "--" source)
+ :error-patterns
+ ((info line-start (file-name) ":" line ": " (message) line-end))
+ :modes (org-mode)
+ :enabled (lambda () (flycheck-org-lint-available-p))
+ :verify (lambda (_)
+ (let ((org-version (when (require 'org nil 'no-error)
+ (org-version))))
+ (list (flycheck-verification-result-new
+ :label "Org-lint available"
+ :message (if (fboundp 'org-lint)
+ (format "yes (Org %s)" org-version)
+ "no")
+ :face (if (fboundp 'org-lint) 'success 'warning))))))
+
+;; Set org-lint to use the current Emacs
+(setf (car (flycheck-checker-get 'org-lint 'command))
+ flycheck-this-emacs-executable)
+
(defun flycheck-ember-template--check-for-config (&rest _ignored)
"Check the required config file is available up the file system."
(and buffer-file-name
diff --git a/test/flycheck-test.el b/test/flycheck-test.el
index aaee28b1bab..28a3e53b57b 100644
--- a/test/flycheck-test.el
+++ b/test/flycheck-test.el
@@ -5187,6 +5187,30 @@ The manifest path is relative to
'(2 6 "Field does not exist: flat" :checker jsonnet
:end-line 2 :end-column 14)))
+;; Test org-lint checker with various Org mode issues
+;; Note: org-lint reports issues at info level (not error/warning)
+;; The exact error messages may vary depending on org-lint version
+(flycheck-ert-def-checker-test org-lint org nil
+ (let ((inhibit-message t))
+ (flycheck-ert-should-syntax-check
+ "language/org/lint.org" 'org-mode
+ ;; Line 21: Invalid link to nonexistent file
+ '(21 nil info "Link to non-existent local file \"nonexistent.org\""
:checker org-lint)
+ ;; Line 24: Invalid link to non-existent absolute path
+ '(24 nil info "Link to non-existent local file
\"/tmp/does-not-exist.org\"" :checker org-lint)
+ ;; Line 28: First duplicate CUSTOM_ID property
+ '(28 nil info "Duplicate CUSTOM_ID property \"duplicate-id-123\""
:checker org-lint)
+ ;; Line 33: Second duplicate CUSTOM_ID property
+ '(33 nil info "Duplicate CUSTOM_ID property \"duplicate-id-123\""
:checker org-lint)
+ ;; Line 40: Invalid link with special characters
+ '(40 nil info "Link to non-existent local file \"file with spaces and
\\\"quotes\\\".org\"" :checker org-lint))))
+
+;; Negative test: Verify that valid.org produces no errors
+(flycheck-ert-def-checker-test org-lint-valid org nil
+ (let ((inhibit-message t))
+ (flycheck-ert-should-syntax-check
+ "language/org/valid.org" 'org-mode)))
+
(flycheck-ert-initialize flycheck-test-resources-directory)
(provide 'flycheck-test)
diff --git a/test/resources/language/org/lint.org
b/test/resources/language/org/lint.org
new file mode 100644
index 00000000000..edeed5d658b
--- /dev/null
+++ b/test/resources/language/org/lint.org
@@ -0,0 +1,40 @@
+;; This file contains various Org lint issues for testing the org-lint checker.
+;;
+;; Test Coverage:
+;; - Invalid file links (nonexistent files)
+;; - Duplicate CUSTOM_ID properties (same CUSTOM_ID used multiple times)
+;; - Links with special characters
+;;
+;; Limitations:
+;; - Due to org-lint's duplicate CUSTOM_ID detection, only the first
occurrence of
+;; duplicate-id-123 can be tested (org-lint reports subsequent duplicates)
+;; - Exact line numbers may vary if file is modified
+;; - Error messages depend on org-lint version and enabled checkers
+;;
+;; Expected Errors:
+;; - Line 21: Invalid link to nonexistent.org
+;; - Line 24: Invalid link to /tmp/does-not-exist.org (absolute path)
+;; - Line 33: Duplicate CUSTOM_ID (second occurrence of duplicate-id-123)
+;; - Line 40: Invalid link with spaces and quotes
+
+* Invalid Link Test
+This link is invalid: [[file:nonexistent.org]]
+
+* Dead Link Test
+This link points to a dead file: [[file:/tmp/does-not-exist.org]]
+
+* Duplicate CUSTOM_ID Test
+:PROPERTIES:
+:CUSTOM_ID: duplicate-id-123
+:END:
+
+* Another Duplicate CUSTOM_ID Test
+:PROPERTIES:
+:CUSTOM_ID: duplicate-id-123
+:END:
+
+* Valid Link
+This link is valid: [[https://www.gnu.org/software/emacs/]]
+
+* Invalid Special Characters
+Link with invalid chars: [[file:file with spaces and "quotes".org]]
diff --git a/test/resources/language/org/valid.org
b/test/resources/language/org/valid.org
new file mode 100644
index 00000000000..9953997487b
--- /dev/null
+++ b/test/resources/language/org/valid.org
@@ -0,0 +1,20 @@
+;; Valid Org file for negative testing.
+;;
+;; This file is used to verify that org-lint produces no errors for
+;; well-formed Org files with:
+;; - Proper heading structure
+;; - Valid external links (HTTPS URLs)
+;; - Correct list formatting
+;; - Proper text markup (bold/italic)
+;;
+;; Expected: No errors from org-lint checker
+
+* Test Heading
+This is a valid Org file with no lint errors.
+
+- Item 1
+- Item 2
+
+Some *bold text* and /italic text/ with proper Org syntax.
+
+[[https://www.example.org][Valid external link]]