Fix infloop when indenting in cperl-mode

* lisp/progmodes/cperl-mode.el (cperl-indent-exp): Fix (Bug#10483)
Perl expressions (e.g. function calls) ending in ")" without
statement terminator on the same line no longer loop endlessly.
This commit is contained in:
Harald Jörg 2020-09-04 05:13:43 +02:00 committed by Lars Ingebrigtsen
parent 74ba8f8421
commit 70af9a9cb9
3 changed files with 144 additions and 6 deletions

View file

@ -4819,9 +4819,10 @@ conditional/loop constructs."
(while (< (point) tmp-end)
(parse-partial-sexp (point) tmp-end nil t) ; To start-sexp or eol
(or (eolp) (forward-sexp 1)))
(if (> (point) tmp-end) ; Yes, there an unfinished block
(if (> (point) tmp-end) ; Check for an unfinished block
nil
(if (eq ?\) (preceding-char))
;; closing parens can be preceded by up to three sexps
(progn ;; Plan B: find by REGEXP block followup this line
(setq top (point))
(condition-case nil
@ -4842,7 +4843,9 @@ conditional/loop constructs."
(progn
(goto-char top)
(forward-sexp 1)
(setq top (point)))))
(setq top (point)))
;; no block to be processed: expression ends here
(setq done t)))
(error (setq done t)))
(goto-char top))
(if (looking-at ; Try Plan C: continuation block

View file

@ -0,0 +1,52 @@
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
# This file contains test input and expected output for the tests in
# cperl-mode-tests.el, cperl-mode-test-indent-exp. The code is
# syntactically valid, but doesn't make much sense.
# -------- for loop: input --------
for my $foo (@ARGV)
{
...;
}
# -------- for loop: expected output --------
for my $foo (@ARGV) {
...;
}
# -------- for loop: end --------
# -------- while loop: input --------
{
while (1)
{
say "boring loop";
}
continue
{
last; # no endless loop, though
}
}
# -------- while loop: expected output --------
{
while (1) {
say "boring loop";
} continue {
last; # no endless loop, though
}
}
# -------- while loop: end --------
# -------- if-then-else: input --------
if (my $foo) { bar() } elsif (quux()) { baz() } else { quuux }
# -------- if-then-else: expected output --------
if (my $foo) {
bar();
} elsif (quux()) {
baz();
} else {
quuux;
}
# -------- if-then-else: end --------

View file

@ -24,10 +24,7 @@
;;; Commentary:
;; This is a collection of tests for the fontification of CPerl-mode.
;; Run these tests interactively:
;; (ert-run-tests-interactively '(tag :fontification))
;; This is a collection of tests for CPerl-mode.
;;; Code:
@ -35,6 +32,14 @@
(require 'cperl-mode)
(defvar cperl-mode-tests-data-directory
(expand-file-name "lisp/progmodes/cperl-mode-resources"
(or (getenv "EMACS_TEST_DIRECTORY")
(expand-file-name "../../../"
(or load-file-name
buffer-file-name))))
"Directory containing cperl-mode test data.")
(defun cperl-test-ppss (text regexp)
"Return the `syntax-ppss' of the first character matched by REGEXP in TEXT."
(interactive)
@ -86,4 +91,82 @@ have a face property."
(should (equal result nil))
(should (= (point) 15))))) ; point has skipped the group
(defun cperl-mode-test--run-bug-10483 ()
"Runs a short program, intended to be under timer scrutiny.
This function is intended to be used by an Emacs subprocess in
batch mode. The message buffer is used to report the result of
running `cperl-indent-exp' for a very simple input. The result
is expected to be different from the input, to verify that
indentation actually takes place.."
(let ((code "poop ('foo', \n'bar')")) ; see the bug report
(message "Test Bug#10483 started")
(with-temp-buffer
(insert code)
(funcall cperl-test-mode)
(goto-char (point-min))
(search-forward "poop")
(cperl-indent-exp)
(message "%s" (buffer-string)))))
(ert-deftest cperl-mode-test-bug-10483 ()
"Verifies that a piece of code which ends in a paren without a
statement terminato ron tne same line does not loop forever. The
test starts an asynchronous Emacs batch process under timeout
control."
(interactive)
(let* ((emacs (concat invocation-directory invocation-name))
(test-function 'cperl-mode-test--run-bug-10483)
(test-function-name (symbol-name test-function))
(test-file (symbol-file test-function 'defun))
(ran-out-of-time nil)
(process-connection-type nil)
runner)
(with-temp-buffer
(with-timeout (1
(delete-process runner)
(setq ran-out-of-time t))
(setq runner (start-process "speedy"
(current-buffer)
emacs
"-batch"
"--quick"
"--load" test-file
"--funcall" test-function-name))
(while (accept-process-output runner)))
(should (equal ran-out-of-time nil))
(goto-char (point-min))
;; just a very simple test for indentation: This should
;; be rather robust with regard to indentation defaults
(should (string-match
"poop ('foo', \n 'bar')" (buffer-string))))))
(ert-deftest cperl-mode-test-indent-exp ()
"Run various tests for `cperl-indent-exp' edge cases.
These exercise some standard blocks and also the special
treatment for Perl expressions where a closing paren isn't the
end of the statement."
(let ((file (expand-file-name "cperl-indent-exp.pl"
cperl-mode-tests-data-directory)))
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(while (re-search-forward
(concat "^# ?-+ \\_<\\(?1:.+?\\)\\_>: input ?-+\n"
"\\(?2:\\(?:.*\n\\)+?\\)"
"# ?-+ \\1: expected output ?-+\n"
"\\(?3:\\(?:.*\n\\)+?\\)"
"# ?-+ \\1: end ?-+")
nil t)
(let ((name (match-string 1))
(code (match-string 2))
(expected (match-string 3))
got)
(with-temp-buffer
(insert code)
(goto-char (point-min))
(cperl-indent-exp) ; here we go!
(setq expected (concat "test case " name ":\n" expected))
(setq got (concat "test case " name ":\n" (buffer-string)))
(should (equal got expected))))))))
;;; cperl-mode-tests.el ends here