Extract Semgrep JavaScript vardef scanner

ober

d447e027fbd0a73c96b33388cd53a43268806003

diff --git a/SEMGREP_JERBOA_IMPLEMENTATION.md b/SEMGREP_JERBOA_IMPLEMENTATION.md
index 423a2da..7a13512 100644
--- a/SEMGREP_JERBOA_IMPLEMENTATION.md
+++ b/SEMGREP_JERBOA_IMPLEMENTATION.md
@@ -475,6 +475,8 @@ Completed in the repo:
     `src/semgrep/engine/text-support.ss`
   - extracted XML/YAML special-case scanners into
     `src/semgrep/engine/markup-scan.ss`
+  - extracted JavaScript `vardef_assign` text scanning into
+    `src/semgrep/engine/js-vardef-scan.ss`
 
 Validation at this checkpoint:
 
diff --git a/lib/semgrep/engine/js-vardef-scan.sls b/lib/semgrep/engine/js-vardef-scan.sls
new file mode 100644
index 0000000..2b1c7e6
--- /dev/null
+++ b/lib/semgrep/engine/js-vardef-scan.sls
@@ -0,0 +1,216 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/semgrep/engine/js-vardef-scan.ss
+
+(library (semgrep engine js-vardef-scan)
+  (export scan-javascript-vardef-assign-pattern)
+  (import
+    (except (chezscheme) make-hash-table hash-table? sort sort!
+     printf fprintf format path-extension path-absolute?
+     with-input-from-string with-output-to-string iota \x31;+
+     \x31;- partition make-date make-time meta atom?)
+    (except (jerboa prelude) meta atom?) (std regex)
+    (semgrep rule) (semgrep engine generic-scan)
+    (semgrep engine regex-support)
+    (semgrep engine text-support))
+  (def (alist-ref/default xs key default)
+       (let ([found (assoc key xs)])
+         (if found (cdr found) default)))
+  (def (rule-option-entry rule key)
+       (let ([options (rule-options rule)])
+         (or (assoc key options)
+             (and (string? key) (assoc (string->symbol key) options))
+             (and (symbol? key) (assoc (symbol->string key) options)))))
+  (def (rule-option-explicit-false? rule key)
+       (let ([found (rule-option-entry rule key)])
+         (and found (not (cdr found)))))
+  (def (sg-string-prefix? prefix s)
+       (let ([prefix-len (string-length prefix)]
+             [len (string-length s)])
+         (and (<= prefix-len len)
+              (string=? (substring s 0 prefix-len) prefix))))
+  (def (string-find-substring s needle)
+       (let ([len (string-length s)])
+         (let loop ([i 0])
+           (cond
+             [(> i len) #f]
+             [(substring-at? s needle i) i]
+             [else (loop (+ i 1))]))))
+  (def (identifier-char? ch)
+       (or (char-alphabetic? ch)
+           (char-numeric? ch)
+           (char=? ch #\_)
+           (char=? ch #\$)))
+  (def (skip-horizontal-backward source i)
+       (let loop ([j i])
+         (if (and (>= j 0)
+                  (let ([ch (string-ref source j)])
+                    (or (char=? ch #\space) (char=? ch #\tab))))
+             (loop (- j 1))
+             j)))
+  (def (javascript-vardef-assign-pattern-kind pattern)
+       (let ([trimmed (string-trim pattern)])
+         (cond
+           [(sg-string-prefix? "<..." trimmed) 'expression-ellipsis]
+           [(string-find-substring pattern "\n") 'statement-sequence]
+           [else 'plain])))
+  (def (javascript-vardef-assign-pattern-spec pattern)
+       (let* ([match (re-search
+                       (re "([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*([^;>\\n]+)")
+                       pattern
+                       0)]
+              [name (and match (re-match-group match 1))]
+              [value0 (and match (string-trim (re-match-group match 2)))]
+              [ellipsis (and value0 (string-find-substring value0 "..."))]
+              [value (and value0
+                          (if ellipsis
+                              (string-trim (substring value0 0 ellipsis))
+                              value0))])
+         (and name
+              value
+              (not (string-find-substring name "$"))
+              (not (string-find-substring value "$"))
+              (list
+                (cons 'name name)
+                (cons 'value value)
+                (cons
+                  'kind
+                  (javascript-vardef-assign-pattern-kind pattern))))))
+  (def (identifier-boundary-before? source index)
+       (or (= index 0)
+           (not (identifier-char? (string-ref source (- index 1))))))
+  (def (identifier-boundary-after? source index)
+       (or (>= index (string-length source))
+           (not (identifier-char? (string-ref source index)))))
+  (def (previous-word-before source index)
+       (let ([before (skip-horizontal-backward
+                       source
+                       (- index 1))])
+         (and (>= before 0)
+              (identifier-char? (string-ref source before))
+              (let loop ([start before])
+                (if (and (> start 0)
+                         (identifier-char?
+                           (string-ref source (- start 1))))
+                    (loop (- start 1))
+                    (substring source start (+ before 1)))))))
+  (def (javascript-declaration-keyword? word)
+       (or (string=? word "var")
+           (string=? word "let")
+           (string=? word "const")))
+  (def (javascript-vardef-assignment-preceded-by-declaration?
+         source
+         start)
+       (let ([word (previous-word-before source start)])
+         (and word (javascript-declaration-keyword? word))))
+  (def (javascript-vardef-assignment-finding rule path source
+         start end)
+       (finding-for-range-with-bindings rule path source start end
+         '()))
+  (def (scan-javascript-vardef-actual-assignments
+         rule
+         path
+         source
+         spec)
+       (let* ([name (alist-ref/default spec 'name "")]
+              [value (alist-ref/default spec 'value "")]
+              [rx (re (string-append
+                        (regex-escape-string name)
+                        "[ \\t]*=[ \\t]*"
+                        (regex-escape-string value)))]
+              [len (string-length source)])
+         (let loop ([start 0] [acc '()])
+           (let ([match (re-search rx source start)])
+             (if (not match)
+                 (reverse acc)
+                 (let* ([match-start (re-match-start match)]
+                        [match-end (re-match-end match)]
+                        [finding (and (identifier-boundary-before?
+                                        source
+                                        match-start)
+                                      (identifier-boundary-after?
+                                        source
+                                        (+ match-start
+                                           (string-length name)))
+                                      (identifier-boundary-after?
+                                        source
+                                        match-end)
+                                      (not (javascript-vardef-assignment-preceded-by-declaration?
+                                             source
+                                             match-start))
+                                      (javascript-vardef-assignment-finding rule path source match-start
+                                        match-end))]
+                        [next (max (+ match-start 1) match-end)])
+                   (loop next (if finding (cons finding acc) acc))))))))
+  (def (javascript-vardef-declaration-range source spec match)
+       (let* ([kind (alist-ref/default spec 'kind 'plain)]
+              [name (alist-ref/default spec 'name "")]
+              [decl-start (re-match-start match)]
+              [match-end (re-match-end match)]
+              [name-start (string-find-substring-from
+                            source
+                            name
+                            decl-start)]
+              [start (if (eq? kind 'statement-sequence)
+                         decl-start
+                         name-start)])
+         (and start (cons start match-end))))
+  (def (scan-javascript-vardef-declarations
+         rule
+         path
+         source
+         spec)
+       (let* ([name (alist-ref/default spec 'name "")]
+              [value (alist-ref/default spec 'value "")]
+              [rx (re (string-append
+                        "(var|let|const)[ \\t]+"
+                        (regex-escape-string name)
+                        "[ \\t]*=[ \\t]*"
+                        (regex-escape-string value)))]
+              [len (string-length source)])
+         (let loop ([start 0] [acc '()])
+           (let ([match (re-search rx source start)])
+             (if (not match)
+                 (reverse acc)
+                 (let* ([range (and (identifier-boundary-before?
+                                      source
+                                      (re-match-start match))
+                                    (identifier-boundary-after?
+                                      source
+                                      (re-match-end match))
+                                    (javascript-vardef-declaration-range
+                                      source
+                                      spec
+                                      match))]
+                        [finding (and range
+                                      (javascript-vardef-assignment-finding rule path source (car range)
+                                        (cdr range)))]
+                        [next (max (+ (re-match-start match) 1)
+                                   (re-match-end match))])
+                   (loop next (if finding (cons finding acc) acc))))))))
+  (def (scan-javascript-vardef-assign-pattern
+         rule
+         path
+         source
+         pattern)
+       (let ([option (rule-option-entry rule "vardef_assign")])
+         (and option
+              (let ([spec (javascript-vardef-assign-pattern-spec
+                            pattern)])
+                (and spec
+                     (let ([assignments (scan-javascript-vardef-actual-assignments
+                                          rule
+                                          path
+                                          source
+                                          spec)])
+                       (if (rule-option-explicit-false?
+                             rule
+                             "vardef_assign")
+                           assignments
+                           (append
+                             (scan-javascript-vardef-declarations
+                               rule
+                               path
+                               source
+                               spec)
+                             assignments)))))))))
diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index 168c88f..6879ad3 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -17,7 +17,8 @@
    (tree-sitter tree-sitter) (semgrep lang) (semgrep rule)
    (semgrep result) (semgrep result builders)
    (semgrep result extras) (semgrep result findings)
-   (semgrep engine generic-scan) (semgrep engine markup-scan)
+   (semgrep engine generic-scan)
+   (semgrep engine js-vardef-scan) (semgrep engine markup-scan)
    (semgrep engine regex-scan) (semgrep engine rule-plan)
    (semgrep engine regex-support) (semgrep engine text-support)
    (semgrep rule parse-rule) (semgrep parse parse-target)
@@ -151,6 +152,24 @@
                   (string=? (substring text (- len 2) len) "()"))
              (substring text 0 (- len 2))
              text)))
+  (def (identifier-boundary-before? source index)
+       (or (= index 0)
+           (not (identifier-char? (string-ref source (- index 1))))))
+  (def (identifier-boundary-after? source index)
+       (or (>= index (string-length source))
+           (not (identifier-char? (string-ref source index)))))
+  (def (previous-word-before source index)
+       (let ([before (skip-horizontal-backward
+                       source
+                       (- index 1))])
+         (and (>= before 0)
+              (identifier-char? (string-ref source before))
+              (let loop ([start before])
+                (if (and (> start 0)
+                         (identifier-char?
+                           (string-ref source (- start 1))))
+                    (loop (- start 1))
+                    (substring source start (+ before 1)))))))
   (def (decorated-field-pattern-spec pattern)
        (let* ([trimmed (string-trim pattern)]
               [len (string-length trimmed)])
@@ -459,172 +478,6 @@
                           (loop
                             next
                             (if finding (cons finding acc) acc))))))))))
-  (def (javascript-vardef-assign-pattern-kind pattern)
-       (let ([trimmed (string-trim pattern)])
-         (cond
-           [(sg-string-prefix? "<..." trimmed) 'expression-ellipsis]
-           [(string-find-substring pattern "\n") 'statement-sequence]
-           [else 'plain])))
-  (def (javascript-vardef-assign-pattern-spec pattern)
-       (let* ([match (re-search
-                       (re "([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*([^;>\\n]+)")
-                       pattern
-                       0)]
-              [name (and match (re-match-group match 1))]
-              [value0 (and match (string-trim (re-match-group match 2)))]
-              [ellipsis (and value0 (string-find-substring value0 "..."))]
-              [value (and value0
-                          (if ellipsis
-                              (string-trim (substring value0 0 ellipsis))
-                              value0))])
-         (and name
-              value
-              (not (string-find-substring name "$"))
-              (not (string-find-substring value "$"))
-              (list
-                (cons 'name name)
-                (cons 'value value)
-                (cons
-                  'kind
-                  (javascript-vardef-assign-pattern-kind pattern))))))
-  (def (identifier-boundary-before? source index)
-       (or (= index 0)
-           (not (identifier-char? (string-ref source (- index 1))))))
-  (def (identifier-boundary-after? source index)
-       (or (>= index (string-length source))
-           (not (identifier-char? (string-ref source index)))))
-  (def (previous-word-before source index)
-       (let ([before (skip-horizontal-backward
-                       source
-                       (- index 1))])
-         (and (>= before 0)
-              (identifier-char? (string-ref source before))
-              (let loop ([start before])
-                (if (and (> start 0)
-                         (identifier-char?
-                           (string-ref source (- start 1))))
-                    (loop (- start 1))
-                    (substring source start (+ before 1)))))))
-  (def (javascript-declaration-keyword? word)
-       (or (string=? word "var")
-           (string=? word "let")
-           (string=? word "const")))
-  (def (javascript-vardef-assignment-preceded-by-declaration?
-         source
-         start)
-       (let ([word (previous-word-before source start)])
-         (and word (javascript-declaration-keyword? word))))
-  (def (javascript-vardef-assignment-finding rule path source
-         start end)
-       (finding-for-range-with-bindings rule path source start end
-         '()))
-  (def (scan-javascript-vardef-actual-assignments
-         rule
-         path
-         source
-         spec)
-       (let* ([name (alist-ref/default spec 'name "")]
-              [value (alist-ref/default spec 'value "")]
-              [rx (re (string-append
-                        (regex-escape-string name)
-                        "[ \\t]*=[ \\t]*"
-                        (regex-escape-string value)))]
-              [len (string-length source)])
-         (let loop ([start 0] [acc '()])
-           (let ([match (re-search rx source start)])
-             (if (not match)
-                 (reverse acc)
-                 (let* ([match-start (re-match-start match)]
-                        [match-end (re-match-end match)]
-                        [finding (and (identifier-boundary-before?
-                                        source
-                                        match-start)
-                                      (identifier-boundary-after?
-                                        source
-                                        (+ match-start
-                                           (string-length name)))
-                                      (identifier-boundary-after?
-                                        source
-                                        match-end)
-                                      (not (javascript-vardef-assignment-preceded-by-declaration?
-                                             source
-                                             match-start))
-                                      (javascript-vardef-assignment-finding rule path source match-start
-                                        match-end))]
-                        [next (max (+ match-start 1) match-end)])
-                   (loop next (if finding (cons finding acc) acc))))))))
-  (def (javascript-vardef-declaration-range source spec match)
-       (let* ([kind (alist-ref/default spec 'kind 'plain)]
-              [name (alist-ref/default spec 'name "")]
-              [decl-start (re-match-start match)]
-              [match-end (re-match-end match)]
-              [name-start (string-find-substring-from
-                            source
-                            name
-                            decl-start)]
-              [start (if (eq? kind 'statement-sequence)
-                         decl-start
-                         name-start)])
-         (and start (cons start match-end))))
-  (def (scan-javascript-vardef-declarations
-         rule
-         path
-         source
-         spec)
-       (let* ([name (alist-ref/default spec 'name "")]
-              [value (alist-ref/default spec 'value "")]
-              [rx (re (string-append
-                        "(var|let|const)[ \\t]+"
-                        (regex-escape-string name)
-                        "[ \\t]*=[ \\t]*"
-                        (regex-escape-string value)))]
-              [len (string-length source)])
-         (let loop ([start 0] [acc '()])
-           (let ([match (re-search rx source start)])
-             (if (not match)
-                 (reverse acc)
-                 (let* ([range (and (identifier-boundary-before?
-                                      source
-                                      (re-match-start match))
-                                    (identifier-boundary-after?
-                                      source
-                                      (re-match-end match))
-                                    (javascript-vardef-declaration-range
-                                      source
-                                      spec
-                                      match))]
-                        [finding (and range
-                                      (javascript-vardef-assignment-finding rule path source (car range)
-                                        (cdr range)))]
-                        [next (max (+ (re-match-start match) 1)
-                                   (re-match-end match))])
-                   (loop next (if finding (cons finding acc) acc))))))))
-  (def (scan-javascript-vardef-assign-pattern
-         rule
-         path
-         source
-         pattern)
-       (let ([option (rule-option-entry rule "vardef_assign")])
-         (and option
-              (let ([spec (javascript-vardef-assign-pattern-spec
-                            pattern)])
-                (and spec
-                     (let ([assignments (scan-javascript-vardef-actual-assignments
-                                          rule
-                                          path
-                                          source
-                                          spec)])
-                       (if (rule-option-explicit-false?
-                             rule
-                             "vardef_assign")
-                           assignments
-                           (append
-                             (scan-javascript-vardef-declarations
-                               rule
-                               path
-                               source
-                               spec)
-                             assignments))))))))
   (def (json-key-value-pattern-spec pattern)
        (let* ([match (re-search
                        (re "\\$([A-Za-z_][A-Za-z0-9_]*)[ \\t]*:[ \\t]*([^,}\\n]+)")
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index d1d85b5..5f5803e 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -1,4 +1,7 @@
 (("src/semgrep/output/sarif.ss" . "E935456E4B1921FB")
+ ("src/semgrep/engine/js-vardef-scan.ss"
+   .
+   "BD82BDDFEDD7242D")
  ("src/semgrep/result.ss" . "22D23E40B49BA529")
  ("src/semgrep/engine/regex-scan.ss" . "D75414F0AEFE2F66")
  ("src/semgrep/engine/rule-plan.ss" . "6631789392AB5F80")
@@ -13,7 +16,7 @@
  ("src/semgrep/lang.ss" . "6982E07679D20836")
  ("src/semgrep/parse/parse-target.ss" . "97AA8FFEB12736DA")
  ("src/semgrep/result/extras.ss" . "DF0B3AAE2BAEB5D")
- ("src/semgrep/scan.ss" . "FC88B6BCF91FDD04")
+ ("src/semgrep/scan.ss" . "D0040BF672A86721")
  ("src/semgrep/engine/generic-scan.ss" . "F69D0ACD0DD62610")
  ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
  ("src/semgrep/rule.ss" . "E12C108153C181FA")
diff --git a/src/semgrep/engine/js-vardef-scan.ss b/src/semgrep/engine/js-vardef-scan.ss
new file mode 100644
index 0000000..d855510
--- /dev/null
+++ b/src/semgrep/engine/js-vardef-scan.ss
@@ -0,0 +1,212 @@
+(export
+  scan-javascript-vardef-assign-pattern)
+
+(import (except (jerboa prelude) meta atom?)
+        (std regex)
+        (semgrep rule)
+        (semgrep engine generic-scan)
+        (semgrep engine regex-support)
+        (semgrep engine text-support))
+
+(def (alist-ref/default xs key default)
+  (let ([found (assoc key xs)])
+    (if found (cdr found) default)))
+
+(def (rule-option-entry rule key)
+  (let ([options (rule-options rule)])
+    (or (assoc key options)
+        (and (string? key)
+             (assoc (string->symbol key) options))
+        (and (symbol? key)
+             (assoc (symbol->string key) options)))))
+
+(def (rule-option-explicit-false? rule key)
+  (let ([found (rule-option-entry rule key)])
+    (and found (not (cdr found)))))
+
+(def (sg-string-prefix? prefix s)
+  (let ([prefix-len (string-length prefix)]
+        [len (string-length s)])
+    (and (<= prefix-len len)
+         (string=? (substring s 0 prefix-len) prefix))))
+
+(def (string-find-substring s needle)
+  (let ([len (string-length s)])
+    (let loop ([i 0])
+      (cond
+        [(> i len) #f]
+        [(substring-at? s needle i) i]
+        [else (loop (+ i 1))]))))
+
+(def (identifier-char? ch)
+  (or (char-alphabetic? ch)
+      (char-numeric? ch)
+      (char=? ch #\_)
+      (char=? ch #\$)))
+
+(def (skip-horizontal-backward source i)
+  (let loop ([j i])
+    (if (and (>= j 0)
+             (let ([ch (string-ref source j)])
+               (or (char=? ch #\space)
+                   (char=? ch #\tab))))
+        (loop (- j 1))
+        j)))
+
+(def (javascript-vardef-assign-pattern-kind pattern)
+  (let ([trimmed (string-trim pattern)])
+    (cond
+      [(sg-string-prefix? "<..." trimmed) 'expression-ellipsis]
+      [(string-find-substring pattern "\n") 'statement-sequence]
+      [else 'plain])))
+
+(def (javascript-vardef-assign-pattern-spec pattern)
+  (let* ([match (re-search
+                  (re "([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*([^;>\\n]+)")
+                  pattern
+                  0)]
+         [name (and match (re-match-group match 1))]
+         [value0 (and match (string-trim (re-match-group match 2)))]
+         [ellipsis (and value0 (string-find-substring value0 "..."))]
+         [value (and value0
+                     (if ellipsis
+                         (string-trim (substring value0 0 ellipsis))
+                         value0))])
+    (and name
+         value
+         (not (string-find-substring name "$"))
+         (not (string-find-substring value "$"))
+         (list (cons 'name name)
+               (cons 'value value)
+               (cons 'kind
+                     (javascript-vardef-assign-pattern-kind pattern))))))
+
+(def (identifier-boundary-before? source index)
+  (or (= index 0)
+      (not (identifier-char? (string-ref source (- index 1))))))
+
+(def (identifier-boundary-after? source index)
+  (or (>= index (string-length source))
+      (not (identifier-char? (string-ref source index)))))
+
+(def (previous-word-before source index)
+  (let ([before (skip-horizontal-backward source (- index 1))])
+    (and (>= before 0)
+         (identifier-char? (string-ref source before))
+         (let loop ([start before])
+           (if (and (> start 0)
+                    (identifier-char? (string-ref source (- start 1))))
+               (loop (- start 1))
+               (substring source start (+ before 1)))))))
+
+(def (javascript-declaration-keyword? word)
+  (or (string=? word "var")
+      (string=? word "let")
+      (string=? word "const")))
+
+(def (javascript-vardef-assignment-preceded-by-declaration? source start)
+  (let ([word (previous-word-before source start)])
+    (and word (javascript-declaration-keyword? word))))
+
+(def (javascript-vardef-assignment-finding rule path source start end)
+  (finding-for-range-with-bindings rule path source start end '()))
+
+(def (scan-javascript-vardef-actual-assignments rule path source spec)
+  (let* ([name (alist-ref/default spec 'name "")]
+         [value (alist-ref/default spec 'value "")]
+         [rx (re (string-append
+                   (regex-escape-string name)
+                   "[ \\t]*=[ \\t]*"
+                   (regex-escape-string value)))]
+         [len (string-length source)])
+    (let loop ([start 0] [acc '()])
+      (let ([match (re-search rx source start)])
+        (if (not match)
+            (reverse acc)
+            (let* ([match-start (re-match-start match)]
+                   [match-end (re-match-end match)]
+                   [finding
+                    (and (identifier-boundary-before? source match-start)
+                         (identifier-boundary-after?
+                           source
+                           (+ match-start (string-length name)))
+                         (identifier-boundary-after? source match-end)
+                         (not (javascript-vardef-assignment-preceded-by-declaration?
+                                source
+                                match-start))
+                         (javascript-vardef-assignment-finding
+                           rule
+                           path
+                           source
+                           match-start
+                           match-end))]
+                   [next (max (+ match-start 1) match-end)])
+              (loop next (if finding (cons finding acc) acc))))))))
+
+(def (javascript-vardef-declaration-range source spec match)
+  (let* ([kind (alist-ref/default spec 'kind 'plain)]
+         [name (alist-ref/default spec 'name "")]
+         [decl-start (re-match-start match)]
+         [match-end (re-match-end match)]
+         [name-start (string-find-substring-from source name decl-start)]
+         [start (if (eq? kind 'statement-sequence)
+                    decl-start
+                    name-start)])
+    (and start (cons start match-end))))
+
+(def (scan-javascript-vardef-declarations rule path source spec)
+  (let* ([name (alist-ref/default spec 'name "")]
+         [value (alist-ref/default spec 'value "")]
+         [rx (re (string-append
+                   "(var|let|const)[ \\t]+"
+                   (regex-escape-string name)
+                   "[ \\t]*=[ \\t]*"
+                   (regex-escape-string value)))]
+         [len (string-length source)])
+    (let loop ([start 0] [acc '()])
+      (let ([match (re-search rx source start)])
+        (if (not match)
+            (reverse acc)
+            (let* ([range
+                    (and (identifier-boundary-before?
+                           source
+                           (re-match-start match))
+                         (identifier-boundary-after?
+                           source
+                           (re-match-end match))
+                         (javascript-vardef-declaration-range
+                           source
+                           spec
+                           match))]
+                   [finding
+                    (and range
+                         (javascript-vardef-assignment-finding
+                           rule
+                           path
+                           source
+                           (car range)
+                           (cdr range)))]
+                   [next (max (+ (re-match-start match) 1)
+                              (re-match-end match))])
+              (loop next (if finding (cons finding acc) acc))))))))
+
+(def (scan-javascript-vardef-assign-pattern rule path source pattern)
+  (let ([option (rule-option-entry rule "vardef_assign")])
+    (and option
+         (let ([spec (javascript-vardef-assign-pattern-spec pattern)])
+           (and spec
+                (let ([assignments
+                       (scan-javascript-vardef-actual-assignments
+                         rule
+                         path
+                         source
+                         spec)])
+                  (if (rule-option-explicit-false? rule "vardef_assign")
+                      assignments
+                      (append
+                        (scan-javascript-vardef-declarations
+                          rule
+                          path
+                          source
+                          spec)
+                        assignments))))))))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index c701f76..7c90bac 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -14,6 +14,7 @@
         (semgrep result extras)
         (semgrep result findings)
         (semgrep engine generic-scan)
+        (semgrep engine js-vardef-scan)
         (semgrep engine markup-scan)
         (semgrep engine regex-scan)
         (semgrep engine rule-plan)
@@ -166,6 +167,24 @@
         (substring text 0 (- len 2))
         text)))
 
+(def (identifier-boundary-before? source index)
+  (or (= index 0)
+      (not (identifier-char? (string-ref source (- index 1))))))
+
+(def (identifier-boundary-after? source index)
+  (or (>= index (string-length source))
+      (not (identifier-char? (string-ref source index)))))
+
+(def (previous-word-before source index)
+  (let ([before (skip-horizontal-backward source (- index 1))])
+    (and (>= before 0)
+         (identifier-char? (string-ref source before))
+         (let loop ([start before])
+           (if (and (> start 0)
+                    (identifier-char? (string-ref source (- start 1))))
+               (loop (- start 1))
+               (substring source start (+ before 1)))))))
+
 (def (decorated-field-pattern-spec pattern)
   (let* ([trimmed (string-trim pattern)]
          [len (string-length trimmed)])
@@ -471,164 +490,6 @@
                      (loop next
                            (if finding (cons finding acc) acc))))))))))
 
-(def (javascript-vardef-assign-pattern-kind pattern)
-  (let ([trimmed (string-trim pattern)])
-    (cond
-      [(sg-string-prefix? "<..." trimmed) 'expression-ellipsis]
-      [(string-find-substring pattern "\n") 'statement-sequence]
-      [else 'plain])))
-
-(def (javascript-vardef-assign-pattern-spec pattern)
-  (let* ([match (re-search
-                  (re "([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*([^;>\\n]+)")
-                  pattern
-                  0)]
-         [name (and match (re-match-group match 1))]
-         [value0 (and match (string-trim (re-match-group match 2)))]
-         [ellipsis (and value0 (string-find-substring value0 "..."))]
-         [value (and value0
-                     (if ellipsis
-                         (string-trim (substring value0 0 ellipsis))
-                         value0))])
-    (and name
-         value
-         (not (string-find-substring name "$"))
-         (not (string-find-substring value "$"))
-         (list (cons 'name name)
-               (cons 'value value)
-               (cons 'kind
-                     (javascript-vardef-assign-pattern-kind pattern))))))
-
-(def (identifier-boundary-before? source index)
-  (or (= index 0)
-      (not (identifier-char? (string-ref source (- index 1))))))
-
-(def (identifier-boundary-after? source index)
-  (or (>= index (string-length source))
-      (not (identifier-char? (string-ref source index)))))
-
-(def (previous-word-before source index)
-  (let ([before (skip-horizontal-backward source (- index 1))])
-    (and (>= before 0)
-         (identifier-char? (string-ref source before))
-         (let loop ([start before])
-           (if (and (> start 0)
-                    (identifier-char? (string-ref source (- start 1))))
-               (loop (- start 1))
-               (substring source start (+ before 1)))))))
-
-(def (javascript-declaration-keyword? word)
-  (or (string=? word "var")
-      (string=? word "let")
-      (string=? word "const")))
-
-(def (javascript-vardef-assignment-preceded-by-declaration? source start)
-  (let ([word (previous-word-before source start)])
-    (and word (javascript-declaration-keyword? word))))
-
-(def (javascript-vardef-assignment-finding rule path source start end)
-  (finding-for-range-with-bindings rule path source start end '()))
-
-(def (scan-javascript-vardef-actual-assignments rule path source spec)
-  (let* ([name (alist-ref/default spec 'name "")]
-         [value (alist-ref/default spec 'value "")]
-         [rx (re (string-append
-                   (regex-escape-string name)
-                   "[ \\t]*=[ \\t]*"
-                   (regex-escape-string value)))]
-         [len (string-length source)])
-    (let loop ([start 0] [acc '()])
-      (let ([match (re-search rx source start)])
-        (if (not match)
-            (reverse acc)
-            (let* ([match-start (re-match-start match)]
-                   [match-end (re-match-end match)]
-                   [finding
-                    (and (identifier-boundary-before? source match-start)
-                         (identifier-boundary-after?
-                           source
-                           (+ match-start (string-length name)))
-                         (identifier-boundary-after? source match-end)
-                         (not (javascript-vardef-assignment-preceded-by-declaration?
-                                source
-                                match-start))
-                         (javascript-vardef-assignment-finding
-                           rule
-                           path
-                           source
-                           match-start
-                           match-end))]
-                   [next (max (+ match-start 1) match-end)])
-              (loop next (if finding (cons finding acc) acc))))))))
-
-(def (javascript-vardef-declaration-range source spec match)
-  (let* ([kind (alist-ref/default spec 'kind 'plain)]
-         [name (alist-ref/default spec 'name "")]
-         [decl-start (re-match-start match)]
-         [match-end (re-match-end match)]
-         [name-start (string-find-substring-from source name decl-start)]
-         [start (if (eq? kind 'statement-sequence)
-                    decl-start
-                    name-start)])
-    (and start (cons start match-end))))
-
-(def (scan-javascript-vardef-declarations rule path source spec)
-  (let* ([name (alist-ref/default spec 'name "")]
-         [value (alist-ref/default spec 'value "")]
-         [rx (re (string-append
-                   "(var|let|const)[ \\t]+"
-                   (regex-escape-string name)
-                   "[ \\t]*=[ \\t]*"
-                   (regex-escape-string value)))]
-         [len (string-length source)])
-    (let loop ([start 0] [acc '()])
-      (let ([match (re-search rx source start)])
-        (if (not match)
-            (reverse acc)
-            (let* ([range
-                    (and (identifier-boundary-before?
-                           source
-                           (re-match-start match))
-                         (identifier-boundary-after?
-                           source
-                           (re-match-end match))
-                         (javascript-vardef-declaration-range
-                           source
-                           spec
-                           match))]
-                   [finding
-                    (and range
-                         (javascript-vardef-assignment-finding
-                           rule
-                           path
-                           source
-                           (car range)
-                           (cdr range)))]
-                   [next (max (+ (re-match-start match) 1)
-                              (re-match-end match))])
-              (loop next (if finding (cons finding acc) acc))))))))
-
-(def (scan-javascript-vardef-assign-pattern rule path source pattern)
-  (let ([option (rule-option-entry rule "vardef_assign")])
-    (and option
-         (let ([spec (javascript-vardef-assign-pattern-spec pattern)])
-           (and spec
-                (let ([assignments
-                       (scan-javascript-vardef-actual-assignments
-                         rule
-                         path
-                         source
-                         spec)])
-                  (if (rule-option-explicit-false? rule "vardef_assign")
-                      assignments
-                      (append
-                        (scan-javascript-vardef-declarations
-                          rule
-                          path
-                          source
-                          spec)
-                        assignments))))))))
-
 (def (json-key-value-pattern-spec pattern)
   (let* ([match (re-search
                   (re "\\$([A-Za-z_][A-Za-z0-9_]*)[ \\t]*:[ \\t]*([^,}\\n]+)")