branch: elpa/flycheck
commit a988009dc4e798b692e44e701f0d2ffb8c95fd20
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Fix remote-execution gaps found in review
    
    Complete the TRAMP support with correctness fixes the initial pass
    missed:
    
    - protoc's --proto_path and pug's -p passed the raw TRAMP file name to
      the remote binary; reduce them with file-local-name like the sibling
      arguments already were.
    - flycheck-temp-directory reported the local /tmp for source checkers
      while the temp files are created on the remote host, so a checker
      could be judged usable when the remote temp dir is not writable. Probe
      the host-aware (temporary-file-directory) instead.
    - The proselint version cache was a single global, so a version detected
      on one host was reused on another; key it by host.
    - flycheck-compile (via flycheck-checker-shell-command) resolved the
      program and source paths locally, so running a checker as a compile
      command in a remote buffer targeted the wrong host. Resolve the
      executable on the remote host and localize the file arguments.
    - Move the possibly-remote file-exists-p check in
      flycheck-may-check-automatically after the trigger-set check, so a
      disallowed trigger no longer blocks on a remote stat while typing.
---
 flycheck.el                            | 66 +++++++++++++++++++++-------------
 test/specs/languages/test-proselint.el | 26 ++++++++++++++
 2 files changed, 67 insertions(+), 25 deletions(-)

diff --git a/flycheck.el b/flycheck.el
index 333419edfd..78b7397ec8 100644
--- a/flycheck.el
+++ b/flycheck.el
@@ -1684,7 +1684,10 @@ Return the path of the file."
 Return nil if the CHECKER does not write temporary files."
   (let ((args (flycheck-checker-arguments checker)))
     (cond
-     ((memq 'source args) temporary-file-directory)
+     ;; `flycheck-temp-file-system' creates the file with
+     ;; `make-nearby-temp-file', i.e. on the host of `default-directory',
+     ;; so probe that host's temporary directory, not the local one.
+     ((memq 'source args) (temporary-file-directory))
      ((memq 'source-inplace args)
       (if buffer-file-name (file-name-directory buffer-file-name)
         temporary-file-directory))
@@ -3816,7 +3819,6 @@ If CONDITIONS are given, determine whether syntax may be 
checked
 under at least one of them, according to
 `flycheck-check-syntax-automatically'."
   (and (not (or buffer-read-only (flycheck-ephemeral-buffer-p)))
-       (file-exists-p default-directory)
        (or (not conditions)
            (let ((allowed
                   ;; Remote buffers use a narrower set of trigger events by
@@ -3827,7 +3829,10 @@ under at least one of them, according to
                       flycheck-check-syntax-automatically-remote
                     flycheck-check-syntax-automatically)))
              (seq-some (lambda (condition) (memq condition allowed))
-                       conditions)))))
+                       conditions)))
+       ;; Checked last so a disallowed trigger short-circuits before this
+       ;; possibly-remote (and thus blocking) stat of `default-directory'.
+       (file-exists-p default-directory)))
 
 (defvar-local flycheck--idle-trigger-timer nil
   "Timer used to trigger a syntax check after an idle delay.")
@@ -7555,7 +7560,9 @@ use with `compilation-error-regexp-alist'."
 Like `flycheck-substitute-argument', except for source,
 source-inplace, and source-original."
   (if (memq arg '(source source-inplace source-original))
-      (list buffer-file-name)
+      ;; The command runs on the host of `default-directory', so strip any
+      ;; remote prefix from the file name.
+      (list (file-local-name buffer-file-name))
     (flycheck-substitute-argument arg checker)))
 
 (defun flycheck--checker-substituted-shell-command-arguments (checker)
@@ -7587,8 +7594,9 @@ shell execution."
          (abs-prog
           ;; The executable path returned by 
`flycheck-command-wrapper-function'
           ;; may not be absolute, so expand it here.  See URL
-          ;; `https://github.com/flycheck/flycheck/issues/1461'.
-          (or (executable-find (car wrapped))
+          ;; `https://github.com/flycheck/flycheck/issues/1461'.  Resolve it on
+          ;; the host the command runs on when `default-directory' is remote.
+          (or (executable-find (car wrapped) (file-remote-p default-directory))
               (user-error "Cannot find `%s' using `executable-find'"
                           (car wrapped))))
          (command (mapconcat #'shell-quote-argument
@@ -7596,7 +7604,8 @@ shell execution."
     (if (flycheck-checker-get checker 'standard-input)
         ;; If the syntax checker expects the source from standard input add an
         ;; appropriate shell redirection
-        (concat command " < " (shell-quote-argument (buffer-file-name)))
+        (concat command " < "
+                (shell-quote-argument (file-local-name (buffer-file-name))))
       command)))
 
 (defun flycheck-compile-name (_name)
@@ -11323,26 +11332,31 @@ See URL `https://proselint.com/' for more information 
about proselint."
               (let-alist (car response)
                 .result.<stdin>.diagnostics)))))
 
-(defvar flycheck--proselint-use-old-args 'unknown
-  "Cache for proselint version detection.
-Value is t for old (<= 0.14.0), nil for new (>= 0.16.0),
-or `unknown' if not yet detected.")
+(defvar flycheck--proselint-use-old-args (make-hash-table :test 'equal)
+  "Cache for proselint version detection, keyed by host.
+The key is the remote identifier of `default-directory' (see
+`file-remote-p'), or nil for the local host, since the proselint
+on each host may have a different version.  Each value is t for
+old (<= 0.14.0) proselint and nil for new (>= 0.16.0); a host
+absent from the table has not been probed yet.")
 
 (defvar flycheck-proselint-executable)
 
 (defun flycheck--proselint-args ()
-  "Return command arguments for proselint, detecting the version once."
-  (when (eq flycheck--proselint-use-old-args 'unknown)
-    (setq flycheck--proselint-use-old-args
-          ;; Probe on the host the check will run on (remote over TRAMP)
-          (zerop (process-file
-                  (or flycheck-proselint-executable "proselint")
-                  nil nil nil "--version"))))
-  (if flycheck--proselint-use-old-args
-      ;; Proselint versions <= 0.14.0:
-      (list "--json" "-")
-    ;; Proselint versions >= 0.16.0
-    (list "check" "--output-format=json")))
+  "Return command arguments for proselint, detecting the version once per 
host."
+  (let ((host (file-remote-p default-directory)))
+    (when (eq (gethash host flycheck--proselint-use-old-args 'unknown) 
'unknown)
+      (puthash host
+               ;; Probe on the host the check will run on (remote over TRAMP).
+               (zerop (process-file
+                       (or flycheck-proselint-executable "proselint")
+                       nil nil nil "--version"))
+               flycheck--proselint-use-old-args))
+    (if (gethash host flycheck--proselint-use-old-args)
+        ;; Proselint versions <= 0.14.0:
+        (list "--json" "-")
+      ;; Proselint versions >= 0.16.0
+      (list "check" "--output-format=json"))))
 
 (flycheck-define-checker proselint
   "Flycheck checker using Proselint.
@@ -11374,7 +11388,8 @@ See URL 
`https://developers.google.com/protocol-buffers/'."
                                          (flycheck-temp-dir-system))))
             ;; Add the current directory to resolve imports
             (eval (concat "--proto_path="
-                          (file-name-directory (buffer-file-name))))
+                          (file-local-name
+                           (file-name-directory (buffer-file-name)))))
             ;; Add other import paths; this needs to be after the current
             ;; directory to produce the right output.  See URL
             ;; `https://github.com/flycheck/flycheck/pull/1655'
@@ -11395,7 +11410,8 @@ See URL 
`https://developers.google.com/protocol-buffers/'."
   "A Pug syntax checker using the pug compiler.
 
 See URL `https://pugjs.org/'."
-  :command ("pug" "-p" (eval (expand-file-name (buffer-file-name))))
+  :command ("pug" "-p"
+            (eval (file-local-name (expand-file-name (buffer-file-name)))))
   :standard-input t
   :error-patterns
   ;; errors with includes/extends (e.g. missing files)
diff --git a/test/specs/languages/test-proselint.el 
b/test/specs/languages/test-proselint.el
index 7e1c480aa9..73c949d8fc 100644
--- a/test/specs/languages/test-proselint.el
+++ b/test/specs/languages/test-proselint.el
@@ -4,6 +4,32 @@
 (require 'test-helpers)
 
 (describe "Language Proselint"
+
+  (describe "flycheck--proselint-args"
+    (before-each
+      (clrhash flycheck--proselint-use-old-args))
+
+    (it "probes a host once and caches the detected version"
+      ;; Exit 0 -> old proselint, which takes the "--json -" arguments.
+      (spy-on 'process-file :and-return-value 0)
+      (let ((default-directory "/tmp/"))
+        (expect (flycheck--proselint-args) :to-equal '("--json" "-"))
+        (expect (flycheck--proselint-args) :to-equal '("--json" "-")))
+      (expect 'process-file :to-have-been-called-times 1))
+
+    (it "detects the version independently on each host"
+      ;; `file-remote-p' parses the prefix without connecting, so the probe
+      ;; can be faked per host without a live remote.
+      (spy-on 'process-file :and-call-fake
+              (lambda (&rest _)
+                (if (file-remote-p default-directory) 1 0)))
+      (let ((default-directory "/tmp/"))
+        (expect (flycheck--proselint-args) :to-equal '("--json" "-")))
+      (let ((default-directory "/ssh:host:/tmp/"))
+        (expect (flycheck--proselint-args)
+                :to-equal '("check" "--output-format=json")))
+      (expect 'process-file :to-have-been-called-times 2)))
+
   (flycheck-buttercup-def-checker-test proselint (text markdown) nil
     (let ((flycheck-disabled-checkers '(markdown-markdownlint-cli 
markdown-markdownlint-cli2 markdown-mdl markdown-pymarkdown)))
       (flycheck-buttercup-with-env '(("LC_ALL" . nil))

Reply via email to