Normalize reversed bounds and validate SUBEXP in the pattern engine
SRC-1: tp--pattern-apply-single now swaps START > END bounds before branching on the object type, so the string path stops signaling a raw substring args-out-of-range while the buffer path keeps its historical narrow-to-region swap; the behavior is now uniform and documented in all six tp-match-*/tp-regexp-* docstrings. SRC-2: a SUBEXP larger than the pattern's capture-group count (per regexp-opt-depth) signals "Regexp X has no group N" instead of silently matching nothing, while legal non-participating and zero-width groups keep working quietly. Also corrects the return wording of tp-match-set/tp-regexp-set (a NEW string for string objects, not "the modified string") and adds the missing return sections to tp-match-add/tp-regexp-add, matching the verified tp-match-reset behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e870b87941
commit
575c99be88
@ -684,5 +684,46 @@ never registered the buffer (REG-1)."
|
||||
(tp-layer-reset)
|
||||
(setq tp-search-reg1b-color nil)))
|
||||
|
||||
;;; SRC-1: reversed START/END bounds are swapped on both object paths
|
||||
|
||||
(ert-deftest tp-search-test-reversed-bounds-string-swaps ()
|
||||
"String-path START > END is swapped instead of signaling out-of-range."
|
||||
(let ((res (tp-match-set "o" '(face bold) "foo" 2 1)))
|
||||
(should (eq (get-text-property 1 'face res) 'bold))
|
||||
(should-not (get-text-property 2 'face res)))
|
||||
;; Swapped bounds behave exactly like the same bounds in order.
|
||||
(should (equal-including-properties
|
||||
(tp-regexp-set "o+" '(face bold) "foo" 3 1)
|
||||
(tp-regexp-set "o+" '(face bold) "foo" 1 3))))
|
||||
|
||||
(ert-deftest tp-search-test-reversed-bounds-buffer-swaps ()
|
||||
"Buffer-path START > END keeps its historical swap behavior."
|
||||
(with-temp-buffer
|
||||
(insert "foo")
|
||||
(should (equal (tp-match-set "o" '(face bold) nil 3 2)
|
||||
'((2 . 3))))
|
||||
(should (eq (get-text-property 2 'face) 'bold))
|
||||
(should-not (get-text-property 3 'face))))
|
||||
|
||||
;;; SRC-2: SUBEXP beyond the pattern's group count signals clearly
|
||||
|
||||
(ert-deftest tp-search-test-subexp-beyond-group-count-errors ()
|
||||
"A SUBEXP larger than the pattern's group count errors on both paths."
|
||||
(should-error (tp-regexp-set "abc" '(face bold)
|
||||
(copy-sequence "abc") nil nil 5))
|
||||
(with-temp-buffer
|
||||
(insert "abc")
|
||||
(should-error (tp-regexp-add "a\\(b\\)c" '(face bold) nil nil nil 2))
|
||||
;; Nothing was applied before the error.
|
||||
(should-not (get-text-property 1 'face))))
|
||||
|
||||
(ert-deftest tp-search-test-subexp-non-participating-group-quiet ()
|
||||
"A legal group that never participates still returns nil quietly."
|
||||
(with-temp-buffer
|
||||
(insert "xbc")
|
||||
(should (null (tp-regexp-set "\\(a\\)bc\\|xbc" '(face bold)
|
||||
nil nil nil 1)))
|
||||
(should-not (get-text-property 1 'face))))
|
||||
|
||||
(provide 'tp-search-tests)
|
||||
;;; tp-search-tests.el ends here
|
||||
|
||||
73
tp-search.el
73
tp-search.el
@ -49,7 +49,9 @@ is a regexp. APPLY-FN is called with (START END PROPS OBJECT) for
|
||||
each match.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds. Matching behaves as if
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped (matching the buffer path's historical narrow-to-region
|
||||
behavior, now uniform across object types). Matching behaves as if
|
||||
OBJECT consisted only of that portion (the buffer path narrows, the
|
||||
string path matches against the substring), so no match crosses the
|
||||
boundaries.
|
||||
@ -57,7 +59,9 @@ When SUBEXP is non-nil, it names a capture group of PATTERN: the
|
||||
properties and returned regions cover (match-beginning SUBEXP) to
|
||||
\(match-end SUBEXP) of each match, and a match in which that group
|
||||
does not participate contributes nothing. The scan still advances
|
||||
past the whole match.
|
||||
past the whole match. A SUBEXP larger than PATTERN's group count
|
||||
\(per `regexp-opt-depth') signals an error instead of silently
|
||||
matching nothing.
|
||||
For strings, returns a NEW string with properties applied
|
||||
\(non-destructive).
|
||||
For buffers, modifies in-place and returns list of regions.
|
||||
@ -66,6 +70,14 @@ Zero-width matches (an empty literal pattern, or a regexp that can
|
||||
match the empty string) are recorded and the scan advances one
|
||||
position past them, so the search always terminates."
|
||||
(let ((regexp (if literal (regexp-quote pattern) pattern)))
|
||||
;; Reversed bounds are swapped, not signaled: the buffer path's
|
||||
;; narrow-to-region always did this, so the string path follows.
|
||||
(when (and start end (> start end))
|
||||
(cl-rotatef start end))
|
||||
;; A group number beyond the pattern's group count could never
|
||||
;; match; make the typo loud instead of a silent no-op.
|
||||
(when (and subexp (> subexp (regexp-opt-depth regexp)))
|
||||
(error "Regexp %S has no group %d" pattern subexp))
|
||||
(cond
|
||||
;; String object
|
||||
((stringp object)
|
||||
@ -247,12 +259,13 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds. Matching behaves as if
|
||||
OBJECT consisted only of that portion, so no match crosses the
|
||||
boundaries.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped. Matching behaves as if OBJECT consisted only of that
|
||||
portion, so no match crosses the boundaries.
|
||||
|
||||
Returns:
|
||||
- For strings: the modified string
|
||||
- For strings: a NEW string with properties applied (the original
|
||||
string is not modified)
|
||||
- For buffers: list of (START . END) pairs for all matches."
|
||||
(tp--match-apply pattern (tp--ensure-props plist) #'tp-set object
|
||||
start end))
|
||||
@ -269,12 +282,14 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped.
|
||||
|
||||
Unlike `tp-match-set', this completely replaces all existing properties.
|
||||
|
||||
For strings, returns a NEW string (original is not modified).
|
||||
For buffers, modifies in-place and returns list of regions."
|
||||
For buffers, modifies in-place and returns list of (START . END)
|
||||
regions."
|
||||
(tp--match-apply pattern (tp--ensure-props plist)
|
||||
#'tp--reset-apply
|
||||
object start end))
|
||||
@ -303,9 +318,14 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped.
|
||||
|
||||
Unlike `tp-match-set', this deeply merges nested properties."
|
||||
Unlike `tp-match-set', this deeply merges nested properties.
|
||||
|
||||
For strings, returns a NEW string (original is not modified).
|
||||
For buffers, modifies in-place and returns list of (START . END)
|
||||
regions."
|
||||
(tp--match-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
||||
object start end))
|
||||
|
||||
@ -322,16 +342,18 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds. Matching behaves as if
|
||||
OBJECT consisted only of that portion, so no match crosses the
|
||||
boundaries.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped. Matching behaves as if OBJECT consisted only of that
|
||||
portion, so no match crosses the boundaries.
|
||||
When SUBEXP is non-nil, it names a capture group of PATTERN (1 for
|
||||
the first group, like font-lock highlights): properties apply to that
|
||||
group of each match instead of the whole match, and a match in which
|
||||
the group does not participate contributes nothing.
|
||||
the group does not participate contributes nothing. A SUBEXP larger
|
||||
than PATTERN's group count signals an error.
|
||||
|
||||
Returns:
|
||||
- For strings: the modified string
|
||||
- For strings: a NEW string with properties applied (the original
|
||||
string is not modified)
|
||||
- For buffers: list of (START . END) pairs for all matches."
|
||||
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp-set object
|
||||
start end subexp))
|
||||
@ -348,15 +370,18 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped.
|
||||
When SUBEXP is non-nil, properties apply to that capture group of
|
||||
each match instead of the whole match; a match in which the group
|
||||
does not participate contributes nothing.
|
||||
does not participate contributes nothing. A SUBEXP larger than
|
||||
PATTERN's group count signals an error.
|
||||
|
||||
Unlike `tp-regexp-set', this completely replaces all existing properties.
|
||||
|
||||
For strings, returns a NEW string (original is not modified).
|
||||
For buffers, modifies in-place and returns list of regions."
|
||||
For buffers, modifies in-place and returns list of (START . END)
|
||||
regions."
|
||||
(tp--regexp-apply pattern (tp--ensure-props plist)
|
||||
#'tp--reset-apply
|
||||
object start end subexp))
|
||||
@ -373,12 +398,18 @@ or `define-tp-group'.
|
||||
OBJECT is a buffer or string; nil means current buffer.
|
||||
START and END restrict matching to the [START, END) portion of
|
||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||
buffers); nil means the object's bounds.
|
||||
buffers); nil means the object's bounds. If START > END the bounds
|
||||
are swapped.
|
||||
When SUBEXP is non-nil, properties apply to that capture group of
|
||||
each match instead of the whole match; a match in which the group
|
||||
does not participate contributes nothing.
|
||||
does not participate contributes nothing. A SUBEXP larger than
|
||||
PATTERN's group count signals an error.
|
||||
|
||||
Unlike `tp-regexp-set', this deeply merges nested properties."
|
||||
Unlike `tp-regexp-set', this deeply merges nested properties.
|
||||
|
||||
For strings, returns a NEW string (original is not modified).
|
||||
For buffers, modifies in-place and returns list of (START . END)
|
||||
regions."
|
||||
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
||||
object start end subexp))
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user