Honor JavaScript vardef assignment option
ober
45a5501e705249abb2203b470a4b30f5be4f7e1c
--- a/HANDOFF_OPUS_4_8.md +++ b/HANDOFF_OPUS_4_8.md @@ -1,10 +1,10 @@ # Opus 4.8 Handoff: jerboa-semgrep Semgrep Parity -Date: 2026-05-29 03:15 MDT +Date: 2026-05-29 03:50 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: `113620a Honor taint metavariable unification` +Base HEAD before this checkpoint: `5ddcbd3 Clear decorated TypeScript and Unicode frontiers` 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 @@ -55,7 +55,7 @@ make test Result: ```text -211 tests, 211 passed, 0 failed +212 tests, 212 passed, 0 failed ``` Local oracle: @@ -70,16 +70,16 @@ Result: oracle: 42 passed, 0 failed ``` -Focused decorated/Unicode guardrail: +Focused vardef guardrail: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^(ts_decorated_async_property|ts_decorated_static_property|unicode_string)$' LIST_MISMATCHES=1 MAX_DIFFS=180 tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^vardef_assign_(false|false1|true|true1|true2)$' LIST_MISMATCHES=1 MAX_DIFFS=260 tests/oracle/upstream-sweep.sh ``` Result: ```text -upstream-sweep: 3 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 3 compared +upstream-sweep: 5 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 5 compared ``` Full upstream sweep: @@ -91,7 +91,7 @@ SEMGREP_CURRENT=/Users/user/.local/bin/semgrep LIST_MISMATCHES=1 MAX_DIFFS=0 tes Result: ```text -upstream-sweep: 234 passed, 5 mismatched, 0 jerboa errors, 2 current errors, 241 compared +upstream-sweep: 239 passed, 0 mismatched, 0 jerboa errors, 2 current errors, 241 compared ``` The two current errors are packaged-Semgrep oracle errors, not Jerboa scanner @@ -100,57 +100,55 @@ invokes upstream Semgrep for each sorted case. ## What Changed In This Checkpoint -This checkpoint clears three upstream mismatches: +This checkpoint clears the last five known Jerboa mismatches in the current +sorted upstream sweep: ```text -ts_decorated_async_property -ts_decorated_static_property -unicode_string +vardef_assign_false +vardef_assign_false1 +vardef_assign_true +vardef_assign_true1 +vardef_assign_true2 ``` The full sweep moved from the previous checkpoint: ```text -231 passed, 8 mismatched, 0 jerboa errors, 2 current errors +234 passed, 5 mismatched, 0 jerboa errors, 2 current errors ``` to: ```text -234 passed, 5 mismatched, 0 jerboa errors, 2 current errors +239 passed, 0 mismatched, 0 jerboa errors, 2 current errors ``` Implementation changes in `src/semgrep/scan.ss`: -- Added a TypeScript decorated class-member scanner for patterns of the form: - `@Decorator(...) async name`, `@Decorator(...) async name(...)`, - `@Decorator(...) async name(...){ ... }`, and the matching `static` forms. -- The scanner parses the decorator token, drops pattern decorator call suffixes - such as `@Query(...)`, records the required modifier (`async` or `static`), - target member name, and shape (`property`, `method`, or `method-body`). -- Target matching accepts `static async` for both async and static method - patterns where Semgrep does. For async patterns, `static` is optional before - `async`; for static patterns, `async` is optional after `static`. -- Property-shaped patterns such as `async foo` and `static foo` deliberately do - not match method declarations such as `async foo(){}` or `static foo(){}`. -- Method-shaped patterns end at the parameter close paren. Method-body-shaped - patterns extend through the matching method body close brace. +- Added `rule-option-explicit-false?` and a narrow JavaScript + `vardef_assign` scanner for explicit `options: { vardef_assign: ... }`. +- The scanner parses assignment-like patterns such as `x = 1`, multiline + `...; x = 1;`, and expression ellipsis `<... x = 1 ...>`. +- With `vardef_assign: false`, it suppresses initializer-derived matches from + declarations such as `var x = 1;` while still allowing real assignment + statements such as `x = 1;`. +- With `vardef_assign: true`, it treats `var`/`let`/`const` initializers as + assignment-equivalent matches and returns Semgrep-compatible ranges: + plain `x = 1` and `<... x = 1 ...>` report the `x = 1` slice, while the + multiline statement-sequence shape reports the whole declaration statement. - The scanner is wired into the positive pattern paths used by direct pattern - rules, formula entries, and binding-aware positive entries. -- `offset->line-col` now counts UTF-8 byte width for columns, matching Semgrep - JSON positions for non-ASCII source. Source offsets already used Semgrep byte - offsets for tree-sitter-backed findings; the mismatch was column width. + rules, formula entries, and binding-aware positive entries so it can preempt + the general structural matcher where the explicit option matters. New smoke coverage in `tests/smoke.ss`: ```text -scan Python unicode string byte columns -scan TypeScript decorated async methods -scan TypeScript decorated static methods +scan JavaScript vardef_assign option ``` -The Unicode smoke uses ASCII Scheme `\x1f385;` escapes so the test file itself -does not need literal non-ASCII characters. +The previous checkpoint also added `scan Python unicode string byte columns`, +`scan TypeScript decorated async methods`, and `scan TypeScript decorated +static methods`. ## Recently Cleared Cases @@ -168,6 +166,11 @@ taint_unify_mvars ts_decorated_async_property ts_decorated_static_property unicode_string +vardef_assign_false +vardef_assign_false1 +vardef_assign_true +vardef_assign_true1 +vardef_assign_true2 ``` Useful upstream files for the latest cleared cases: @@ -179,6 +182,16 @@ Useful upstream files for the latest cleared cases: /Users/user/mine/semgrep/tests/rules/ts_decorated_static_property.ts /Users/user/mine/semgrep/tests/rules/unicode_string.yaml /Users/user/mine/semgrep/tests/rules/unicode_string.py +/Users/user/mine/semgrep/tests/rules/vardef_assign_false.yaml +/Users/user/mine/semgrep/tests/rules/vardef_assign_false.js +/Users/user/mine/semgrep/tests/rules/vardef_assign_false1.yaml +/Users/user/mine/semgrep/tests/rules/vardef_assign_false1.js +/Users/user/mine/semgrep/tests/rules/vardef_assign_true.yaml +/Users/user/mine/semgrep/tests/rules/vardef_assign_true.js +/Users/user/mine/semgrep/tests/rules/vardef_assign_true1.yaml +/Users/user/mine/semgrep/tests/rules/vardef_assign_true1.js +/Users/user/mine/semgrep/tests/rules/vardef_assign_true2.yaml +/Users/user/mine/semgrep/tests/rules/vardef_assign_true2.js ``` Important observations: @@ -193,18 +206,19 @@ Important observations: `start offset 69`, `end offset 75`. Only the end column differed: Semgrep reported `7`; Jerboa reported `4` because it counted the emoji as one character instead of four UTF-8 bytes. +- The `vardef_assign_*` cases all scan `var x = 1;`. Explicit + `vardef_assign: false` should not treat that initializer as an assignment. + Explicit `vardef_assign: true` should, with range selection depending on + whether the pattern is a plain assignment, statement sequence, or expression + ellipsis. ## Current Full-Sweep Frontier The current full sorted upstream sweep compared 241 rule/target pairs and has -these 5 mismatches: +no known Jerboa mismatches: ```text -vardef_assign_false -vardef_assign_false1 -vardef_assign_true -vardef_assign_true1 -vardef_assign_true2 +upstream-sweep: 239 passed, 0 mismatched, 0 jerboa errors, 2 current errors, 241 compared ``` Refresh the frontier with: @@ -213,66 +227,23 @@ Refresh the frontier with: SEMGREP_CURRENT=/Users/user/.local/bin/semgrep LIST_MISMATCHES=1 MAX_DIFFS=0 tests/oracle/upstream-sweep.sh ``` -## Recommended Next Target: vardef_assign_false - -The next sorted frontier is `vardef_assign_false`. Start with the whole family -because these cases almost certainly share one vardef/assignment semantic gap: - -```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^vardef_assign_(false|false1|true|true1|true2)$' LIST_MISMATCHES=1 MAX_DIFFS=260 tests/oracle/upstream-sweep.sh -``` - -Current focused result: - -```text -MISMATCH vardef_assign_false -@@ -0,0 +1 @@ -+(finding "test" ".../vardef_assign_false.js" 2 1 10 2 10 19 "WARNING" "Match Found!" "") -MISMATCH vardef_assign_false1 -@@ -0,0 +1 @@ -+(finding "test" ".../vardef_assign_false1.js" 1 1 0 3 1 21 "WARNING" "Match Found!" "") -MISMATCH vardef_assign_true -@@ -1 +1 @@ --(finding "test" ".../vardef_assign_true.js" 2 5 18 2 10 23 "WARNING" "Match Found!" "") -+(finding "test" ".../vardef_assign_true.js" 2 1 14 2 10 23 "WARNING" "Match Found!" "") -MISMATCH vardef_assign_true1 -@@ -1 +1 @@ --(finding "test" ".../vardef_assign_true1.js" 2 1 14 2 10 23 "WARNING" "Match Found!" "") -+(finding "test" ".../vardef_assign_true1.js" 1 1 0 3 1 25 "WARNING" "Match Found!" "") -MISMATCH vardef_assign_true2 -@@ -1 +0,0 @@ --(finding "test" ".../vardef_assign_true2.js" 2 5 18 2 10 23 "WARNING" "Match Found!" "") -upstream-sweep: 0 passed, 5 mismatched, 0 jerboa errors, 0 current errors, 5 compared -``` +## Recommended Next Target -Then inspect the upstream fixtures: +There is no known Jerboa mismatch in the current 241-case sorted upstream +sweep. The next useful work is to identify the two packaged-Semgrep +`current_error` cases and decide whether they should be excluded from this +oracle, pinned to a different Semgrep binary, or used to expand the Jerboa +compatibility corpus once upstream current can produce JSON for them. -```sh -sed -n '1,160p' /Users/user/mine/semgrep/tests/rules/vardef_assign_false.yaml -sed -n '1,160p' /Users/user/mine/semgrep/tests/rules/vardef_assign_false1.yaml -sed -n '1,160p' /Users/user/mine/semgrep/tests/rules/vardef_assign_true.yaml -sed -n '1,160p' /Users/user/mine/semgrep/tests/rules/vardef_assign_true1.yaml -sed -n '1,160p' /Users/user/mine/semgrep/tests/rules/vardef_assign_true2.yaml -``` +If continuing parity beyond this sweep, broaden the oracle rather than chasing +this now-empty frontier. Candidate next expansions: -Likely fix area: - -- The rules all scan `var x = 1;` with `pattern: x = 1`, a statement-sequence - pattern ending in `x = 1;`, or expression-ellipsis pattern - `<... x = 1 ...>`. -- `options: { vardef_assign: false }` should suppress matches where `x = 1` - comes only from the initializer in `var x = 1;`. -- `options: { vardef_assign: true }` should allow the initializer to match, - but the expected range differs by pattern shape: plain `x = 1` and - `<... x = 1 ...>` report the `x = 1` slice starting at column 5, while the - multiline `...; x = 1;` pattern reports the whole `var x = 1;` statement. -- `rule-option-enabled?` and `rule-options` already exist in - `src/semgrep/scan.ss`; the missing piece is likely matcher behavior around - JavaScript `variable_declaration`/`variable_declarator` nodes, range - selection, and expression-ellipsis support for assignment-equivalent - vardefs. -- Add a focused smoke test that covers the smallest upstream shape before - changing shared structural matching. +- Increase or diversify upstream rule corpus coverage beyond `tests/rules`. +- Add parser-specific cases for TypeScript/TSX beyond the current JavaScript + parser fallback. +- Add CLI/target-selection snapshot parity from the Semgrep checkout. +- Identify the two `current_error` cases by temporarily instrumenting + `tests/oracle/upstream-sweep.sh` to print current Semgrep failures. ## Useful Commands --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -60,6 +60,9 @@ (def (rule-option-enabled? rule key) (let ([found (rule-option-entry rule key)]) (and found (cdr found)))) + (def (rule-option-explicit-false? rule key) + (let ([found (rule-option-entry rule key)]) + (and found (not (cdr found))))) (def (sg-filter pred xs) (let loop ([remaining xs] [acc '()]) (cond @@ -1278,6 +1281,172 @@ (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]+)") @@ -4883,6 +5052,12 @@ path source (cdr entry))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (cdr entry))) (scan-string-key-object-pattern rule path @@ -5129,6 +5304,12 @@ path source (cdr entry))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (cdr entry))) (and (string=? language "json") (scan-json-key-value-pattern rule @@ -11698,6 +11879,12 @@ path source (rule-pattern rule))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (rule-pattern rule))) (and (string=? language "json") (scan-json-key-value-pattern rule --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -3,7 +3,7 @@ ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC") ("src/semgrep/lang.ss" . "7E5441BD00A7F1D4") ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA") - ("src/semgrep/scan.ss" . "E3D9FE6B8D7E1CE8") + ("src/semgrep/scan.ss" . "6F89C2FDF56F5DE7") ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -65,6 +65,10 @@ (let ([found (rule-option-entry rule key)]) (and found (cdr found)))) +(def (rule-option-explicit-false? rule key) + (let ([found (rule-option-entry rule key)]) + (and found (not (cdr found))))) + (def (sg-filter pred xs) (let loop ([remaining xs] [acc '()]) (cond @@ -1346,6 +1350,164 @@ (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]+)") @@ -5060,6 +5222,12 @@ path source (cdr entry))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (cdr entry))) (scan-string-key-object-pattern rule path @@ -5357,6 +5525,12 @@ path source (cdr entry))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (cdr entry))) (and (string=? language "json") (scan-json-key-value-pattern rule @@ -12346,6 +12520,12 @@ path source (rule-pattern rule))) + (and (javascript-like-language? language) + (scan-javascript-vardef-assign-pattern + rule + path + source + (rule-pattern rule))) (and (string=? language "json") (scan-json-key-value-pattern rule --- a/tests/smoke.ss +++ b/tests/smoke.ss @@ -409,6 +409,35 @@ "function f(x) { console.log(x); console.log(y); }\n")]) (check (length findings) => 2))) +(test-case "scan JavaScript vardef_assign option" + (let* ([false-config + "rules:\n - id: demo.vardef.false\n languages: [javascript]\n message: vardef false\n severity: WARNING\n options:\n vardef_assign: false\n pattern: x = 1\n"] + [true-config + "rules:\n - id: demo.vardef.true\n languages: [javascript]\n message: vardef true\n severity: WARNING\n options:\n vardef_assign: true\n pattern: x = 1\n"] + [sequence-config + "rules:\n - id: demo.vardef.sequence\n languages: [javascript]\n message: vardef sequence\n severity: WARNING\n options:\n vardef_assign: true\n pattern: |\n ...\n x = 1;\n"] + [ellipsis-config + "rules:\n - id: demo.vardef.ellipsis\n languages: [javascript]\n message: vardef ellipsis\n severity: WARNING\n options:\n vardef_assign: true\n pattern: |\n <... x = 1 ...>\n"] + [false-findings + (scan-config-string false-config "javascript" "demo.js" "var x = 1;\n")] + [actual-findings + (scan-config-string false-config "javascript" "demo.js" "x = 1;\n")] + [true-findings + (scan-config-string true-config "javascript" "demo.js" "var x = 1;\n")] + [sequence-findings + (scan-config-string sequence-config "javascript" "demo.js" "var x = 1;\n")] + [ellipsis-findings + (scan-config-string ellipsis-config "javascript" "demo.js" "var x = 1;\n")]) + (check (length false-findings) => 0) + (check (length actual-findings) => 1) + (check (finding-start-col (car actual-findings)) => 1) + (check (length true-findings) => 1) + (check (finding-start-col (car true-findings)) => 5) + (check (length sequence-findings) => 1) + (check (finding-start-col (car sequence-findings)) => 1) + (check (length ellipsis-findings) => 1) + (check (finding-start-col (car ellipsis-findings)) => 5))) + (test-case "scan JavaScript symbolic reassignment chain" (let* ([js-config "rules:\n - id: demo.js.symbolic.reassign\n languages: [javascript]\n message: reassigned chain\n severity: WARNING\n options:\n symbolic_propagation: true\n pattern: ff(ff(..., -1), ..., -2)\n"]