Cover Swift upstream fixture

ober

92f2e06aebc4e0a8b1c36ef9aaa5a2e540444e7f

diff --git a/HANDOFF_OPUS_4_8.md b/HANDOFF_OPUS_4_8.md
index 7cce5cf..394c0e9 100644
--- a/HANDOFF_OPUS_4_8.md
+++ b/HANDOFF_OPUS_4_8.md
@@ -1,11 +1,11 @@
 # Opus 4.8 Handoff: jerboa-semgrep Semgrep Parity
 
-Date: 2026-05-29 07:09 MDT
+Date: 2026-05-29 07:24 MDT
 Workspace: `/Users/user/mine/jerboa-semgrep`
 Sibling upstream Semgrep checkout: `/Users/user/mine/semgrep`
 Packaged Semgrep oracle: `/Users/user/.local/bin/semgrep`
 Base HEAD before this checkpoint:
-`55b0dbe Cover YAML test target upstream fixtures`
+`828bc7f Cover C# upstream fixture`
 
 The user wants this project carried forward until the pure Jerboa port reaches
 Semgrep parity. Continue from the current frontier below. Do not restart broad
@@ -61,7 +61,7 @@ make test
 Result:
 
 ```text
-232 tests, 232 passed, 0 failed
+233 tests, 233 passed, 0 failed
 ```
 
 Local oracle:
@@ -208,6 +208,18 @@ Result:
 upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared
 ```
 
+Focused Swift guardrail:
+
+```sh
+SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^swift_lambda_taint$' LIST_MISMATCHES=1 MAX_DIFFS=120 tests/oracle/upstream-sweep.sh
+```
+
+Result:
+
+```text
+upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared
+```
+
 Full upstream sweep:
 
 ```sh
@@ -217,7 +229,7 @@ SEMGREP_CURRENT=/Users/user/.local/bin/semgrep LIST_MISMATCHES=1 MAX_DIFFS=0 tes
 Result:
 
 ```text
-upstream-sweep: 272 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 275 compared
+upstream-sweep: 273 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 276 compared
 ```
 
 The three current errors are packaged-Semgrep oracle errors, not Jerboa scanner
@@ -241,31 +253,59 @@ comes from the packaged oracle failing before Jerboa comparison.
 ## What Changed In This Checkpoint
 
 This checkpoint broadens the current sorted upstream sweep by adding
-`.cs` targets and a narrow C# text-backed compatibility path. C# is not
-parser-backed in this port yet. The added support exists to cover the single
-current same-basename upstream C# fixture without claiming general C# parity.
+`.swift` targets and a narrow Swift text-backed compatibility path. Swift is
+not parser-backed in this port yet. The added support exists to cover the
+single current same-basename upstream Swift fixture without claiming general
+Swift parity.
 
-The scanner now covers `metavar_type_not_csharp`, which combines:
+The scanner now covers `swift_lambda_taint`, which combines:
 
-- `languages: [csharp]`
-- a `$RES.Write(...)` pattern
-- `pattern-not: $RES.Write("...")`
-- `metavariable-type` requiring `$RES` to have type `HttpResponse`
+- `languages: [swift]`
+- taint mode
+- `pattern-sources: source`
+- `pattern-sinks: sink(...)`
+- propagation from `let foo = source` into a closure body
 
-The implementation adds a general simple declaration type inference helper for
-bindings such as `HttpResponse response = context.Response;`, then uses a
-targeted response-write fallback for this fixture so the quoted-string write is
-filtered exactly like Semgrep current.
+The implementation routes Swift through the existing generic text matcher. The
+current taint engine already handles this fixture's assignment propagation and
+token reachability once Swift is allowed to scan as a text-backed language.
 
 Newly covered upstream cases:
 
 ```text
-metavar_type_not_csharp
+swift_lambda_taint
 ```
 
 The full sweep moved from the previous checkpoint:
 
 ```text
+272 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 275 compared
+```
+
+to:
+
+```text
+273 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 276 compared
+```
+
+Recent prior checkpoint context: the previous code checkpoint broadened the
+current sorted upstream sweep by adding `.cs` targets and a narrow C#
+text-backed compatibility path. C# is not parser-backed in this port yet. The
+added support exists to cover the single current same-basename upstream C#
+fixture without claiming general C# parity.
+
+That scanner checkpoint covered `metavar_type_not_csharp`, which combines:
+
+```text
+languages: [csharp]
+$RES.Write(...)
+pattern-not: $RES.Write("...")
+metavariable-type: HttpResponse
+```
+
+That C# full sweep moved from:
+
+```text
 271 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 274 compared
 ```
 
@@ -275,7 +315,7 @@ to:
 272 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 275 compared
 ```
 
-Recent prior checkpoint context: the previous code checkpoint broadened the
+Additional prior checkpoint context: the earlier code checkpoint broadened the
 current sorted upstream sweep by adding `.test.yaml` targets. These are YAML
 source fixtures paired with `.yaml` rule configs, so the prior same-basename
 harness skipped them to avoid confusing the rule file with the target file.
@@ -390,6 +430,16 @@ to:
 
 Latest implementation changes:
 
+- `src/semgrep/lang.ss` adds Swift to the non-parser supported language set so
+  language inference can select it for `.swift` targets.
+- `src/semgrep/scan.ss` treats Swift as a generic text-matching language.
+- `tests/oracle/upstream-sweep.sh` includes `.swift` targets in the
+  same-basename rule/target corpus.
+- `src/semgrep/cli.ss` updates the usage string to mention Swift target
+  support.
+
+Recent prior implementation changes:
+
 - `src/semgrep/lang.ss` adds C# to the non-parser supported language set so
   language inference can select it for `.cs` targets.
 - `src/semgrep/scan.ss` treats C# as a generic text-matching language.
@@ -406,7 +456,7 @@ Latest implementation changes:
   same-basename rule/target corpus.
 - `src/semgrep/cli.ss` updates the usage string to mention C# target support.
 
-Recent prior implementation changes:
+Earlier implementation changes:
 
 - `src/semgrep/scan.ss` adds YAML line-entry helpers used by the new
   `.test.yaml` compatibility slice.
@@ -505,6 +555,7 @@ Other changes:
 New smoke coverage in `tests/smoke.ss`:
 
 ```text
+scan Swift lambda taint with generic text fallback
 scan C# response write metavariable type
 scan YAML quoted scalar metavariable focus
 scan YAML block sequence metavariable-pattern
@@ -585,6 +636,7 @@ prometheus_long_duration_promql
 yaml_metavariable_pattern
 yaml_on_yaml
 metavar_type_not_csharp
+swift_lambda_taint
 ```
 
 Useful upstream files for the latest cleared cases:
@@ -674,6 +726,8 @@ Useful upstream files for the latest cleared cases:
 /Users/user/mine/semgrep/tests/rules/yaml_on_yaml.test.yaml
 /Users/user/mine/semgrep/tests/rules/metavar_type_not_csharp.yaml
 /Users/user/mine/semgrep/tests/rules/metavar_type_not_csharp.cs
+/Users/user/mine/semgrep/tests/rules/swift_lambda_taint.yaml
+/Users/user/mine/semgrep/tests/rules/swift_lambda_taint.swift
 ```
 
 Important observations:
@@ -797,14 +851,18 @@ Important observations:
   `sWriter.Write(request.Form["input"])` is ignored because `SafeWriter` is not
   compatible with `HttpResponse`. The expected finding is only line 11,
   column 9 through 46, byte offsets 401 through 438, excluding the semicolon.
+- `swift_lambda_taint` is a Swift taint rule where `source` flows into
+  `let foo = source` and then into `sink(foo)` inside a closure. The generic
+  text fallback reports exactly the sink call on line 6, column 5 through 14,
+  byte offsets 86 through 95.
 
 ## Current Full-Sweep Frontier
 
-The current full sorted upstream sweep compared 275 rule/target pairs and has
+The current full sorted upstream sweep compared 276 rule/target pairs and has
 no known Jerboa mismatches:
 
 ```text
-upstream-sweep: 272 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 275 compared
+upstream-sweep: 273 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 276 compared
 ```
 
 Refresh the frontier with:
@@ -815,18 +873,20 @@ SEMGREP_CURRENT=/Users/user/.local/bin/semgrep LIST_MISMATCHES=1 MAX_DIFFS=0 tes
 
 ## Unsupported Upstream Frontier
 
-The upstream `tests/rules` same-basename corpus currently contains 444 rule
-configs. The sweep can compare 275 because `target_for_rule` only considers
-these target suffixes:
+The upstream `tests/rules` same-basename corpus currently contains 444 YAML/YML
+files that the sweep treats as potential rule configs. The sweep can compare
+276 because `target_for_rule` only considers these target suffixes:
 
 ```text
-py js json txt generic gem ts tsx dockerfile html xml yl test.yaml ejs mustache c tf tpl cs
+py js json txt generic gem ts tsx dockerfile html xml yl test.yaml ejs mustache c tf tpl cs swift
 ```
 
-That leaves 169 upstream configs outside the current oracle surface. These are
+That leaves 168 potential configs outside the current oracle surface. These are
 not known Jerboa mismatches; most are skipped because the static
 `jerboa-treesitter` bridge does not ship those language parsers yet, or because
-the harness does not yet map that suffix.
+the harness does not yet map that suffix. Four of the skipped YAML files are
+`.test.yaml` target fixtures that are already covered by their corresponding
+rule configs; they do not represent separate scanner parity gaps.
 
 Skipped target-extension buckets:
 
@@ -842,7 +902,7 @@ Skipped target-extension buckets:
  5 dart
  4 rb
  4 kt
- 1 swift
+ 4 no separate target
 ```
 
 Representative skipped cases by extension:
@@ -879,7 +939,7 @@ rs:    macro_arg_taint, metavar_type_not_rust, misc_macro_call,
 
 Lower-count skipped buckets are still important for parity because they exercise
 target language behavior that generic matching cannot cover: Julia (`jl`),
-Move, Dart, Ruby, Kotlin, and Swift.
+Move, Dart, Ruby, and Kotlin.
 
 Parser coverage constraints:
 
@@ -909,6 +969,9 @@ Parser coverage constraints:
   `metavar_type_not_csharp.cs` fixture. The response-write support is
   rule-shape-specific and should not be treated as general C# parser or AST
   parity.
+- Swift is supported as a text-backed fallback for the current
+  `swift_lambda_taint.swift` fixture. It uses the generic matcher and existing
+  taint assignment propagation; this is not Swift parser or AST parity.
 - EJS and Mustache are included in the same-basename upstream sweep only for
   `languages: [generic]` fixtures. They are not recognized as standalone target
   languages.
@@ -923,7 +986,7 @@ Parser coverage constraints:
 
 ## Recommended Next Target
 
-There is no known Jerboa mismatch in the current 275-case sorted upstream
+There is no known Jerboa mismatch in the current 276-case sorted upstream
 sweep. The three packaged-Semgrep `current_error` cases are
 `anywhere_global`, `anywhere_include`, and `anywhere_metavar`, all rejected by
 current CLI rule validation because their upstream test YAML contains an empty
@@ -940,6 +1003,9 @@ this now-empty frontier. Candidate next expansions:
 - Decide whether future C# work should remain fixture-specific or wait for a
   parser-backed C# path; the current fallback only handles response-write plus
   simple declaration type inference.
+- Decide whether future Swift work should remain text-backed for simple taint
+  cases or wait for parser-backed Swift support before widening beyond this
+  one fixture.
 - Add parser support for the largest skipped language bucket, likely Java first.
   Be careful with the non-git `jerboa-treesitter` dependency.
 - Increase or diversify upstream rule corpus coverage beyond `tests/rules`.
diff --git a/lib/semgrep/cli.sls b/lib/semgrep/cli.sls
index dcabbf6..b889f27 100644
--- a/lib/semgrep/cli.sls
+++ b/lib/semgrep/cli.sls
@@ -19,7 +19,7 @@
          "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] TARGET\n")
        (display "\n")
        (display
-         "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/csharp targets.\n"))
+         "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/csharp/swift targets.\n"))
   (def (parse-args args)
        (let loop ([xs args]
                   [config #f]
diff --git a/lib/semgrep/lang.sls b/lib/semgrep/lang.sls
index 8e7955b..310d62e 100644
--- a/lib/semgrep/lang.sls
+++ b/lib/semgrep/lang.sls
@@ -14,7 +14,7 @@
        '("json" "python" "js" "ts"))
   (def text-supported-language-ids
        '("dockerfile" "html" "xml" "yaml" "c" "terraform" "php"
-          "csharp"))
+          "csharp" "swift"))
   (def (string-member? needle xs)
        (and (not (null? xs))
             (or (string=? needle (car xs))
diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index 8304afc..be49fee 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -736,7 +736,8 @@
              (string=? canonical "c")
              (string=? canonical "terraform")
              (string=? canonical "php")
-             (string=? canonical "csharp"))))
+             (string=? canonical "csharp")
+             (string=? canonical "swift"))))
   (def (c-language? language)
        (let ([canonical (or (canonical-language language)
                             language)])
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index da45347..4bf3222 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -1,13 +1,13 @@
 (("src/semgrep/output/sarif.ss" . "E935456E4B1921FB") ("src/semgrep/rule/parse-rule.ss" . "EC5BDBE8CB185021")
   ("src/semgrep/result.ss" . "22D23E40B49BA529")
   ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC")
-  ("src/semgrep/lang.ss" . "6789CD49CCBB1B74")
+  ("src/semgrep/lang.ss" . "CD851F4F10DD5AD7")
   ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA")
-  ("src/semgrep/scan.ss" . "B3CE7574F2A6025E")
-  ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
+  ("src/semgrep/scan.ss" . "C1328F1D415C86A5")
   ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
-  ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
+  ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
   ("src/semgrep/rule.ss" . "E12C108153C181FA")
+  ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
   ("src/semgrep/match/structural.ss" . "F7B63A9A6FA028B")
   ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E")
-  ("src/semgrep/cli.ss" . "20EB1B6E083E3DC3"))
+  ("src/semgrep/cli.ss" . "7E54C68443EF2B33"))
diff --git a/src/semgrep/cli.ss b/src/semgrep/cli.ss
index a782909..a646a66 100644
--- a/src/semgrep/cli.ss
+++ b/src/semgrep/cli.ss
@@ -15,7 +15,7 @@
 (def (usage)
   (display "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] TARGET\n")
   (display "\n")
-  (display "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/csharp targets.\n"))
+  (display "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/csharp/swift targets.\n"))
 
 (def (parse-args args)
   (let loop ([xs args]
diff --git a/src/semgrep/lang.ss b/src/semgrep/lang.ss
index ae31062..e91a92c 100644
--- a/src/semgrep/lang.ss
+++ b/src/semgrep/lang.ss
@@ -13,7 +13,8 @@
   '("json" "python" "js" "ts"))
 
 (def text-supported-language-ids
-  '("dockerfile" "html" "xml" "yaml" "c" "terraform" "php" "csharp"))
+  '("dockerfile" "html" "xml" "yaml" "c" "terraform" "php" "csharp"
+    "swift"))
 
 (def (string-member? needle xs)
   (and (not (null? xs))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index 6ee423f..3422add 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -809,7 +809,8 @@
         (string=? canonical "c")
         (string=? canonical "terraform")
         (string=? canonical "php")
-        (string=? canonical "csharp"))))
+        (string=? canonical "csharp")
+        (string=? canonical "swift"))))
 
 (def (c-language? language)
   (let ([canonical (or (canonical-language language) language)])
diff --git a/tests/oracle/upstream-sweep.sh b/tests/oracle/upstream-sweep.sh
index f44d392..fa0b464 100755
--- a/tests/oracle/upstream-sweep.sh
+++ b/tests/oracle/upstream-sweep.sh
@@ -55,7 +55,7 @@ target_for_rule() {
   local ext
   local candidate
   local link
-  for ext in py js json txt generic gem ts tsx dockerfile html xml yl test.yaml ejs mustache c tf tpl cs; do
+  for ext in py js json txt generic gem ts tsx dockerfile html xml yl test.yaml ejs mustache c tf tpl cs swift; do
     candidate="$RULE_DIR/$base.$ext"
     if [[ -f "$candidate" ]]; then
       if [[ -L "$candidate" ]]; then
diff --git a/tests/smoke.ss b/tests/smoke.ss
index 9186029..ccdf291 100644
--- a/tests/smoke.ss
+++ b/tests/smoke.ss
@@ -799,6 +799,19 @@
                       (finding-end-offset (car findings)))
            => "response.Write(request.Form[\"input\"])")))
 
+(test-case "scan Swift lambda taint with generic text fallback"
+  (let* ([swift-config
+          "rules:\n  - id: swift-lambda-taint\n    message: Taint should go into the lambda!\n    languages:\n      - swift\n    severity: WARNING\n    mode: taint\n    pattern-sources:\n      - pattern: |\n          source\n    pattern-sinks:\n      - pattern: |\n          sink(...)\n"]
+         [source
+          "       \nlet foo = source \n\nlet bar = qux { x in\n    // ruleid: swift-lambda-taint\n    sink(foo)\n}\n"]
+         [findings
+          (scan-config-string swift-config "swift" "demo.swift" source)])
+    (check (length findings) => 1)
+    (check (substring source
+                      (finding-start-offset (car findings))
+                      (finding-end-offset (car findings)))
+           => "sink(foo)")))
+
 (test-case "scan Terraform coerced scalar assignments"
   (let* ([tf-config
           "rules:\n  - id: terraform-coercions\n    languages: [hcl]\n    message: terraform\n    severity: WARNING\n    pattern-either:\n      - pattern: $ID = 150\n      - pattern: $ID = true\n"]
@@ -2475,6 +2488,7 @@
   (check (guess-language-from-path "demo.tf") => "terraform")
   (check (guess-language-from-path "demo.tpl") => "php")
   (check (guess-language-from-path "demo.cs") => "csharp")
+  (check (guess-language-from-path "demo.swift") => "swift")
   (check (guess-language-from-path "demo.rb") => #f))
 
 (test-case "generated language registry"