Skip to content

Add NestedWith check - #1306

Open
easink wants to merge 1 commit into
rrrene:masterfrom
easink:nested_with
Open

Add NestedWith check#1306
easink wants to merge 1 commit into
rrrene:masterfrom
easink:nested_with

Conversation

@easink

@easink easink commented Aug 10, 2026

Copy link
Copy Markdown

Reports a with that does not start at the top of a function body: one buried inside an if, unless, case, cond, for, receive, or another with. Such a with splits a chain of pattern matches across two levels of control flow, which hides its failure paths and makes the else of either block ambiguous to a reader.

def, defp, defmacro, defmacrop, fn and defmodule open a scope, so a with at the top of one of their bodies is never reported, however deeply the scope itself is nested. defmodule counts because a module is routinely wrapped in a compile-time if Code.ensure_loaded?(Dep), which is conditional compilation rather than control flow around the with. try is neither scope nor block and is passed through: wrapping a chain in try/rescue does not branch it, and a try has no else to confuse with the one belonging to the with.

The traversal is an explicit recursion rather than Credo.Code.prewalk/3 because the flag it carries has to be reset per branch, and reset entirely on the way into a scope.

Opt-in for now, so it lands in :disabled.

Reports a `with` that does not start at the top of a function body: one
buried inside an `if`, `unless`, `case`, `cond`, `for`, `receive`, or
another `with`. Such a `with` splits a chain of pattern matches across
two levels of control flow, which hides its failure paths and makes the
`else` of either block ambiguous to a reader.

`def`, `defp`, `defmacro`, `defmacrop`, `fn` and `defmodule` open a
scope, so a `with` at the top of one of their bodies is never reported,
however deeply the scope itself is nested. `defmodule` counts because a
module is routinely wrapped in a compile-time `if
Code.ensure_loaded?(Dep)`, which is conditional compilation rather than
control flow around the `with`. `try` is neither scope nor block and is
passed through: wrapping a chain in `try/rescue` does not branch it, and
a `try` has no `else` to confuse with the one belonging to the `with`.

The traversal is an explicit recursion rather than `Credo.Code.prewalk/3`
because the flag it carries has to be reset per branch, and reset
entirely on the way into a scope.

Opt-in for now, so it lands in `:disabled`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant