PHP: scaffold metavariable binding text before pattern-not re-match

ober

5bed7523e34127c4b6b6d050ae4e32a95b21190a

diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index c662fbe..9883119 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -23729,6 +23729,11 @@
        (any? positive-pattern-entry? clauses))
   (def (negative-only-text-clause? clause)
        (case (car clause) [(pattern-not) #t] [else #f]))
+  (def (php-fragment-as-target language source)
+       (if (and (php-language? language)
+                (not (string-find-substring source "<?php")))
+           (string-append "<?php " source ";")
+           source))
   (def (negative-only-text-clause-passes?
          rule
          clause
@@ -23740,7 +23745,7 @@
                  rule
                  (cdr clause)
                  language
-                 source))]
+                 (php-fragment-as-target language source)))]
          [else #f]))
   (def (negative-only-patterns-text-findings
          rule
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index 86b3a0d..e7f9c9f 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" . "984D9B3DED8F3EFE")
-  ("src/semgrep/scan.ss" . "A9DB8F06E64C818F")
+  ("src/semgrep/scan.ss" . "80654DC26C131E95")
+  ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
+  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
   ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
   ("src/semgrep/rule.ss" . "E12C108153C181FA")
-  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
-  ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
   ("src/semgrep/match/structural.ss" . "8144F1297C91CF9F")
   ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E")
   ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC"))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index 3ee350c..5fc08e4 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -23931,10 +23931,22 @@
     [(pattern-not) #t]
     [else #f]))
 
+;; A metavariable binding text is a bare fragment; PHP needs a <?php prelude
+;; (and a statement terminator) before it parses as a target for re-matching.
+(def (php-fragment-as-target language source)
+  (if (and (php-language? language)
+           (not (string-find-substring source "<?php")))
+      (string-append "<?php " source ";")
+      source))
+
 (def (negative-only-text-clause-passes? rule clause language source)
   (case (car clause)
     [(pattern-not)
-     (not (positive-entry-text-matches? rule (cdr clause) language source))]
+     (not (positive-entry-text-matches?
+            rule
+            (cdr clause)
+            language
+            (php-fragment-as-target language source)))]
     [else #f]))
 
 (def (negative-only-patterns-text-findings rule clauses language source)