feat(compiler): SYN007 — warn on fetch() calls that bypass the net capability model (?bs 0.7+)#148
Open
marcelofarias wants to merge 11 commits into
Open
feat(compiler): SYN007 — warn on fetch() calls that bypass the net capability model (?bs 0.7+)#148marcelofarias wants to merge 11 commits into
marcelofarias wants to merge 11 commits into
Conversation
…pability model (?bs 0.7+)
fetch() makes real HTTP requests but is invisible to CAP001, which only
checks http.* member calls. A fn that calls fetch() has an undeclared net
dependency — uses { net } is absent from its header and callers cannot see
the network access. This is the same bypass class as console.* (SYN003).
Detects: fetch(...), fetch?.(...), and TypeScript instantiation forms
fetch<T>(...). Suppressed inside unsafe { } blocks and unsafe fn bodies.
Excludes obj.fetch(...) method calls. Fires at ?bs 0.7+.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new compiler warning (SYN007) to detect fetch(...) calls inside function bodies (from ?bs 0.7+) because fetch performs real network I/O while bypassing the http.*-based net capability surface enforced by CAP001, and wires the new code through the compiler’s registries, MCP explanations, and docs/tests.
Changes:
- Register SYN007 (rule/idiom/rewrite/example) and surface it through MCP
explainand diagnostic docs. - Implement token-based detection of
fetch(...),fetch?.(...), andfetch<T>(...)with suppression insideunsafe {}/unsafe fnbodies. - Add compiler + MCP tests to keep the error-code/explanation registries exhaustive and validate SYN007 behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/compiler/src/passes/syn-check.ts | Adds SYN007 token-scan to warn on fetch calls, with unsafe suppression. |
| packages/compiler/src/error-codes.ts | Registers SYN007 metadata (rule/idiom/rewrite/example). |
| packages/compiler/tests/syn007-check.test.ts | Adds unit tests covering SYN007 firing/non-firing cases. |
| packages/compiler/tests/error-codes.test.ts | Extends exhaustive compiler diagnostic allowlist to include SYN007. |
| packages/mcp/src/explanations.ts | Adds long-form MCP explain content for SYN007. |
| packages/mcp/tests/server.test.ts | Adds SYN007 to the expected known-code list used by MCP tests. |
| AGENTS.md | Documents SYN007 in the diagnostic code table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…and Promise return type
- `anglDepth -= at.text.length - 1` was off by one for `>>` and `>>>`
tokens; `fetch<Promise<Response>>(url)` was never detected. Aligns with
the SYN002 pattern that uses `depth -= t.text.length`.
- Add nested-generic test to prevent regressions.
- `http.post` takes an optional `RequestInit` bag, not a raw body string;
fix idiom in error-codes.ts and MCP explanation to use `{ body }`.
- MCP explanation stated `Result<Response, Error>` as the return type;
the actual return is `Promise<Result<Response, Error>>` (runtime/effects.ts).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 tasks
…gnature, >> consistency
- Add SYN006 to syn-check.ts header comment (doc was listing SYN002/SYN003/SYN005/SYN007, skipping SYN006)
- error-codes.ts SYN007 rewrite/example: fn signatures using await must be async fn with Promise<> return
- explanations.ts SYN007 fails/passes examples: same async fn + Promise<> fix
- AGENTS.md SYN007 row: http.post(url, body) → http.post(url, { body }) to match RequestInit signature
- syn-check.ts generic depth scan: unify >> / >>> handling to match SYN002 pattern (single clause, depth -= t.text.length)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l signatures from SYN007
Add the same guard SYN002 uses: if fetch(...) is followed by { / => / : after the closing
paren it is a method definition, not a runtime call — skip to avoid false positives.
Add two regression tests covering the object-method-shorthand and type-literal cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ll forms
Previously, the object/type-literal method exclusion only ran when the token
immediately after `fetch` was `(` (direct call). Generic (`fetch<T>(...)`) and
optional (`fetch?.(...)`) forms bypassed the exclusion, causing false positives
on method signatures like `{ fetch<T>(url): T }` inside fn bodies.
Refactor to normalize `parenIdx7` to the actual `(` token index for all three
call forms before running the exclusion check. Add two regression tests covering
the generic method-signature and return-type-annotated method-shorthand cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-declared (?bs 0.9+)
* feat(compiler): DEP003/DEP004 — warn when reads/writes labels are over-declared (?bs 0.9+)
Squash-rebased onto current main. DEP003 fires when a fn declares
reads { x } but no same-file callee (transitively) reads { x } —
the annotation likely became stale after a refactor. DEP004 is the
symmetric check for writes { x }.
Both are warning-level (non-blocking). Leaf fns are excluded.
Adds DEP003/DEP004 to compiler registry, MCP explain, and tests.
Supersedes PR #100 (same content, rebased). All 996 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): hasOpaqueCall now follows full member chain — detects obj.a.b() and obj.a?.b()
Previous logic only checked one segment deep: obj.method() was detected but
obj.a.b() was not, because afterMethod was `.` rather than `(`. Walk the full
chain until a call or non-chain token is found.
Adds two regression tests for the multi-segment cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): address Copilot review — arrow parens and conditional wording
- Add parens around single-param arrow functions in dep-check.ts (style consistency)
- Clarify DEP001/DEP002 cross-refs: "may be warned about" with scope conditions, not unconditional
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): add DEP003/DEP004 to exhaustive error-codes test allowlist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): derive STDLIB_NAMESPACES from STDLIB_TO_CAP to eliminate drift risk
STDLIB_NAMESPACES was a hardcoded duplicate of the key set of STDLIB_TO_CAP
(_stdlib.ts). Adding or removing a stdlib namespace required updating two
places and hasOpaqueCall behavior could silently diverge. Now derived from
the single source of truth.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): collectTopLevelParamNames handles untyped/default params, collectFnBodyLocalNames handles var
collectTopLevelParamNames:
- Adds skipUntilNextParam flag so type annotations and default values
after the param name are not re-scanned as candidate param names.
Fixes false captures like treating `string` in `(name: string)` as a
param name when a untyped-param lookahead would have matched it.
- Adds fallback for completely untyped and default params: bare identifier
immediately before `,`, `)`, or `=` is collected as a param name.
Fixes the original Copilot gap: `fn f(x) -> void` left `x` unknown,
causing `x.trim()` to be treated as an opaque external call.
- Closing `}` at depth 1 sets skipUntilNextParam so the type annotation
after a destructured binding (`{a, b}: Context`) is not captured.
collectFnBodyLocalNames:
- Adds `var` alongside `const`/`let` so that `var x = ...` bindings are
recognised as local names and `x.method()` is not mistaken for an
opaque namespace call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(dep-check): add coverage for untyped param and var local opaque suppression
Two regression tests to cover the fixes in collectTopLevelParamNames and
collectFnBodyLocalNames:
- Untyped param `fn f(name)`: name.trim() must not suppress DEP003
- Var binding `var x = ...`: x.method() must not suppress DEP003
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): DEP003/DEP004 rule/idiom text and docs reflect moduleEffects and opaque-call suppression
Update DEP003/DEP004 entries in error-codes.ts (rule, idiom), MCP
explanations, and AGENTS.md to accurately describe the two conditions
under which the warning is suppressed: fns with opaque/untracked external
calls, and tracked callees that include moduleEffects entries (not just
same-file fns).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): correct doc comment — collectFnBodyLocalNames also collects var
The implementation collects `const`, `let`, and `var` bindings, but the
doc comment only mentioned `const`/`let`. Updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(callgraph): collect destructured binding names in params and local vars
collectTopLevelParamNames: when a parameter is a destructuring pattern
({ a, b: c } or [x]), extract the binding names (those NOT followed by
`:`) so method calls on destructured locals (e.g. name.trim()) are not
mistaken for opaque external calls that suppress DEP003/DEP004/THR004.
collectFnBodyLocalNames: add collectDestructuredTokenBindings helper that
uses the token matchedAt index to scan { } and [ ] destructuring patterns
recursively, collecting binding names by the same rule (ident not followed
by `:` is a binding, not a property key).
Adds two regression tests: one for destructured params and one for
destructured const locals — both confirm DEP003 still fires when the
only member calls in the body are on locally-scoped destructured names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(callgraph): skip bracket groups in type annotations; fix destructuring depth
collectTopLevelParamNames: when skipping past a param's type annotation
(skipUntilNextParam=true), skip entire {}/[] groups rather than char-by-char
so commas inside `x: { a: string, b: number }` are not mistaken for
param separators.
collectDestructuringStringBindings: change depth guard from `depth !== 1`
to `depth < 1` so nested destructuring patterns like `{ a: { b } }` are
handled — binding names at depth > 1 are now correctly collected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): skip default-value expressions in destructuring binding collectors
Identifiers in `= <expr>` default values (e.g. `{ a = externalDb }`) were
incorrectly collected as binding names, causing hasOpaqueCall() to treat
member calls on those names as local and suppress DEP003/DEP004/THR004
when it shouldn't.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): clarify DEP003/DEP004 titles — tracked callee, not same-file only
Copilot pointed out the titles said "in the same file" but the
implementation also counts moduleEffects entries as tracked callees.
Updated titles in error-codes.ts and explanations.ts to say
"any tracked callee" instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003): exclude module-level stdlib aliases from opaque-call detection
hasOpaqueCall suppresses DEP003/DEP004 when a fn contains a member call whose
receiver is not in STDLIB_NAMESPACES or effectiveLocalNames. Module-level stdlib
aliases like `const t = time` were not included in either set, causing `t.now()`
to be treated as an opaque external call and incorrectly suppressing the warning.
Fix: collect module-level stdlib alias names via collectStdlibAliases() once per
pass invocation and add them to localNames at each hasOpaqueCall call site.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(callgraph): move orphaned JSDoc to collectTopLevelParamNames
The JSDoc block for collectTopLevelParamNames was placed before
collectDestructuringStringBindings, leaving both functions without
proper documentation. Moved the doc block to attach to the correct symbol.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(thr-check): pass stdlib aliases to hasOpaqueCall for THR004 suppression
hasOpaqueCall in passThrCheck was called without localNames, so module-level
stdlib aliases (e.g. `const t = time`) were treated as opaque external
member calls and incorrectly suppressed THR004 warnings. passDepCheck already
passed stdlibAliasNames correctly; align passThrCheck to match.
- Import collectStdlibAliases from _alias.js and collectFnBodyLocalNames
from _callgraph.js in thr-check.ts
- Compute stdlibAliasNames once at passThrCheck entry
- Build localNames = paramNames + bodyLocals + stdlibAliasNames for
the hasOpaqueCall call (mirrors the pattern in passDepCheck)
- Add regression test: fn with t.now() (stdlib alias) still fires THR004
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(callgraph): track angle-bracket depth in collectTopLevelParamNames
Commas inside generic type argument lists like `Map<T, { a }>` were
incorrectly resetting skipUntilNextParam, causing the `{ a }` fragment to
be parsed as a destructuring parameter and adding `a` to the param-names
set. This made `a.method()` in the body appear local rather than opaque,
falsely suppressing DEP003/DEP004 when they should fire (and vice versa
— allowing opaque-call suppression to be defeated).
Fix: track `<`/`>` angle depth in skipUntilNextParam mode. Commas are
only treated as top-level param separators when angleDepth === 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(callgraph): remove unused chainWalk: label in hasOpaqueCall
The label had no corresponding `break chainWalk` or `continue chainWalk`
usage; all break/continue statements in the loop are bare. Removing it
eliminates the noise and avoids potential linter warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(thr-check): filter import aliases to tracked moduleEffects targets for opaque-call check
THR004 opaque-call suppression used allCalleeNames, which includes ALL import
aliases. An alias to an untracked external (not in moduleEffects) is itself an
opaque call — including it in knownForOpaque caused THR004 to fire for fns with
genuinely unknown external dependencies (false warnings).
Fix: derive opaqueKnownBase from fnNames + knownExternalNames + only the
aliases whose resolved target exists in moduleEffects. allCalleeNames is
preserved as-is for collectCallees (call graph building).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(dep003/dep004): correct rule/idiom text — call-graph heuristic, not body scanner
The previous wording implied the compiler scans fn bodies for direct resource
access, which it does not. Reword rule and idiom to accurately describe the
implementation: a call-graph justification check that fires only when all
same-file callees are resolvable and none propagates the declared label.
---------
Co-authored-by: Marcelo Farias <mfarias@Marcelos-MacBook-Pro-4.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…n bodies (?bs 0.7+)
* feat(compiler): SYN004 — warn on eval() and new Function() calls in fn bodies (?bs 0.7+)
eval() and new Function() execute strings as code at runtime, bypassing
every static check botscript provides: CAP001/CAP002 cannot see capability
calls hidden in the string, reads/writes labels are unenforceable, and
SYN002/SYN003 checks can be routed around entirely. This is the universal
bypass — a fn could use eval('process.env.KEY') with no SYN005, or
eval('http.get(...)') with no CAP001.
Detection (token-based, same suppression model as SYN002/SYN003):
- eval(...) — 'eval' ident not preceded by ./ ?., followed by (
- new Function(...) — 'new' followed by 'Function' followed by (
- .eval() (method call on a local) and Function.* member accesses: excluded
- Suppressed inside unsafe {} blocks and unsafe fn bodies
Closes #144 (filed as SYN006; implemented as SYN004 — next available code
on main, SYN004/SYN005 are still in open PRs).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): detect bare Function() calls without new; fix SYN005 xref
`Function(body)` without `new` is equivalent to `new Function(body)` at
runtime and was an easy bypass of SYN004's intent. Now both forms fire.
Also fixes Copilot's comment about the explanations.ts cross-reference to
SYN005 — reworded to not depend on SYN005 existing on main yet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): detect Function?.() optional call; align docs/tests with bare Function() detection
- Add `Function?.(` optional call detection in syn-check.ts (mirrors eval?.( handling)
- Update error-codes.ts, MCP explanations, AGENTS.md, and test header/describe label to
mention bare `Function(...)` / `Function?.(...)` alongside `new Function(...)` — the
previous commit added bare Function() detection but left docs still saying only new Function()
- Fix "behaviour" → "behavior" in MCP explanation (American English consistency)
- Add test for Function?.() optional call form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): exclude declaration contexts from eval/Function detection; fix message text
- eval and Function detection now skip when the paren group is followed by
`{`, `:`, or `=>` — catches `function eval(x) {}` and method shorthands
that would otherwise false-positive
- Warning messages use concrete unsafe block examples (`{ eval(src) }`,
`{ new Function(body) }`) instead of the confusing empty `{ }`
- error-codes.ts title: "call bypasses" → "calls bypass" (plural)
- explanations.ts: clarify Function constructor bullet — not SYN003/Result
contract specifically, but all static checks
- Two new not-fires tests covering the declaration exclusion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): SYN004 Function warning suggests correct unsafe-block syntax
When the flagged call is bare Function(...) (no `new`), the suppression
example in the warning message now matches the detected form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): detect TypeScript instantiation forms eval<T>() and Function<T>()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): remove colon from declaration-exclusion check; add ternary regression tests
The ): exclusion was causing false negatives: cond ? eval(x) : y was
not firing SYN004 because the ternary colon after eval(x) matched the
`:` (return-type annotation) exclusion. The `{` and `=>` exclusions
correctly handle all real declaration contexts (method shorthands and
function declarations). Removes the `:` case from both eval and Function
detection and adds two regression tests for ternary branches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): handle return-type-annotated declarations; guard Function ternary then-branch
- Add `:` to declaration-exclusion check in both eval and Function blocks,
so `function eval(x): T {}` and `{ eval(x): T {} }` are not flagged.
- Guard the `:` check with `isTernaryConsequent` in both blocks to avoid
suppressing `? eval(x) : y` and `? Function(x) : y` (both are real calls).
- Fix grammar in explanations.ts: "bypasses" → "bypass" (plural subject).
- Add 4 regression tests: return-type-annotated eval/Function declarations
must not fire; Function() in ternary then-branch must fire.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): add eval?.() test; clarify SYN005 AGENTS.md description
- Add missing eval?.(…) optional-call test case (SYN004 fires on this
form but it was untested — Function?.() had a test, eval?.() did not)
- Clarify SYN005 AGENTS.md row: rephrase from the ambiguous "no
declaration depends on deployment config" to the accurate "no
capability covers it; fn has an undeclared dependency on deployment
configuration"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): idiom shows content in unsafe block; MCP explanation covers instantiation forms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn004): fire on new Function() in ternary then-branch — check token before new
When `? new Function(body) : other`, prev4 is `new` (not `?`), so the old
isTernaryConsequent4 check evaluated to false and the `:` guard skipped the
warning as if it were a return-type annotation. Now also check the token before
`new` for `?`. Add regression test.
---------
Co-authored-by: Marcelo Farias <mfarias@Marcelos-MacBook-Pro-4.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…k calls in fn bodies (?bs 0.7+)
* feat(compiler): SYN010 — warn on setTimeout/setInterval/queueMicrotask calls in fn bodies (?bs 0.7+)
- Adds **SYN010**: a new warning that fires when a fn body calls `setTimeout(...)`,
`setInterval(...)`, or `queueMicrotask(...)` at `?bs 0.7+`
- These globals schedule callbacks that run after the fn returns — any effects inside
those callbacks are invisible to callers: no capability declaration, no `writes {}`
label, and no `throws {}` entry can cover them
- Same bypass class as `fetch` (SYN007) and `WebSocket` (SYN008): real side effects
that sidestep the declared capability surface, but deferred rather than immediate
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- Member calls (`obj.setTimeout(...)`) and bare references (without `(`) are excluded
- Note: should be merged after PR #150 (SYN009) lands; SYN010 is the next available code
Changes:
- error-codes.ts: add SYN010 entry with rule/idiom/rewrite/example
- syn-check.ts: add SYN010 detection; add SYN006 + SYN010 to module header comment;
fix outdated unsafe-fn skip comment
- tests/syn010-check.test.ts: 12 tests covering all three globals, optional-call form,
severity, version gating, unsafe suppression, member-call exclusion, bare reference
- tests/error-codes.test.ts: add SYN010 to exhaustive allowlist
- mcp/src/explanations.ts: add SYN010 long-form explanation with fails/passes examples
- mcp/tests/server.test.ts: add SYN010 to KNOWN_CODES list
- AGENTS.md: add SYN010 row to diagnostic table
- README.md: add SYN010 to explain tool code list
* fix(syn010): address Copilot review — hoist TIMER_GLOBALS, fix param name and reason string
- Hoist TIMER_GLOBALS to module scope (was re-created per fn, wasteful in hot pass)
- Rename `fn` parameter to `callback` in unsafe-fn test (fn is a reserved keyword)
- Fix unsafe reason string: "suspends execution" → "schedules deferred effect" (consistent with idiom)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(syn010): address remaining Copilot feedback — false positive on declarations and explanation codes
- syn-check: exclude function declarations named setTimeout/setInterval/queueMicrotask
(prev significant token is `function`) — these are definitions, not calls
- syn-check: exclude object/class method shorthands where after closing `)` is `{` or `:`
- tests: add two tests covering function-declaration and object-method-shorthand exclusions
- explanations: remove SYN007/SYN008 code references in SYN010 body; reference by name instead
to avoid dangling code cross-references until those PRs land
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(compiler): address SYN010 Copilot review feedback — exclude fn keyword declarations
- syn-check.ts: also skip `fn setTimeout(...) -> void {}` botscript-style nested
function declarations (prev token is `keyword`/`fn`), not just JS `function`
declarations — prevents false positives on nested fn declarations inside fn bodies
- syn010-check.test.ts: add test asserting SYN010 does NOT fire on
`fn setTimeout(delay: number) -> void { }` inside a fn body
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Marcelo Farias <mfarias@Marcelos-MacBook-Pro-4.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…pability model (?bs 0.7+) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| | `primer` | (no args) | The canonical language primer (same text the `?primer` directive emits). | | ||
| | `transform` | `{ source: string, filename?: string }` | `{ ok: true, code, forms, version, warnings: [...] }` on success, or `{ ok: false, diagnostics: [...] }` on failure. `warnings` is an array of non-blocking diagnostics (e.g. CAP003). | | ||
| | `explain` | `{ code: string }` | Long-form explanation for any stable diagnostic code (`ALI001`, `ALI002`, `ALI003`, `BS001`, `BS002`, `CAP001`–`CAP003`, `DEP001`, `DEP002`, `EFF002`–`EFF004`, `FMT001`, `INT001`–`INT005`, `MAT001`–`MAT004`, `RES001`, `RES002`, `SYN001`, `SYN002`, `SYN003`, `SYN005`, `SYN006`, `THR001`–`THR004`, `UNS001`–`UNS005`, `VER001`–`VER003`) plus a fails/passes example pair. | | ||
| | `explain` | `{ code: string }` | Long-form explanation for any stable diagnostic code (`ALI001`, `ALI002`, `ALI003`, `BS001`, `BS002`, `CAP001`–`CAP003`, `DEP001`–`DEP004`, `EFF002`–`EFF004`, `FMT001`, `INT001`–`INT005`, `MAT001`–`MAT004`, `RES001`, `RES002`, `SYN001`, `SYN002`, `SYN003`, `SYN004`, `SYN005`, `SYN006`, `SYN010`, `THR001`–`THR004`, `UNS001`–`UNS005`, `VER001`–`VER003`) plus a fails/passes example pair. | |
Comment on lines
+296
to
+300
| // SYN004: eval() and Function() / new Function() call detection. | ||
| // Fires on: | ||
| // eval(...) — global eval not preceded by `.`/`?.`, followed by `(` | ||
| // Function(…) — bare Function call not preceded by `.`/`?.`, followed by `(` | ||
| // new Function(…) — same as bare Function call, `new` prefix only affects message |
Comment on lines
+296
to
+302
| // SYN004: eval() and Function() / new Function() call detection. | ||
| // Fires on: | ||
| // eval(...) — global eval not preceded by `.`/`?.`, followed by `(` | ||
| // Function(…) — bare Function call not preceded by `.`/`?.`, followed by `(` | ||
| // new Function(…) — same as bare Function call, `new` prefix only affects message | ||
| // Suppressed inside `unsafe { }` blocks and `unsafe fn` bodies. | ||
| // `.eval(...)` (method call on a local) and `Function.*` member accesses are NOT flagged. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
fetch(...),fetch?.(...), or TypeScript instantiation formfetch<T>(...)at?bs 0.7+fetch()is a real network call that CAP001 cannot see — it only checkshttp.*member calls. A fn that usesfetchhas an undeclarednetdependency: nouses { net }in the header, no signal to callers, no capability manifest entryconsole.*(SYN003): real effects, invisible to the declared capability surfaceunsafe "reason" { }blocks andunsafe "reason" fnbodieshttp.get()/http.post()and declareuses { net }Changes
packages/compiler/src/error-codes.tspackages/compiler/src/passes/syn-check.tsfetchnot preceded by./?., followed by(,?.(, or<T>(; unsafe suppressionpackages/compiler/tests/syn007-check.test.tspackages/compiler/tests/error-codes.test.tspackages/mcp/src/explanations.tspackages/mcp/tests/server.test.tsAGENTS.mdTest plan
pnpm -r buildpassesnpx vitest run— 10/10 SYN007 tests pass, 1159 total tests passfetch(url),fetch(url, init),fetch?.(url)fetch<Response>(url)?bs 0.7unsafe {}orunsafe fnobj.fetch(...)(member call on a local)fetchreference (not called)explaintool returns long-form for SYN007🤖 Generated with Claude Code