Limit multiline ellipsis to call parentheses

ober

880a691bb7567f5c0599a04cca3510a50c7e337d

diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index c2b8b2d..5fc2090 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -900,15 +900,20 @@
             (generic-plain-ellipsis-at?
               source
               (generic-skip-whitespace source (+ i 1)))))
-  (def (generic-ellipsis-inside-parens? source i)
-       (let loop ([j 0] [depth 0])
+  (def (generic-ellipsis-inside-call-parens? source i)
+       (let loop ([j 0] [stack '()])
          (cond
-           [(>= j i) (> depth 0)]
+           [(>= j i)
+            (and (pair? stack)
+                 (let ([open (car stack)])
+                   (and (> open 0)
+                        (identifier-token-char?
+                          (string-ref source (- open 1))))))]
            [(char=? (string-ref source j) #\()
-            (loop (+ j 1) (+ depth 1))]
+            (loop (+ j 1) (cons j stack))]
            [(char=? (string-ref source j) #\))
-            (loop (+ j 1) (max 0 (- depth 1)))]
-           [else (loop (+ j 1) depth)])))
+            (loop (+ j 1) (if (pair? stack) (cdr stack) stack))]
+           [else (loop (+ j 1) stack)])))
   (def (generic-word-char? ch)
        (or (char-alphabetic? ch)
            (char-numeric? ch)
@@ -1026,7 +1031,7 @@
               (loop
                 (generic-skip-whitespace source (+ i 3))
                 (cons
-                  (if (generic-ellipsis-inside-parens? source i)
+                  (if (generic-ellipsis-inside-call-parens? source i)
                       "(?:.|\\n)*?"
                       plain-ellipsis)
                   parts)
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index 76cd7be..3cc7705 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -3,11 +3,11 @@
   ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC")
   ("src/semgrep/lang.ss" . "6982E07679D20836")
   ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA")
-  ("src/semgrep/scan.ss" . "71FE8A8073E6A24A")
-  ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
+  ("src/semgrep/scan.ss" . "E4428A1FD577875E")
   ("src/semgrep/rule.ss" . "E12C108153C181FA")
-  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
+  ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
   ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
+  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
   ("src/semgrep/match/structural.ss" . "6FE77014EE9FDCE4")
   ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E")
   ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC"))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index 38c1382..1e4d722 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -999,16 +999,22 @@
          source
          (generic-skip-whitespace source (+ i 1)))))
 
-;; An ellipsis inside an open call/parenthesis should match across newlines, so
-;; `sink(...)` matches a call whose arguments span multiple lines. Outside
-;; parens the line-bounded form is kept to avoid over-matching across statements.
-(def (generic-ellipsis-inside-parens? source i)
-  (let loop ([j 0] [depth 0])
+;; An ellipsis inside an open CALL parenthesis should match across newlines, so
+;; `sink(...)` matches a call whose arguments span multiple lines. The enclosing
+;; `(` must be a call (preceded by an identifier): a bare grouping `(...)` keeps
+;; the line-bounded form, so it does not span across whole files/statements.
+(def (generic-ellipsis-inside-call-parens? source i)
+  (let loop ([j 0] [stack '()])
     (cond
-      [(>= j i) (> depth 0)]
-      [(char=? (string-ref source j) #\() (loop (+ j 1) (+ depth 1))]
-      [(char=? (string-ref source j) #\)) (loop (+ j 1) (max 0 (- depth 1)))]
-      [else (loop (+ j 1) depth)])))
+      [(>= j i)
+       (and (pair? stack)
+            (let ([open (car stack)])
+              (and (> open 0)
+                   (identifier-token-char? (string-ref source (- open 1))))))]
+      [(char=? (string-ref source j) #\() (loop (+ j 1) (cons j stack))]
+      [(char=? (string-ref source j) #\))
+       (loop (+ j 1) (if (pair? stack) (cdr stack) stack))]
+      [else (loop (+ j 1) stack)])))
 
 (def (generic-word-char? ch)
   (or (char-alphabetic? ch)
@@ -1114,7 +1120,7 @@
               (char=? (string-ref source (+ i 1)) #\.)
               (char=? (string-ref source (+ i 2)) #\.))
          (loop (generic-skip-whitespace source (+ i 3))
-               (cons (if (generic-ellipsis-inside-parens? source i)
+               (cons (if (generic-ellipsis-inside-call-parens? source i)
                          "(?:.|\\n)*?"
                          plain-ellipsis)
                      parts)