Format Ruff-backed cells as function bodies#9910
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
ba5aed6 to
614c2e1
Compare
|
Rebased this on current Local checks: uv run --group test pytest tests/_utils/test_formatter.py -q
uv run --group dev ruff check marimo/_utils/formatter.py tests/_utils/test_formatter.pyThe PR diff is still limited to the formatter change and its tests. |
for more information, see https://pre-commit.ci
|
@kirangadhave I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
Pull request overview
This PR aims to make Ruff formatting of marimo cell code match the cell’s eventual execution scope (a function body), preventing Ruff from applying module-level blank-line rules that introduce extra spacing (e.g., around nested defs).
Changes:
- Add an optional
format_as_cellmode to the low-levelruff()helper that wraps code in a temporary async function and unwraps the formatted body. - Update
RuffFormatterto callruff(..., format_as_cell=True). - Extend formatter tests, including a new regression test for blank-line behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
marimo/_utils/formatter.py |
Adds wrap/unwrap helpers and a format_as_cell option to ruff(), and enables it in RuffFormatter. |
tests/_utils/test_formatter.py |
Updates RuffFormatter mock expectations and adds a new test for cell-body formatting behavior. |
| "--line-length", | ||
| str(self.line_length), | ||
| stdin_filename=stdin_filename, | ||
| format_as_cell=True, |
| async def test_ruff_function_formats_code_as_cell_body(self): | ||
| codes: CellCodes = { | ||
| "cell1": "x = 3\n\n\ndef _foo():\n return x + 1\n\n\nprint(_foo())" | ||
| } | ||
|
|
||
| result = await ruff( | ||
| codes, | ||
| "format", | ||
| "--line-length", | ||
| "88", | ||
| format_as_cell=True, | ||
| ) | ||
|
|
||
| assert result == { | ||
| "cell1": "x = 3\n\ndef _foo():\n return x + 1\n\nprint(_foo())" | ||
| } |
There was a problem hiding this comment.
No issues found across 2 files
Architecture diagram
sequenceDiagram
participant Fmt as `Formatter.format()`
participant RuffFn as `ruff()`
participant Wrap as `_wrap_cell_code()`
participant SubProc as `_run_subprocess_safe()`
participant RuffProc as Ruff (subprocess)
participant Unwrap as `_unwrap_cell_code()`
Note over Fmt,RuffFn: NEW: format as cell body flow
Fmt->>RuffFn: format(codes, ..., format_as_cell=True)
loop for each cell (key, code)
alt format_as_cell=True
RuffFn->>Wrap: _wrap_cell_code(code)
Wrap->>Wrap: Check if cell has statement
alt no statement (comment-only or empty)
Wrap->>RuffFn: wrapped_code, added_pass=True
else has statement
Wrap->>RuffFn: wrapped_code, added_pass=False
end
else format_as_cell=False (unchanged)
RuffFn->>RuffFn: input_code = code (no wrap)
end
RuffFn->>SubProc: _run_subprocess_safe(command, input_data=input_code)
SubProc->>RuffProc: run ruff format --
RuffProc-->>SubProc: stdout (formatted code)
SubProc-->>RuffFn: (stdout, stderr, returncode)
alt returncode != 0
RuffFn-->>Fmt: FormatError
else success
alt format_as_cell=True
RuffFn->>Unwrap: _unwrap_cell_code(stdout, added_pass)
Unwrap->>Unwrap: Remove wrapper, optionally trailing pass
Unwrap-->>RuffFn: unwrapped_code
else format_as_cell=False
RuffFn->>RuffFn: formatted = stdout (no unwrap)
end
RuffFn->>RuffFn: store formatted.strip() in result dict
end
end
RuffFn-->>Fmt: formatted codes dict
Summary
Closes #9848
Cell formatting currently sends each cell directly to Ruff as stdin, so Ruff treats the cell as module-level code. That adds module-level spacing around nested function definitions, which is one blank line too many once the cell is serialized inside
def _():.This formats Ruff-backed cells inside a temporary async function wrapper, then unwraps the formatted body before returning it. That keeps Ruff's parser and formatter behavior aligned with the final marimo cell scope, including cells with top-level
await. Empty or comment-only cells get a temporarypassonly for formatting and have it removed afterward.Tests
python -m pytest tests\_utils\test_formatter.py -qpython -m ruff check marimo\_utils\formatter.py tests\_utils\test_formatter.pygit diff --check