fix(formatter): wrap cells in function scope before ruff formatting#9889
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
for more information, see https://pre-commit.ci
| # Wrapping caused ruff to reject this cell; retry unwrapped. | ||
| fallback_keys.append(key) | ||
|
|
||
| if fallback_keys: |
There was a problem hiding this comment.
Any known code paths trigger this or is this just defensive?
There was a problem hiding this comment.
Couldn't find a real code path that triggers it(any valid cell body is valid inside def _():), so removed it.
dmadisetti
left a comment
There was a problem hiding this comment.
Thanks again @VishakBaddur !
Test looks great. I wonder if we can trigger that "failed" branch though
Think you can give a shot at using marimo/ast/_parse.py?
|
Switched the unwrap to use marimo/_ast/parse.py and removed the fallback since I couldn't find a case that actually hits it. Let me know if you think there's an edge case I'm missing! |
for more information, see https://pre-commit.ci
dmadisetti
left a comment
There was a problem hiding this comment.
Sorry found a bug- but clean!
| # left unwrapped because an empty function body is invalid Python. | ||
| wrapped: CellCodes = {} | ||
| for key, code in codes.items(): | ||
| if code.strip(): |
There was a problem hiding this comment.
This may fail for pure comments too just realizing- can we add a test?
def _():
# just a commentis also a syntax error
There was a problem hiding this comment.
fixed by precomputing skip_wrap, a set of keys whose code has no executable statements (all lines are whitespace or comments). Both the wrap and unwrap loops key off this set, so comment-only cells are never wrapped. I've also added a test covering this case.
| assert isinstance(fn, ast.FunctionDef) | ||
| assert fn.end_lineno is not None |
There was a problem hiding this comment.
We should throw a value error instead of assert here I think.
Assertions are a little obtuse, and although this path should never be hit (type safety sanity check)
There was a problem hiding this comment.
replaced both with raise ValueError
| wrapped: CellCodes = {} | ||
| for key, code in codes.items(): | ||
| if code.strip(): | ||
| wrapped[key] = "def _():\n" + textwrap.indent(code, " ") |
There was a problem hiding this comment.
While we're making changes, fstring?
There was a problem hiding this comment.
nvm, the \n will make that annoying
| wrapped[key] = "def _():\n" + textwrap.indent(code, " ") | |
| wrapped[key] = "\n".join("def _():", textwrap.indent(code, " ")) |
or reuse the helper, which will take care of the comment case (because there will be a return)
There was a problem hiding this comment.
Used "\n".join(...) as suggested. Regarding the alternative to reuse the helper, if we wrapped comment-only cells and appended a pass to make the function body syntactically valid, the pass would end up preserved in the unwrapped output. Handling them via skip_wrap keeps the final output completely clean.
| # parse mechanism in marimo/_ast/parse.py (Extractor + fixed_dedent). | ||
| result: CellCodes = {} | ||
| for key, code in codes.items(): | ||
| if not code.strip(): |
There was a problem hiding this comment.
If we just use the helper, then we won't need the conditional (all code contents should be handled)
There was a problem hiding this comment.
The skip_wrap set removes the need for the inline condition in both loops. wrap loop uses key not in skip_wrap, unwrap loop uses key in skip_wrap. No duplication, no condition repeated inline
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.11-dev26 |
📝 Summary
Closes #9848
RuffFormatter.format()was passing raw cell code directly toruff formatvia stdin. Ruff treats stdin as file scope and applies E302 (2 blank lines around top-level function definitions). But when the user runsruffon the saved.pyfile, cells are already wrapped indef _():functions, so ruff applies E301 (1 blank line around nested defs). This creates a conflict - marimo's in-editor formatting and ruff on the saved file disagree on blank lines around nested functions.Fix: Before sending to ruff, wrap each non-empty cell in
def _():\n+ indented body. After formatting, drop the first line, dedent, and strip. This makes marimo match what ruff produces on the saved file. Whitespace-only cells are left unwrapped (empty function body is invalid Python). If wrapping causes ruff to reject a cell, it falls back to formatting unwrapped.📋 Pre-Review Checklist
✅ Merge Checklist