Skip to content

refactor: deepen fix into fixer.py; main() becomes an adapter#21

Merged
lesnik512 merged 6 commits into
mainfrom
feat/deepen-fixer
Jun 26, 2026
Merged

refactor: deepen fix into fixer.py; main() becomes an adapter#21
lesnik512 merged 6 commits into
mainfrom
feat/deepen-fixer

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What & why

A deepening refactor (from the architecture review): the EOF-fixing logic moves out of main.py into a new module eof_fixer/fixer.py, and main() becomes a thin CLI adapter. The goal is testability — the fix behavior previously had no interface, so all 16 tests reached it by swapping sys.argv + sys.stdout + os.chdir. Now the interface is the test surface.

No behavior change except one documented, non-semantic detail: Fixing lines are batch-rendered after the walk instead of streamed during it (same lines, order, exit codes).

Shape

eof_fixer/fixer.py:

  • fix_file(file_obj, *, check) -> bool — normalize one open file; binary-skip + the none/append_lf/truncate action model (moved verbatim).
  • fix_directory(root, *, check=False, extra_excludes=()) -> list[pathlib.Path] — drives discovery.iter_text_files, owns the default-skip policy DEFAULT_EXCLUDES = (".cache", ".uv-cache"), returns the fixed relative paths.

eof_fixer/main.py: argparse → is_dir guard (exit 2) → fix_directory → render Fixing lines → exit 1/0. No file-level logic, no os/IO imports.

Tests, three layers

  • Contentfix_file(BytesIO(...)) — CRLF/CR/BOM/null-byte/empty, no filesystem.
  • Orchestrationfix_directory(tmp_path, ...) — gitignore/binary/symlink/--exclude/check/readonly/cwd.
  • Adapter → 2 path-rejection + 2 render tests through main(); _run_main_in shrinks to serve only these.

Planning artifacts (Full lane)

  • Spec + plan: planning/changes/2026-06-26.02-deepen-fixer/
  • Decision (constants stay local, no settings module): planning/decisions/2026-06-26-no-central-settings-module.md
  • Architecture promotion: architecture/eof-normalization.md, architecture/cli.md

Verification

Built via subagent-driven TDD (per-task spec+quality reviews, all PASS) + a final whole-branch review (Ready to merge: Yes, behavior preservation verified against the old code, incl. the empty-file OSError vs BytesIO paths). just test35 passed, 100% coverage; just lint-ci clean (ruff select=["ALL"], ty).

🤖 Generated with Claude Code

lesnik512 and others added 6 commits June 26, 2026 21:41
Records the design choice (made while planning the fixer deepening) to
keep constants beside their owning behavior rather than gathering them
into a settings/constants module; revisit when config becomes
runtime-loaded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full-lane bundle 2026-06-26.02-deepen-fixer: extract fix_file +
fix_directory into eof_fixer/fixer.py, make main() a thin adapter, and
move the test surface off the argv/stdout/cwd harness. Three-task TDD
plan with complete code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One deviation from the brief: added an empty-read guard in _detect_trailing
(after read(1) returns b"") because BytesIO silently clamps seek(-1, SEEK_END)
to position 0 on an empty buffer rather than raising OSError as real files do.
Added # noqa: PLR0911 for the extra return statement this introduces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds test_empty_real_file_unchanged to test_fixer.py to cover the
OSError branch in _detect_trailing (seek(-1, SEEK_END) on a real empty
file), which was previously masked by the deleted harness tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…railing

Swap the final `return ("none", 0)  # pragma: no cover` for
`raise AssertionError("unreachable")` so the invariant fails loud instead
of silently returning a wrong value. With only 6 return paths remaining,
the PLR0911 suppression is no longer needed and has been removed.
EM101 is suppressed inline on the unreachable line only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit 8a19455 into main Jun 26, 2026
6 checks passed
@lesnik512 lesnik512 deleted the feat/deepen-fixer branch June 26, 2026 19:20
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