Structural matcher: semgrep typed-metavariable syntax `(Type $VAR)` for Java

ober

8240b87351fbcc0fca46383a62c84de82975d372

diff --git a/lib/semgrep/match/structural.sls b/lib/semgrep/match/structural.sls
index 8ded0c2..ac6b3a0 100644
--- a/lib/semgrep/match/structural.sls
+++ b/lib/semgrep/match/structural.sls
@@ -112,6 +112,60 @@
                                  (loop (+ i 1) (cons ch acc))))
                            (loop (+ i 1) (cons ch acc))))
                      (loop (+ i 1) (cons ch acc))))))))
+  (def (java-type-char? c)
+       (or (and (char<=? #\a c) (char<=? c #\z))
+           (and (char<=? #\A c) (char<=? c #\Z))
+           (and (char<=? #\0 c) (char<=? c #\9))
+           (memv c '(#\_ #\. #\< #\> #\, #\[ #\]))))
+  (def (java-typed-metavar-content source start len)
+       (let ([s1 (go-skip-spaces source start len)])
+         (and (< s1 len)
+              (let ([c0 (string-ref source s1)])
+                (or (and (char<=? #\a c0) (char<=? c0 #\z))
+                    (and (char<=? #\A c0) (char<=? c0 #\Z))
+                    (char=? c0 #\_)))
+              (let scan-type ([k s1])
+                (if (and (< k len) (java-type-char? (string-ref source k)))
+                    (scan-type (+ k 1))
+                    (let ([sp (go-skip-spaces source k len)])
+                      (and (> sp k)
+                           (< sp len)
+                           (char=? (string-ref source sp) #\$)
+                           (< (+ sp 1) len)
+                           (identifier-start? (string-ref source (+ sp 1)))
+                           (go-scan-ident source (+ sp 1) len))))))))
+  (def (java-strip-typed-metavars source)
+       (let ([len (string-length source)])
+         (let loop ([i 0] [acc '()])
+           (if (>= i len)
+               (list->string (reverse acc))
+               (let ([ch (string-ref source i)])
+                 (if (char=? ch #\()
+                     (let* ([content-end (java-typed-metavar-content
+                                           source
+                                           (+ i 1)
+                                           len)]
+                            [close (and content-end
+                                        (go-skip-spaces
+                                          source
+                                          content-end
+                                          len))])
+                       (if (and close
+                                (< close len)
+                                (char=? (string-ref source close) #\)))
+                           (loop
+                             (+ close 1)
+                             (append
+                               (reverse
+                                 (string->list
+                                   (string-trim
+                                     (substring
+                                       source
+                                       (+ i 1)
+                                       content-end))))
+                               acc))
+                           (loop (+ i 1) (cons ch acc))))
+                     (loop (+ i 1) (cons ch acc))))))))
   (def (sg-string-prefix? prefix s)
        (let ([prefix-len (string-length prefix)]
              [len (string-length s)])
@@ -229,6 +283,9 @@
   (def (rewrite-metavariables language source)
        (let* ([go? (go-language? language)]
               [source (if go? (go-strip-typed-metavars source) source)]
+              [source (if (string=? language "java")
+                          (java-strip-typed-metavars source)
+                          source)]
               [len (string-length source)])
          (let loop ([i 0]
                     [state 'normal]
diff --git a/src/semgrep/match/structural.ss b/src/semgrep/match/structural.ss
index fc23fd6..9a84678 100644
--- a/src/semgrep/match/structural.ss
+++ b/src/semgrep/match/structural.ss
@@ -128,6 +128,46 @@
                       (loop (+ i 1) (cons ch acc))))
                 (loop (+ i 1) (cons ch acc))))))))
 
+(def (java-type-char? c)
+  (or (and (char<=? #\a c) (char<=? c #\z))
+      (and (char<=? #\A c) (char<=? c #\Z))
+      (and (char<=? #\0 c) (char<=? c #\9))
+      (memv c '(#\_ #\. #\< #\> #\, #\[ #\]))))
+
+(def (java-typed-metavar-content source start len)
+  (let ([s1 (go-skip-spaces source start len)])
+    (and (< s1 len)
+         (let ([c0 (string-ref source s1)])
+           (or (and (char<=? #\a c0) (char<=? c0 #\z))
+               (and (char<=? #\A c0) (char<=? c0 #\Z))
+               (char=? c0 #\_)))
+         (let scan-type ([k s1])
+           (if (and (< k len) (java-type-char? (string-ref source k)))
+               (scan-type (+ k 1))
+               (let ([sp (go-skip-spaces source k len)])
+                 (and (> sp k) (< sp len)
+                      (char=? (string-ref source sp) #\$)
+                      (< (+ sp 1) len)
+                      (identifier-start? (string-ref source (+ sp 1)))
+                      (go-scan-ident source (+ sp 1) len))))))))
+
+(def (java-strip-typed-metavars source)
+  (let ([len (string-length source)])
+    (let loop ([i 0] [acc '()])
+      (if (>= i len)
+          (list->string (reverse acc))
+          (let ([ch (string-ref source i)])
+            (if (char=? ch #\()
+                (let* ([content-end (java-typed-metavar-content source (+ i 1) len)]
+                       [close (and content-end (go-skip-spaces source content-end len))])
+                  (if (and close (< close len) (char=? (string-ref source close) #\)))
+                      (loop (+ close 1)
+                            (append (reverse (string->list
+                                               (string-trim (substring source (+ i 1) content-end))))
+                                    acc))
+                      (loop (+ i 1) (cons ch acc))))
+                (loop (+ i 1) (cons ch acc))))))))
+
 (def (sg-string-prefix? prefix s)
   (let ([prefix-len (string-length prefix)]
         [len (string-length s)])
@@ -266,6 +306,9 @@
 (def (rewrite-metavariables language source)
   (let* ([go? (go-language? language)]
          [source (if go? (go-strip-typed-metavars source) source)]
+         [source (if (string=? language "java")
+                     (java-strip-typed-metavars source)
+                     source)]
          [len (string-length source)])
     (let loop ([i 0] [state 'normal] [escaped? #f]
                [paren-depth 0] [brace-depth 0] [acc '()])