Go structural: implement deep-expression matching
ober
03a2b049a82b73a6cb29627c8e8bbd345e7cee50
--- a/lib/semgrep/match/structural.sls +++ b/lib/semgrep/match/structural.sls @@ -976,6 +976,63 @@ (if (null? ys) (loop (cdr remaining) inner-acc) (inner (cdr ys) (cons (car ys) inner-acc))))))) + (def (go-deep-pattern-arg pattern) + (and (string=? (node-type pattern) "call_expression") + (>= (node-matchable-child-count pattern) 2) + (let ([fn (node-matchable-child pattern 0)]) + (and fn + (let ([is-deep (and (string=? + (node-type fn) + "identifier") + (string=? + (node-text fn) + deep-token-name))]) + (node-close! fn) + (and is-deep + (let ([args (node-matchable-child pattern 1)]) + (and args + (let ([arg (single-named-child args)]) + (node-close! args) + arg))))))))) + (def (go-deep-match-here-or-descendant + language + pat + target + bindings) + (let ([here (structural-node-matches + language + pat + target + bindings)]) + (if (not (null? here)) + here + (let loop ([i 0]) + (if (>= i (node-matchable-child-count target)) + '() + (let ([child (node-matchable-child target i)]) + (if child + (let ([r (go-deep-match-here-or-descendant + language + pat + child + bindings)]) + (node-close! child) + (if (null? r) (loop (+ i 1)) r)) + (loop (+ i 1))))))))) + (def (go-deep-expression-matches + language + pattern + target + bindings) + (let ([arg (go-deep-pattern-arg pattern)]) + (and arg + (let ([result (go-deep-match-here-or-descendant + language + arg + target + bindings)]) + (node-close! arg) + result)))) (def (structural-node-match language pattern @@ -995,6 +1052,12 @@ (let ([text (node-text pattern)]) (cond [(ellipsis-node? pattern) (list bindings)] + [(go-deep-expression-matches + language + pattern + target + bindings) => + (lambda (matches) matches)] [(quoted-ellipsis-metavariable-name-from-text text) => (lambda (name) (let ([next (if (string=? language "json") --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -4,10 +4,10 @@ ("src/semgrep/lang.ss" . "6982E07679D20836") ("src/semgrep/parse/parse-target.ss" . "B1616180DE7038ED") ("src/semgrep/scan.ss" . "A09A5F4FE16A2036") - ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") - ("src/semgrep/rule.ss" . "E12C108153C181FA") + ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") - ("src/semgrep/match/structural.ss" . "F46F2E199123036E") + ("src/semgrep/rule.ss" . "E12C108153C181FA") + ("src/semgrep/match/structural.ss" . "F93B68FF81D41586") ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E") ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC")) --- a/src/semgrep/match/structural.ss +++ b/src/semgrep/match/structural.ss @@ -1008,6 +1008,50 @@ (loop (cdr remaining) inner-acc) (inner (cdr ys) (cons (car ys) inner-acc))))))) +;; Go deep-expression operator `<... X ...>`, rewritten to __sg_deep__(X). +;; If `pattern` is such a call, return the inner pattern node X (caller closes), +;; else #f. +(def (go-deep-pattern-arg pattern) + (and (string=? (node-type pattern) "call_expression") + (>= (node-matchable-child-count pattern) 2) + (let ([fn (node-matchable-child pattern 0)]) + (and fn + (let ([is-deep (and (string=? (node-type fn) "identifier") + (string=? (node-text fn) deep-token-name))]) + (node-close! fn) + (and is-deep + (let ([args (node-matchable-child pattern 1)]) + (and args + (let ([arg (single-named-child args)]) + (node-close! args) + arg))))))))) + +;; X matches `target` or any of its descendants (deep search). +(def (go-deep-match-here-or-descendant language pat target bindings) + (let ([here (structural-node-matches language pat target bindings)]) + (if (not (null? here)) + here + (let loop ([i 0]) + (if (>= i (node-matchable-child-count target)) + '() + (let ([child (node-matchable-child target i)]) + (if child + (let ([r (go-deep-match-here-or-descendant + language pat child bindings)]) + (node-close! child) + (if (null? r) (loop (+ i 1)) r)) + (loop (+ i 1))))))))) + +;; Returns the match list (possibly empty) if `pattern` is a deep-expression, +;; or #f if it is not (so the caller falls through to normal matching). +(def (go-deep-expression-matches language pattern target bindings) + (let ([arg (go-deep-pattern-arg pattern)]) + (and arg + (let ([result (go-deep-match-here-or-descendant + language arg target bindings)]) + (node-close! arg) + result)))) + (def (structural-node-match language pattern target bindings) (let ([matches (structural-node-matches language pattern target bindings)]) (and (not (null? matches)) @@ -1017,6 +1061,8 @@ (let ([text (node-text pattern)]) (cond [(ellipsis-node? pattern) (list bindings)] + [(go-deep-expression-matches language pattern target bindings) + => (lambda (matches) matches)] [(quoted-ellipsis-metavariable-name-from-text text) => (lambda (name) (let ([next (if (string=? language "json")