Subject: Extensive docs and tests for `ruby-forward-string' (PATCH)
From: Cameron Desautels <[email-masked]>
To: [email-masked]
Date: Fri, 6 Dec 2013 11:15:52 -0600
List-Id: <debbugs-submit.debbugs.gnu.org>

The attached patch adds extensive documentation and tests to the
`ruby-forward-string' function.

This may seem an odd function to document thoroughly, but I spent
quite a while wrapping my head around the exact behavior and I want to
spare the next person.  It also underlies some important parsing
functionality in ruby-mode.

Note the one test which is expected to fail: this represents an
outstanding bug in `ruby-forward-string`.  I'll be (immediately)
following this report with a patch which fixes *that* issue, but it
didn't seem prudent to combine the commits.
--
Cameron Desautels <[email-masked]>
*** lisp/progmodes/ruby-mode.el.orig	2013-12-06 10:56:52.000000000 -0600
--- lisp/progmodes/ruby-mode.el     	2013-12-06 10:54:07.000000000 -0600
***************
*** 791,797 ****
                    (t nil)))))))))

  (defun ruby-forward-string (term &optional end no-error expand)
!   "TODO: document."
    (let ((n 1) (c (string-to-char term))
          (re (if expand
                  (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
--- 791,811 ----
                    (t nil)))))))))

  (defun ruby-forward-string (term &optional end no-error expand)
!   "Move forward across one balanced pair of string delimiters.
! Skips escaped delimiters. If EXPAND is non-nil, also ignores
! delimiters in interpolated strings.
!
! TERM should be a string containing either a single, self-matching
! delimiter (e.g. \"/\"), or a pair of matching delimiters with the
! close delimiter first (e.g. \"][\").
!
! When non-nil, search is bounded by position END.
!
! Throws an error if a balanced match is not found, unless NO-ERROR
! is non-nil, in which case nil will be returned.
!
! This command assumes the character after point is an opening
! delimiter."
    (let ((n 1) (c (string-to-char term))
          (re (if expand
                  (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
*** test/automated/ruby-mode-tests.el.orig	2013-12-06 10:56:52.000000000 -0600
--- test/automated/ruby-mode-tests.el     	2013-12-06 10:54:07.000000000 -0600
***************
*** 639,644 ****
--- 639,695 ----
        (ruby--insert-coding-comment "utf-8")
        (should (string= "# encoding: utf-8\n\n" (buffer-string))))))

+ (defun ruby-forward-string-should-move-to (content term index &optional expand)
+   "Assert that `ruby-forward-string', called on buffer containing
+ CONTENT, passing TERM and EXPAND, leaves point at INDEX.
+
+ Pass nil for INDEX if an error should be expected."
+   (ruby-with-temp-buffer content
+     (goto-char (point-min))
+     (if (ruby-forward-string term nil t expand)
+         (should (= (point) index))
+       (should (null index)))))
+
+ (ert-deftest ruby-forward-string-accepts-paired-delimiters ()
+   (ruby-forward-string-should-move-to "<foo>bar" "><" 6)
+   (ruby-forward-string-should-move-to "[foo]bar" "][" 6)
+   (ruby-forward-string-should-move-to "(foo)bar" ")(" 6))
+
+ (ert-deftest ruby-forward-string-accepts-single-delimiters ()
+   (ruby-forward-string-should-move-to "/foo/bar" "/" 6)
+   (ruby-forward-string-should-move-to "|foo|bar" "|" 6)
+   (ruby-forward-string-should-move-to "-foo-bar" "-" 6))
+
+ (ert-deftest ruby-forward-string-accepts-carets ()
+   :expected-result :failed
+   (ruby-forward-string-should-move-to "^foo^bar" "^" 6))
+
+ (ert-deftest ruby-forward-string-scans-the-shortest-match ()
+   (ruby-forward-string-should-move-to "<foo>"   "><" 6)
+   (ruby-forward-string-should-move-to "<foo>>"  "><" 6)
+   (ruby-forward-string-should-move-to "<foo><>" "><" 6))
+
+ (ert-deftest ruby-forward-string-skips-escaped-delimiters ()
+   (ruby-forward-string-should-move-to "<foo\\>"   "><" nil)
+   (ruby-forward-string-should-move-to "<foo\\>>"  "><" 8)
+   (ruby-forward-string-should-move-to "/foo\\/"   "/"  nil)
+   (ruby-forward-string-should-move-to "/foo\\//"  "/"  8)
+   (ruby-forward-string-should-move-to "/foo\\\\/" "/"  8))
+
+ (ert-deftest ruby-forward-string-requires-matched-delimiters ()
+   (ruby-forward-string-should-move-to "<foo"    "><" nil)
+   (ruby-forward-string-should-move-to "<foo<"   "><" nil)
+   (ruby-forward-string-should-move-to "<foo<>"  "><" nil)
+   (ruby-forward-string-should-move-to "<foo<>>" "><" 8)
+   (ruby-forward-string-should-move-to "<<><>>"  "><" 7)
+   (ruby-forward-string-should-move-to "><"      "><" nil)
+   (ruby-forward-string-should-move-to "/foo"    "/"  nil))
+
+ (ert-deftest ruby-forward-string-can-skip-interpolations ()
+   (ruby-forward-string-should-move-to "<f#{>}o>" "><" 6 nil)
+   (ruby-forward-string-should-move-to "<f#{>}o>" "><" 9 t)
+   (ruby-forward-string-should-move-to "/f#{/}o/" "/"  6 nil)
+   (ruby-forward-string-should-move-to "/f#{/}o/" "/"  9 t))

  (provide 'ruby-mode-tests)