From 95068836b5970c1aebb088e987741ad316007b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Gouguec?= Date: Sun, 12 Nov 2023 10:55:24 +0100 Subject: [PATCH] Recognize shebang lines that pass -S/--split-string to env (bug#66902) * etc/NEWS: announce the change. * lisp/files.el (auto-mode-interpreter-regexp): Add optional -S switch to the ignored group capturing the env invocation. Allow multiple spaces between #!, interpreter and first argument: empirically, Linux's execve accepts that. * test/lisp/files-tests.el (files-tests--check-shebang): New helper to generate a temporary file with a given interpreter line, and assert that the mode picked by 'set-auto-mode' is derived from an expected mode. Write the 'should' form so that failure reports include useful context; for example: (ert-test-failed ((should (equal (list shebang actual-mode) (list shebang expected-mode))) :form (equal ("#!/usr/bin/env -S make -f" fundamental-mode) ("#!/usr/bin/env -S make -f" makefile-mode)) :value nil :explanation (list-elt 1 (different-atoms fundamental-mode makefile-mode)))) (files-tests-auto-mode-interpreter): New test; exercise some aspects of interpreter-mode-alist. --- etc/NEWS | 6 ++++++ lisp/files.el | 12 ++++++++++-- test/lisp/files-tests.el | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 12ae8058cb1..b9ee3747040 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -233,6 +233,12 @@ to enter the file you want to modify. It can be used to customize the look of the appointment notification displayed on the mode line when 'appt-display-mode-line' is non-nil. +--- +*** Emacs now recognizes shebang lines that pass -S/--split-string to env. +When visiting a script that invokes 'env -S INTERPRETER ARGS...' in +its shebang line, Emacs will now skip over 'env -S' and deduce the +major mode based on the interpreter. + ** Emacs Server and Client --- diff --git a/lisp/files.el b/lisp/files.el index d729bdf8c25..1cdcec23b11 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -3245,8 +3245,16 @@ inhibit-local-variables-p temp)) (defvar auto-mode-interpreter-regexp - (purecopy "#![ \t]?\\([^ \t\n]*\ -/bin/env[ \t]\\)?\\([^ \t\n]+\\)") + (purecopy + (concat + "#![ \t]*" + ;; Optional group 1: env(1) invocation. + "\\(" + "[^ \t\n]*/bin/env[ \t]*" + "\\(?:-S[ \t]*\\|--split-string\\(?:=\\|[ \t]*\\)\\)?" + "\\)?" + ;; Group 2: interpreter. + "\\([^ \t\n]+\\)")) "Regexp matching interpreters, for file mode determination. This regular expression is matched against the first line of a file to determine the file's mode in `set-auto-mode'. If it matches, the file diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el index 3492bd701b2..3e499fff468 100644 --- a/test/lisp/files-tests.el +++ b/test/lisp/files-tests.el @@ -1656,6 +1656,31 @@ files-tests-file-name-base (should (equal (file-name-base "foo") "foo")) (should (equal (file-name-base "foo/bar") "bar"))) +(defun files-tests--check-shebang (shebang expected-mode) + "Assert that mode for SHEBANG derives from EXPECTED-MODE." + (let ((actual-mode + (ert-with-temp-file script-file + :text shebang + (find-file script-file) + (if (derived-mode-p expected-mode) + expected-mode + major-mode)))) + ;; Tuck all the information we need in the `should' form: input + ;; shebang, expected mode vs actual. + (should + (equal (list shebang actual-mode) + (list shebang expected-mode))))) + +(ert-deftest files-tests-auto-mode-interpreter () + "Test that `set-auto-mode' deduces correct modes from shebangs." + (files-tests--check-shebang "#!/bin/bash" 'sh-mode) + (files-tests--check-shebang "#!/usr/bin/env bash" 'sh-mode) + (files-tests--check-shebang "#!/usr/bin/env python" 'python-base-mode) + (files-tests--check-shebang "#!/usr/bin/env python3" 'python-base-mode) + (files-tests--check-shebang "#!/usr/bin/env -S awk -v FS=\"\\t\" -v OFS=\"\\t\" -f" 'awk-mode) + (files-tests--check-shebang "#!/usr/bin/env -S make -f" 'makefile-mode) + (files-tests--check-shebang "#!/usr/bin/make -f" 'makefile-mode)) + (ert-deftest files-test-dir-locals-auto-mode-alist () "Test an `auto-mode-alist' entry in `.dir-locals.el'" (find-file (ert-resource-file "whatever.quux")) -- 2.42.1