#![cfg(not(target_family = "wasm"))] use std::fs; use std::path::Path; use std::process::Command; use std::str; use anyhow::Result; use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; use tempfile::TempDir; const BIN_NAME: &str = "ruff"; #[test] fn default_options() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--isolated", "--stdin-filename", "test.py"]) .arg("-") .pass_stdin(r#" def foo(arg1, arg2,): print('Should\'t change quotes') if condition: print('Hy "Micha"') # Should not change quotes "#), @r###" success: true exit_code: 0 ----- stdout ----- def foo( arg1, arg2, ): print("Should't change quotes") if condition: print('Hy "Micha"') # Should not change quotes ----- stderr ----- "###); } #[test] fn default_files() -> Result<()> { let tempdir = TempDir::new()?; fs::write( tempdir.path().join("foo.py"), r#" foo = "needs formatting" "#, )?; fs::write( tempdir.path().join("bar.py"), r#" bar = "needs formatting" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--isolated", "--no-cache", "--check"]).current_dir(tempdir.path()), @r###" success: false exit_code: 1 ----- stdout ----- Would reformat: bar.py Would reformat: foo.py 2 files would be reformatted ----- stderr ----- "###); Ok(()) } #[test] fn format_warn_stdin_filename_with_files() { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--isolated", "--stdin-filename", "foo.py"]) .arg("foo.py") .pass_stdin("foo = 1"), @r###" success: true exit_code: 0 ----- stdout ----- foo = 1 ----- stderr ----- warning: Ignoring file foo.py in favor of standard input. "###); } #[test] fn format_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" indent-width = 8 line-length = 84 [format] indent-style = "tab" quote-style = "single" skip-magic-trailing-comma = true line-ending = "cr-lf" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r#" def foo(arg1, arg2,): print("Shouldn't change quotes. It exceeds the line width with the tab size 8") if condition: print("Should change quotes") "#), @r###" success: true exit_code: 0 ----- stdout ----- def foo(arg1, arg2): print( "Shouldn't change quotes. It exceeds the line width with the tab size 8" ) if condition: print('Should change quotes') ----- stderr ----- "###); Ok(()) } #[test] fn docstring_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" [format] docstring-code-format = true docstring-code-line-length = 20 "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r#" def f(x): ''' Something about `f`. And an example: .. code-block:: python foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) Another example: ```py foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) ``` And another: >>> foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) ''' pass "#), @r###" success: true exit_code: 0 ----- stdout ----- def f(x): """ Something about `f`. And an example: .. code-block:: python ( foo, bar, quux, ) = this_is_a_long_line( lion, hippo, lemur, bear, ) Another example: ```py ( foo, bar, quux, ) = this_is_a_long_line( lion, hippo, lemur, bear, ) ``` And another: >>> ( ... foo, ... bar, ... quux, ... ) = this_is_a_long_line( ... lion, ... hippo, ... lemur, ... bear, ... ) """ pass ----- stderr ----- "###); Ok(()) } #[test] fn mixed_line_endings() -> Result<()> { let tempdir = TempDir::new()?; fs::write( tempdir.path().join("main.py"), "from test import say_hy\n\nif __name__ == \"__main__\":\n say_hy(\"dear Ruff contributor\")\n", )?; fs::write( tempdir.path().join("test.py"), "def say_hy(name: str):\r\n print(f\"Hy {name}\")\r\n", )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--diff", "--isolated"]) .arg("."), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- 2 files already formatted "###); Ok(()) } #[test] fn exclude() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-exclude = ["out"] [format] exclude = ["test.py", "generated.py"] "#, )?; fs::write( tempdir.path().join("main.py"), r#" from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") "#, )?; // Excluded file but passed to the CLI directly, should be formatted let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; fs::write( tempdir.path().join("generated.py"), r#"NUMBERS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] OTHER = "OTHER" "#, )?; let out_dir = tempdir.path().join("out"); fs::create_dir(&out_dir)?; fs::write(out_dir.join("a.py"), "a = a")?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--check", "--config"]) .arg(ruff_toml.file_name().unwrap()) // Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude .arg(test_path.file_name().unwrap()) // Format all other files in the directory, should respect the `exclude` and `format.exclude` options .arg("."), @r###" success: false exit_code: 1 ----- stdout ----- Would reformat: main.py Would reformat: test.py 2 files would be reformatted ----- stderr ----- "###); Ok(()) } #[test] fn messages() -> Result<()> { let tempdir = TempDir::new()?; fs::write( tempdir.path().join("main.py"), r#" from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--isolated", "--check"]) .arg("main.py"), @r###" success: false exit_code: 1 ----- stdout ----- Would reformat: main.py 1 file would be reformatted ----- stderr ----- "###); assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--isolated"]) .arg("main.py"), @r###" success: true exit_code: 0 ----- stdout ----- 1 file reformatted ----- stderr ----- "###); assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--isolated"]) .arg("main.py"), @r###" success: true exit_code: 0 ----- stdout ----- 1 file left unchanged ----- stderr ----- "###); Ok(()) } #[test] fn force_exclude() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-exclude = ["out"] [format] exclude = ["test.py", "generated.py"] "#, )?; fs::write( tempdir.path().join("main.py"), r#" from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") "#, )?; // Excluded file but passed to the CLI directly, should be formatted let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; fs::write( tempdir.path().join("generated.py"), r#"NUMBERS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] OTHER = "OTHER" "#, )?; let out_dir = tempdir.path().join("out"); fs::create_dir(&out_dir)?; fs::write(out_dir.join("a.py"), "a = a")?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--no-cache", "--force-exclude", "--check", "--config"]) .arg(ruff_toml.file_name().unwrap()) // Explicitly pass test.py, should be respect the `format.exclude` when `--force-exclude` is present .arg(test_path.file_name().unwrap()) // Format all other files in the directory, should respect the `exclude` and `format.exclude` options .arg("."), @r###" success: false exit_code: 1 ----- stdout ----- Would reformat: main.py 1 file would be reformatted ----- stderr ----- "###); Ok(()) } #[test] fn exclude_stdin() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] ignore = ["Q000", "Q001", "Q002", "Q003"] [format] exclude = ["generated.py"] "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "-"]) .pass_stdin(r#" from test import say_hy if __name__ == '__main__': say_hy("dear Ruff contributor") "#), @r###" success: true exit_code: 0 ----- stdout ----- from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") ----- stderr ----- "###); Ok(()) } #[test] fn force_exclude_stdin() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] ignore = ["Q000", "Q001", "Q002", "Q003"] [format] exclude = ["generated.py"] "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "--force-exclude", "-"]) .pass_stdin(r#" from test import say_hy if __name__ == '__main__': say_hy("dear Ruff contributor") "#), @r###" success: true exit_code: 0 ----- stdout ----- from test import say_hy if __name__ == '__main__': say_hy("dear Ruff contributor") ----- stderr ----- "###); Ok(()) } #[test] fn format_option_inheritance() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); let base_toml = tempdir.path().join("base.toml"); fs::write( &ruff_toml, r#" extend = "base.toml" [lint] extend-select = ["COM812"] [format] quote-style = "single" "#, )?; fs::write( base_toml, r#" [format] indent-style = "tab" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r#" def foo(arg1, arg2,): print("Shouldn't change quotes") if condition: print("Should change quotes") "#), @r###" success: true exit_code: 0 ----- stdout ----- def foo( arg1, arg2, ): print("Shouldn't change quotes") if condition: print('Should change quotes') ----- stderr ----- warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration. "###); Ok(()) } #[test] fn deprecated_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r" tab-size = 2 ", )?; insta::with_settings!({filters => vec![ (&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"), ]}, { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r" if True: pass "), @r###" success: true exit_code: 0 ----- stdout ----- if True: pass ----- stderr ----- warning: The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = ` instead. "###); }); Ok(()) } /// Since 0.1.0 the legacy format option is no longer supported #[test] fn legacy_format_option() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" format = "json" "#, )?; insta::with_settings!({filters => vec![ (&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"), ]}, { assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["check", "--select", "F401", "--no-cache", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r" import os "), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- ruff failed Cause: Failed to parse `[RUFF-TOML-PATH]`: TOML parse error at line 2, column 10 | 2 | format = "json" | ^^^^^^ invalid type: string "json", expected struct FormatOptions "###); }); Ok(()) } #[test] fn conflicting_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" indent-width = 2 [lint] select = ["ALL"] ignore = ["D203", "D212"] [lint.isort] lines-after-imports = 3 lines-between-types = 2 force-wrap-aliases = true combine-as-imports = true split-on-trailing-comma = true [lint.flake8-quotes] inline-quotes = "single" docstring-quotes = "single" multiline-quotes = "single" [format] skip-magic-trailing-comma = true indent-style = "tab" "#, )?; let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--no-cache", "--config"]) .arg(&ruff_toml) .arg(test_path), @r###" success: true exit_code: 0 ----- stdout ----- 1 file reformatted ----- stderr ----- warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration. warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`. warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`. warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`. warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.` warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.` warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default). warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default). warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`. warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`. "###); Ok(()) } #[test] fn conflicting_options_stdin() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" indent-width = 2 [lint] select = ["ALL"] ignore = ["D203", "D212"] [lint.isort] lines-after-imports = 3 lines-between-types = 2 force-wrap-aliases = true combine-as-imports = true split-on-trailing-comma = true [lint.flake8-quotes] inline-quotes = "single" docstring-quotes = "single" multiline-quotes = "single" [format] skip-magic-trailing-comma = true indent-style = "tab" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--config"]) .arg(&ruff_toml) .arg("-") .pass_stdin(r#" def say_hy(name: str): print(f"Hy {name}")"#), @r###" success: true exit_code: 0 ----- stdout ----- def say_hy(name: str): print(f"Hy {name}") ----- stderr ----- warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration. warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`. warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`. warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`. warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.` warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.` warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default). warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default). warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`. warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`. "###); Ok(()) } #[test] fn valid_linter_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" [lint] select = ["ALL"] ignore = ["D203", "D212", "COM812", "ISC001"] [lint.isort] lines-after-imports = 2 lines-between-types = 1 force-wrap-aliases = true combine-as-imports = true split-on-trailing-comma = true [lint.flake8-quotes] inline-quotes = "single" docstring-quotes = "double" multiline-quotes = "double" [format] skip-magic-trailing-comma = false quote-style = "single" "#, )?; let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--no-cache", "--config"]) .arg(&ruff_toml) .arg(test_path), @r###" success: true exit_code: 0 ----- stdout ----- 1 file reformatted ----- stderr ----- "###); Ok(()) } #[test] fn all_rules_default_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" [lint] select = ["ALL"] "#, )?; let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--no-cache", "--config"]) .arg(&ruff_toml) .arg(test_path), @r###" success: true exit_code: 0 ----- stdout ----- 1 file reformatted ----- stderr ----- warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`. warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`. warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration. "###); Ok(()) } #[test] fn test_diff() { let args = ["format", "--no-cache", "--isolated", "--diff"]; let fixtures = Path::new("resources").join("test").join("fixtures"); let paths = [ fixtures.join("unformatted.py"), fixtures.join("formatted.py"), fixtures.join("unformatted.ipynb"), ]; insta::with_settings!({filters => vec![ // Replace windows paths (r"\\", "/"), ]}, { assert_cmd_snapshot!( Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths), @r###" success: false exit_code: 1 ----- stdout ----- --- resources/test/fixtures/unformatted.ipynb:cell 1 +++ resources/test/fixtures/unformatted.ipynb:cell 1 @@ -1,3 +1,4 @@ import numpy -maths = (numpy.arange(100)**2).sum() -stats= numpy.asarray([1,2,3,4]).median() + +maths = (numpy.arange(100) ** 2).sum() +stats = numpy.asarray([1, 2, 3, 4]).median() --- resources/test/fixtures/unformatted.ipynb:cell 3 +++ resources/test/fixtures/unformatted.ipynb:cell 3 @@ -1,4 +1,6 @@ # A cell with IPython escape command def some_function(foo, bar): pass + + %matplotlib inline --- resources/test/fixtures/unformatted.ipynb:cell 4 +++ resources/test/fixtures/unformatted.ipynb:cell 4 @@ -1,5 +1,10 @@ foo = %pwd -def some_function(foo,bar,): + + +def some_function( + foo, + bar, +): # Another cell with IPython escape command foo = %pwd print(foo) --- resources/test/fixtures/unformatted.py +++ resources/test/fixtures/unformatted.py @@ -1,3 +1,3 @@ x = 1 -y=2 +y = 2 z = 3 ----- stderr ----- 2 files would be reformatted, 1 file already formatted "###); }); } #[test] fn test_diff_no_change() { let args = ["format", "--no-cache", "--isolated", "--diff"]; let fixtures = Path::new("resources").join("test").join("fixtures"); let paths = [fixtures.join("unformatted.py")]; insta::with_settings!({filters => vec![ // Replace windows paths (r"\\", "/"), ]}, { assert_cmd_snapshot!( Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths), @r###" success: false exit_code: 1 ----- stdout ----- --- resources/test/fixtures/unformatted.py +++ resources/test/fixtures/unformatted.py @@ -1,3 +1,3 @@ x = 1 -y=2 +y = 2 z = 3 ----- stderr ----- 1 file would be reformatted "### ); }); } #[test] fn test_diff_stdin_unformatted() { let args = [ "format", "--isolated", "--diff", "-", "--stdin-filename", "unformatted.py", ]; let fixtures = Path::new("resources").join("test").join("fixtures"); let unformatted = fs::read(fixtures.join("unformatted.py")).unwrap(); assert_cmd_snapshot!( Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted), @r###" success: false exit_code: 1 ----- stdout ----- --- unformatted.py +++ unformatted.py @@ -1,3 +1,3 @@ x = 1 -y=2 +y = 2 z = 3 ----- stderr ----- "###); } #[test] fn test_diff_stdin_formatted() { let args = ["format", "--isolated", "--diff", "-"]; let fixtures = Path::new("resources").join("test").join("fixtures"); let unformatted = fs::read(fixtures.join("formatted.py")).unwrap(); assert_cmd_snapshot!( Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- "###); } #[test] fn test_notebook_trailing_semicolon() { let fixtures = Path::new("resources").join("test").join("fixtures"); let unformatted = fs::read(fixtures.join("trailing_semicolon.ipynb")).unwrap(); assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["format", "--isolated", "--stdin-filename", "test.ipynb"]) .arg("-") .pass_stdin(unformatted), @r###" success: true exit_code: 0 ----- stdout ----- { "cells": [ { "cell_type": "code", "execution_count": 1, "id": "4f8ce941-1492-4d4e-8ab5-70d733fe891a", "metadata": {}, "outputs": [], "source": [ "%config ZMQInteractiveShell.ast_node_interactivity=\"last_expr_or_assign\"" ] }, { "cell_type": "code", "execution_count": 2, "id": "721ec705-0c65-4bfb-9809-7ed8bc534186", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Assignment statement without a semicolon\n", "x = 1" ] }, { "cell_type": "code", "execution_count": 3, "id": "de50e495-17e5-41cc-94bd-565757555d7e", "metadata": {}, "outputs": [], "source": [ "# Assignment statement with a semicolon\n", "x = 1\n", "x = 1;" ] }, { "cell_type": "code", "execution_count": 4, "id": "39e31201-23da-44eb-8684-41bba3663991", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Augmented assignment without a semicolon\n", "x += 1" ] }, { "cell_type": "code", "execution_count": 5, "id": "6b73d3dd-c73a-4697-9e97-e109a6c1fbab", "metadata": {}, "outputs": [], "source": [ "# Augmented assignment without a semicolon\n", "x += 1\n", "x += 1; # comment\n", "# comment" ] }, { "cell_type": "code", "execution_count": 6, "id": "2a3e5b86-aa5b-46ba-b9c6-0386d876f58c", "metadata": {}, "outputs": [], "source": [ "# Multiple assignment without a semicolon\n", "x = y = 1" ] }, { "cell_type": "code", "execution_count": 7, "id": "07f89e51-9357-4cfb-8fc5-76fb75e35949", "metadata": {}, "outputs": [], "source": [ "# Multiple assignment with a semicolon\n", "x = y = 1\n", "x = y = 1" ] }, { "cell_type": "code", "execution_count": 8, "id": "c22b539d-473e-48f8-a236-625e58c47a00", "metadata": {}, "outputs": [], "source": [ "# Tuple unpacking without a semicolon\n", "x, y = 1, 2" ] }, { "cell_type": "code", "execution_count": 9, "id": "12c87940-a0d5-403b-a81c-7507eb06dc7e", "metadata": {}, "outputs": [], "source": [ "# Tuple unpacking with a semicolon (irrelevant)\n", "x, y = 1, 2\n", "x, y = 1, 2 # comment\n", "# comment" ] }, { "cell_type": "code", "execution_count": 10, "id": "5a768c76-6bc4-470c-b37e-8cc14bc6caf4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Annotated assignment statement without a semicolon\n", "x: int = 1" ] }, { "cell_type": "code", "execution_count": 11, "id": "21bfda82-1a9a-4ba1-9078-74ac480804b5", "metadata": {}, "outputs": [], "source": [ "# Annotated assignment statement without a semicolon\n", "x: int = 1\n", "x: int = 1; # comment\n", "# comment" ] }, { "cell_type": "code", "execution_count": 12, "id": "09929999-ff29-4d10-ad2b-e665af15812d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Assignment expression without a semicolon\n", "(x := 1)" ] }, { "cell_type": "code", "execution_count": 13, "id": "32a83217-1bad-4f61-855e-ffcdb119c763", "metadata": {}, "outputs": [], "source": [ "# Assignment expression with a semicolon\n", "(x := 1)\n", "(x := 1); # comment\n", "# comment" ] }, { "cell_type": "code", "execution_count": 14, "id": "61b81865-277e-4964-b03e-eb78f1f318eb", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1\n", "# Expression without a semicolon\n", "x" ] }, { "cell_type": "code", "execution_count": 15, "id": "974c29be-67e1-4000-95fa-6ca118a63bad", "metadata": {}, "outputs": [], "source": [ "x = 1\n", "# Expression with a semicolon\n", "x;" ] }, { "cell_type": "code", "execution_count": 16, "id": "cfeb1757-46d6-4f13-969f-a283b6d0304f", "metadata": {}, "outputs": [], "source": [ "class Point:\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", "\n", "\n", "p = Point(0, 0);" ] }, { "cell_type": "code", "execution_count": 17, "id": "2ee7f1a5-ccfe-4004-bfa4-ef834a58da97", "metadata": {}, "outputs": [], "source": [ "# Assignment statement where the left is an attribute access doesn't\n", "# print the value.\n", "p.x = 1" ] }, { "cell_type": "code", "execution_count": 18, "id": "3e49370a-048b-474d-aa0a-3d1d4a73ad37", "metadata": {}, "outputs": [], "source": [ "data = {}\n", "\n", "# Neither does the subscript node\n", "data[\"foo\"] = 1" ] }, { "cell_type": "code", "execution_count": 19, "id": "d594bdd3-eaa9-41ef-8cda-cf01bc273b2d", "metadata": {}, "outputs": [], "source": [ "if x := 1:\n", " # It should be the top level statement\n", " x" ] }, { "cell_type": "code", "execution_count": 20, "id": "e532f0cf-80c7-42b7-8226-6002fcf74fb6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Parentheses with comments\n", "(\n", " x := 1 # comment\n", ") # comment" ] }, { "cell_type": "code", "execution_count": 21, "id": "473c5d62-871b-46ed-8a34-27095243f462", "metadata": {}, "outputs": [], "source": [ "# Parentheses with comments\n", "(\n", " x := 1 # comment\n", "); # comment" ] }, { "cell_type": "code", "execution_count": 22, "id": "8c3c2361-f49f-45fe-bbe3-7e27410a8a86", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello world!'" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Hello world!\"\"\"" ] }, { "cell_type": "code", "execution_count": 23, "id": "23dbe9b5-3f68-4890-ab2d-ab0dbfd0712a", "metadata": {}, "outputs": [], "source": [ "\"\"\"Hello world!\"\"\"; # comment\n", "# comment" ] }, { "cell_type": "code", "execution_count": 24, "id": "3ce33108-d95d-4c70-83d1-0d4fd36a2951", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'x = 1'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1\n", "f\"x = {x}\"" ] }, { "cell_type": "code", "execution_count": 25, "id": "654a4a67-de43-4684-824a-9451c67db48f", "metadata": {}, "outputs": [], "source": [ "x = 1\n", "f\"x = {x}\"\n", "f\"x = {x}\"; # comment\n", "# comment" ] } ], "metadata": { "kernelspec": { "display_name": "Python (ruff-playground)", "language": "python", "name": "ruff-playground" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.3" } }, "nbformat": 4, "nbformat_minor": 5 } ----- stderr ----- "###); }