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]]

Reply via email to