Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [

requires-python = ">=3.10"
dependencies = [
"click >= 8.0, < 8.2",
"click >= 8.3.3, < 9",
"libcst >= 0.3.18",
"moreorless >= 0.4.0",
"packaging >= 21",
Expand All @@ -48,7 +48,7 @@ dev = [
"build > 1",
"flake8 == 7.3.0",
"flake8-bugbear == 24.12.12",
"ufmt == 2.8.0",
"ufmt == 2.9.1",
"usort == 1.0.8.post1",
"pyrefly == 0.63.1",
]
Expand Down
13 changes: 12 additions & 1 deletion src/fixit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,18 @@ def f(v: int) -> str:
click.secho(f"🧼 {len(visited)} {f(len(visited))} clean 🧼", err=True)


@click.group()
class _FixitGroup(click.Group):
"""Keep the pre-Click 8.2 behavior for an empty command line."""

def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
if not args and self.no_args_is_help and not ctx.resilient_parsing:
click.echo(ctx.get_help(), color=ctx.color)
ctx.exit()

return super().parse_args(ctx, args)


@click.group(cls=_FixitGroup)
@click.pass_context
@click.version_option(__version__, "--version", "-V", prog_name="fixit")
@click.option(
Expand Down
14 changes: 7 additions & 7 deletions src/fixit/tests/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ def test_format_output(self) -> None:
)
)

runner = CliRunner(mix_stderr=False)
runner = CliRunner()
content = "name = '{name}'.format(name='Jane Doe')"
filepath = self.tdp / "f_string.py"
filepath.write_text(content)
Expand All @@ -622,13 +622,13 @@ def test_format_output(self) -> None:
result = runner.invoke(
main, ["lint", filepath.as_posix()], catch_exceptions=False
)
self.assertRegex(result.output, output_format_regex)
self.assertRegex(result.stdout, output_format_regex)

with self.subTest("fixing vscode"):
result = runner.invoke(
main, ["fix", filepath.as_posix()], catch_exceptions=False
)
self.assertRegex(result.output, output_format_regex)
self.assertRegex(result.stdout, output_format_regex)

custom_output_format_regex = r".*f_string\.py|\d+|\d+ UseFstring: .+"
custom_output_format = (
Expand All @@ -648,21 +648,21 @@ def test_format_output(self) -> None:
result = runner.invoke(
main, ["lint", filepath.as_posix()], catch_exceptions=False
)
self.assertRegex(result.output, custom_output_format_regex)
self.assertRegex(result.stdout, custom_output_format_regex)

with self.subTest("fixing custom"):
result = runner.invoke(
main, ["fix", filepath.as_posix()], catch_exceptions=False
)
self.assertRegex(result.output, custom_output_format_regex)
self.assertRegex(result.stdout, custom_output_format_regex)

with self.subTest("override output-format"):
result = runner.invoke(
main,
["--output-format", "vscode", "lint", filepath.as_posix()],
catch_exceptions=True,
)
self.assertRegex(result.output, output_format_regex)
self.assertRegex(result.stdout, output_format_regex)

with self.subTest("override output-template"):
result = runner.invoke(
Expand All @@ -676,7 +676,7 @@ def test_format_output(self) -> None:
catch_exceptions=True,
)
self.assertRegex(
result.output, r"file .*f_string\.py line \d+ rule UseFstring"
result.stdout, r"file .*f_string\.py line \d+ rule UseFstring"
)

def test_validate_config(self) -> None:
Expand Down
55 changes: 35 additions & 20 deletions src/fixit/tests/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,22 @@

class SmokeTest(TestCase):
def setUp(self) -> None:
self.runner = CliRunner(mix_stderr=False)
self.runner = CliRunner()

def test_cli_without_args_shows_help(self) -> None:
result = self.runner.invoke(main, [])

self.assertEqual(result.exit_code, 0)
self.assertRegex(result.stdout, r"^Usage: ")
self.assertIn("Commands:", result.stdout)
self.assertEqual(result.stderr, "")

def test_cli_options_without_command_fail(self) -> None:
result = self.runner.invoke(main, ["--debug"])

self.assertEqual(result.exit_code, 2)
self.assertEqual(result.stdout, "")
self.assertIn("Error: Missing command.", result.stderr)

def test_cli_version(self) -> None:
result = self.runner.invoke(main, ["--version"])
Expand Down Expand Up @@ -66,10 +81,10 @@ def func():
main, ["lint", path.as_posix()], catch_exceptions=False
)

self.assertNotEqual(result.output, "")
self.assertNotEqual(result.stdout, "")
self.assertNotEqual(result.exit_code, 0)
self.assertRegex(
result.output,
result.stdout,
r"file\.py@\d+:\d+ NoRedundantFString: .+ \(has autofix\)",
)
self.assertEqual(content, path.read_text(), "file unexpectedly changed")
Expand All @@ -82,10 +97,10 @@ def func():
catch_exceptions=False,
)

self.assertNotEqual(result.output, "")
self.assertNotEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)
self.assertRegex(
result.output,
result.stdout,
r"file\.py@\d+:\d+ NoRedundantFString: .+ \(has autofix\)",
)
self.assertEqual(
Expand All @@ -102,10 +117,10 @@ def func():
catch_exceptions=False,
)

self.assertNotEqual(result.output, "")
self.assertNotEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)
self.assertRegex(
result.output,
result.stdout,
r"file\.py@\d+:\d+ NoRedundantFString: .+ \(has autofix\)",
)
self.assertEqual(
Expand All @@ -120,10 +135,10 @@ def func():
catch_exceptions=False,
)

self.assertNotEqual(result.output, "")
self.assertNotEqual(result.stdout, "")
self.assertNotEqual(result.exit_code, 0)
self.assertRegex(
result.output,
result.stdout,
r"file\.py@\d+:\d+ NoRedundantFString: .+ \(has autofix\)",
)

Expand All @@ -136,7 +151,7 @@ def func():
)

self.assertEqual(result.exit_code, 0)
self.assertEqual(expected_format, result.output, "unexpected stdout")
self.assertEqual(expected_format, result.stdout, "unexpected stdout")

with self.subTest("LSP"):
path.write_text(content)
Expand All @@ -161,20 +176,20 @@ def payload(content: str) -> str:

self.assertEqual(result.exit_code, 0)
self.assertRegex(
result.output,
result.stdout,
r"file\.py\".+\"range\".+\"start\".+\"end\".+\"severity\": 2, \"code\": \"NoRedundantFString\", \"source\": \"fixit\"",
)

def test_this_file_is_clean(self) -> None:
path = Path(__file__).resolve().as_posix()
result = self.runner.invoke(main, ["lint", path], catch_exceptions=False)
self.assertEqual(result.output, "")
self.assertEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)

def test_this_project_is_clean(self) -> None:
project_dir = Path(__file__).resolve().parent.parent.as_posix()
result = self.runner.invoke(main, ["lint", project_dir], catch_exceptions=False)
self.assertEqual(result.output, "")
self.assertEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)

def test_directory_with_violations(self) -> None:
Expand All @@ -184,7 +199,7 @@ def test_directory_with_violations(self) -> None:
(tdp / "dirty.py").write_text("name = 'Kirby'\nprint('hello %s' % name)\n")

result = self.runner.invoke(main, ["lint", td])
self.assertIn("dirty.py@2:6 UseFstring:", result.output)
self.assertIn("dirty.py@2:6 UseFstring:", result.stdout)
self.assertEqual(result.exit_code, 1)

def test_directory_with_errors(self) -> None:
Expand All @@ -194,7 +209,7 @@ def test_directory_with_errors(self) -> None:
(tdp / "broken.py").write_text("print)\n")

result = self.runner.invoke(main, ["lint", td])
self.assertIn("broken.py: EXCEPTION: Syntax Error @ 1:", result.output)
self.assertIn("broken.py: EXCEPTION: Syntax Error @ 1:", result.stdout)
self.assertEqual(result.exit_code, 2)

def test_directory_with_violations_and_errors(self) -> None:
Expand All @@ -205,8 +220,8 @@ def test_directory_with_violations_and_errors(self) -> None:
(tdp / "broken.py").write_text("print)\n")

result = self.runner.invoke(main, ["lint", td])
self.assertIn("dirty.py@2:6 UseFstring:", result.output)
self.assertIn("broken.py: EXCEPTION: Syntax Error @ 1:", result.output)
self.assertIn("dirty.py@2:6 UseFstring:", result.stdout)
self.assertIn("broken.py: EXCEPTION: Syntax Error @ 1:", result.stdout)
self.assertEqual(result.exit_code, 3)

def test_directory_with_autofixes(self) -> None:
Expand Down Expand Up @@ -256,7 +271,7 @@ def foo():

result = self.runner.invoke(main, ["fix", "--automatic", td])
errors = defaultdict(list)
for line in result.output.splitlines():
for line in result.stdout.splitlines():
fn, _, error = line.partition("@")
short, _, _ = error.partition(": ")
errors[Path(fn)].append(short)
Expand Down Expand Up @@ -311,7 +326,7 @@ def func():
catch_exceptions=False,
)

self.assertEqual(result.output, "")
self.assertEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)

with self.subTest("fix"):
Expand All @@ -330,5 +345,5 @@ def func():
catch_exceptions=False,
)

self.assertEqual(result.output, "")
self.assertEqual(result.stdout, "")
self.assertEqual(result.exit_code, 0)