From 98e1a85a4b9bbc109d363c480624acdcb3fb35f8 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 7 May 2024 23:14:03 +0530 Subject: [PATCH] merge main --- .github/renovate.json5 | 11 + .github/workflows/docs.yaml | 2 +- .github/workflows/playground.yaml | 2 +- .pre-commit-config.yaml | 6 +- Cargo.lock | 36 +- _typos.toml | 1 + clippy.toml | 11 +- crates/red_knot/src/db/jars.rs | 2 +- crates/red_knot/src/hir.rs | 2 +- .../test/fixtures/flake8_blind_except/BLE.py | 9 + .../test/fixtures/flake8_bugbear/B019.py | 12 + .../test/fixtures/flake8_pyi/PYI059.py | 54 + .../test/fixtures/flake8_pyi/PYI059.pyi | 48 + .../src/checkers/ast/analyze/statement.rs | 5 +- crates/ruff_linter/src/checkers/ast/mod.rs | 19 +- crates/ruff_linter/src/codes.rs | 1 + .../rules/flake8_bandit/rules/django_extra.rs | 11 +- .../flake8_blind_except/rules/blind_except.rs | 195 ++- .../rules/cached_instance_method.rs | 74 +- .../rules/logging_call.rs | 23 +- .../rules/reimplemented_container_builtin.rs | 2 +- .../rules/unnecessary_dict_kwargs.rs | 38 +- .../flake8_pie/rules/unnecessary_spread.rs | 8 +- .../ruff_linter/src/rules/flake8_pyi/mod.rs | 2 + .../rules/generic_not_last_base_class.rs | 122 ++ .../src/rules/flake8_pyi/rules/mod.rs | 2 + .../rules/flake8_pyi/rules/simple_defaults.rs | 16 +- ...__flake8_pyi__tests__PYI059_PYI059.py.snap | 113 ++ ..._flake8_pyi__tests__PYI059_PYI059.pyi.snap | 112 ++ .../flake8_simplify/rules/yoda_conditions.rs | 4 +- .../src/rules/pyflakes/rules/repeated_keys.rs | 22 +- .../src/rules/pyflakes/rules/strings.rs | 11 +- .../pylint/rules/bad_string_format_type.rs | 16 +- .../pylint/rules/dict_iter_missing_items.rs | 8 +- .../rules/pylint/rules/non_slot_assignment.rs | 2 +- .../pylint/rules/repeated_keyword_argument.rs | 6 +- .../convert_typed_dict_functional_to_class.rs | 18 +- .../rules/printf_string_formatting.rs | 9 +- .../src/rules/ruff/rules/sort_dunder_slots.rs | 41 +- .../rules/tryceratops/rules/verbose_raise.rs | 46 +- crates/ruff_python_ast/Cargo.toml | 1 - crates/ruff_python_ast/src/comparable.rs | 30 +- crates/ruff_python_ast/src/helpers.rs | 18 +- crates/ruff_python_ast/src/node.rs | 8 +- crates/ruff_python_ast/src/nodes.rs | 167 +- crates/ruff_python_ast/src/visitor.rs | 16 +- .../src/visitor/transformer.rs | 16 +- .../Cargo.toml | 23 + .../README.md | 13 + .../src/lib.rs | 1 + .../tests/identifier.rs | 3 +- .../tests/parenthesize.rs | 0 .../tests/preorder.rs | 0 .../snapshots/preorder__bytes_literals.snap | 3 +- .../preorder__class_type_parameters.snap | 3 +- .../tests/snapshots/preorder__compare.snap | 3 +- .../tests/snapshots/preorder__decorators.snap | 3 +- .../preorder__dict_comprehension.snap | 3 +- .../tests/snapshots/preorder__f_strings.snap | 3 +- .../preorder__function_arguments.snap | 3 +- ...function_positional_only_with_default.snap | 3 +- .../preorder__function_type_parameters.snap | 3 +- .../preorder__list_comprehension.snap | 3 +- .../preorder__match_class_pattern.snap | 3 +- .../preorder__set_comprehension.snap | 3 +- .../snapshots/preorder__string_literals.snap | 3 +- .../snapshots/preorder__type_aliases.snap | 3 +- .../snapshots/visitor__bytes_literals.snap | 3 +- .../visitor__class_type_parameters.snap | 3 +- .../tests/snapshots/visitor__compare.snap | 3 +- .../tests/snapshots/visitor__decorators.snap | 3 +- .../visitor__dict_comprehension.snap | 3 +- .../tests/snapshots/visitor__f_strings.snap | 3 +- .../visitor__function_arguments.snap | 3 +- ...function_positional_only_with_default.snap | 3 +- .../visitor__function_type_parameters.snap | 3 +- .../visitor__list_comprehension.snap | 3 +- .../visitor__match_class_pattern.snap | 3 +- .../snapshots/visitor__set_comprehension.snap | 3 +- .../snapshots/visitor__string_literals.snap | 3 +- .../snapshots/visitor__type_aliases.snap | 3 +- .../tests/stmt_if.rs | 1 - .../tests/visitor.rs | 12 +- crates/ruff_python_codegen/src/generator.rs | 16 +- .../ruff_python_formatter/src/comments/mod.rs | 27 +- .../src/expression/expr_dict.rs | 31 +- .../src/expression/mod.rs | 6 +- .../src/parser/expression.rs | 23 +- .../ruff_python_parser/src/parser/recovery.rs | 23 +- ...tax@expressions__dict__double_star.py.snap | 638 ++++---- ...s__dict__double_star_comprehension.py.snap | 130 +- ...ons__dict__missing_closing_brace_0.py.snap | 76 +- ...ons__dict__missing_closing_brace_1.py.snap | 64 +- ...ons__dict__missing_closing_brace_2.py.snap | 36 +- ...ressions__dict__named_expression_0.py.snap | 120 +- ...ressions__dict__named_expression_1.py.snap | 120 +- ..._syntax@expressions__dict__recover.py.snap | 474 +++--- ...d_syntax@expressions__set__recover.py.snap | 32 +- .../invalid_syntax@invalid_del_target.py.snap | 54 +- ...ax@params_var_keyword_with_default.py.snap | 104 +- ...ements__invalid_assignment_targets.py.snap | 54 +- ...nvalid_augmented_assignment_target.py.snap | 54 +- ...ax@statements__match__as_pattern_3.py.snap | 3 +- ..._match__invalid_lhs_or_rhs_pattern.py.snap | 68 +- .../valid_syntax@expressions__await.py.snap | 36 +- .../valid_syntax@expressions__call.py.snap | 60 +- ...lid_syntax@expressions__dictionary.py.snap | 1436 +++++++++-------- ...valid_syntax@expressions__f_string.py.snap | 184 +-- .../valid_syntax@expressions__list.py.snap | 36 +- .../valid_syntax@expressions__set.py.snap | 54 +- .../valid_syntax@expressions__yield.py.snap | 36 +- ...lid_syntax@expressions__yield_from.py.snap | 36 +- .../valid_syntax@statement__match.py.snap | 108 +- crates/ruff_python_trivia/Cargo.toml | 2 - .../ruff_python_trivia/src/comment_ranges.rs | 155 -- crates/ruff_python_trivia/src/tokenizer.rs | 423 ----- crates/ruff_python_trivia/src/whitespace.rs | 48 - .../Cargo.toml | 24 + .../README.md | 13 + .../src/lib.rs | 1 + .../tests/block_comments.rs | 151 ++ .../tests/simple_tokenizer.rs | 417 +++++ .../snapshots/simple_tokenizer__Reverse.snap} | 2 +- ...ment_containing_single_quoted_string.snap} | 2 +- ...ment_containing_triple_quoted_string.snap} | 2 +- ...mple_tokenizer__empty_string_literal.snap} | 2 +- ..._identifier_ending_in_non_start_char.snap} | 2 +- ...identifier_starting_with_string_kind.snap} | 2 +- ...e_word_with_only_id_continuing_chars.snap} | 2 +- ..._multiline_string_containing_comment.snap} | 2 +- ...tiline_string_implicit_concatenation.snap} | 2 +- ...string_followed_by_multiple_comments.snap} | 2 +- ...ple_tokenizer__string_with_byte_kind.snap} | 2 +- ...string_with_double_escaped_backslash.snap} | 2 +- ...tokenizer__string_with_escaped_quote.snap} | 2 +- ..._tokenizer__string_with_invalid_kind.snap} | 2 +- .../simple_tokenizer__string_with_kind.snap} | 2 +- .../simple_tokenizer__tokenize_bogus.snap} | 2 +- .../simple_tokenizer__tokenize_comma.snap} | 2 +- ...ple_tokenizer__tokenize_continuation.snap} | 2 +- .../simple_tokenizer__tokenize_eq.snap} | 2 +- ...okenizer__tokenize_invalid_operators.snap} | 2 +- ...simple_tokenizer__tokenize_multichar.snap} | 2 +- .../simple_tokenizer__tokenize_not_eq.snap} | 2 +- ...simple_tokenizer__tokenize_operators.snap} | 2 +- ...mple_tokenizer__tokenize_parentheses.snap} | 2 +- .../simple_tokenizer__tokenize_slash.snap} | 2 +- ...simple_tokenizer__tokenize_substring.snap} | 2 +- .../simple_tokenizer__tokenize_trivia.snap} | 2 +- .../simple_tokenizer__tricky_unicode.snap} | 2 +- ..._multiline_string_containing_comment.snap} | 2 +- .../tests/whitespace.rs | 43 + crates/ruff_source_file/Cargo.toml | 3 +- crates/ruff_workspace/src/options.rs | 6 + playground/api/package-lock.json | 14 +- playground/api/package.json | 2 +- playground/package-lock.json | 118 +- ruff.schema.json | 858 ++-------- 158 files changed, 4137 insertions(+), 3853 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.pyi create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap create mode 100644 crates/ruff_python_ast_integration_tests/Cargo.toml create mode 100644 crates/ruff_python_ast_integration_tests/README.md create mode 100644 crates/ruff_python_ast_integration_tests/src/lib.rs rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/identifier.rs (99%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/parenthesize.rs (100%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/preorder.rs (100%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__bytes_literals.snap (67%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__class_type_parameters.snap (78%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__compare.snap (70%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__decorators.snap (73%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__dict_comprehension.snap (77%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__f_strings.snap (84%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__function_arguments.snap (88%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__function_positional_only_with_default.snap (82%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__function_type_parameters.snap (79%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__list_comprehension.snap (68%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__match_class_pattern.snap (91%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__set_comprehension.snap (68%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__string_literals.snap (67%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/preorder__type_aliases.snap (80%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__bytes_literals.snap (63%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__class_type_parameters.snap (73%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__compare.snap (67%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__decorators.snap (65%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__dict_comprehension.snap (74%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__f_strings.snap (82%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__function_arguments.snap (80%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__function_positional_only_with_default.snap (73%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__function_type_parameters.snap (75%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__list_comprehension.snap (65%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__match_class_pattern.snap (88%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__set_comprehension.snap (64%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__string_literals.snap (64%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/snapshots/visitor__type_aliases.snap (76%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/stmt_if.rs (99%) rename crates/{ruff_python_ast => ruff_python_ast_integration_tests}/tests/visitor.rs (96%) create mode 100644 crates/ruff_python_trivia_integration_tests/Cargo.toml create mode 100644 crates/ruff_python_trivia_integration_tests/README.md create mode 100644 crates/ruff_python_trivia_integration_tests/src/lib.rs create mode 100644 crates/ruff_python_trivia_integration_tests/tests/block_comments.rs create mode 100644 crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__Reverse.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__Reverse.snap} (76%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_single_quoted_string.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_single_quoted_string.snap} (81%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_triple_quoted_string.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_triple_quoted_string.snap} (81%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__empty_string_literal.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__empty_string_literal.snap} (80%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_ending_in_non_start_char.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_ending_in_non_start_char.snap} (58%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_starting_with_string_kind.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_starting_with_string_kind.snap} (76%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__ignore_word_with_only_id_continuing_chars.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__ignore_word_with_only_id_continuing_chars.snap} (69%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_containing_comment.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_containing_comment.snap} (70%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_implicit_concatenation.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_implicit_concatenation.snap} (70%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_followed_by_multiple_comments.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_followed_by_multiple_comments.snap} (81%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_byte_kind.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_byte_kind.snap} (69%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_double_escaped_backslash.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_double_escaped_backslash.snap} (81%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_escaped_quote.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_escaped_quote.snap} (81%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_invalid_kind.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_invalid_kind.snap} (75%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_kind.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_kind.snap} (69%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_bogus.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_bogus.snap} (83%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_comma.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_comma.snap} (79%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_continuation.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_continuation.snap} (85%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_eq.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_eq.snap} (69%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_invalid_operators.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_invalid_operators.snap} (80%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_multichar.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_multichar.snap} (87%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_not_eq.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_not_eq.snap} (69%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_operators.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_operators.snap} (95%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_parentheses.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_parentheses.snap} (85%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_slash.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_slash.snap} (89%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_substring.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_substring.snap} (76%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_trivia.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_trivia.snap} (80%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tricky_unicode.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tricky_unicode.snap} (58%) rename crates/{ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__triple_quoted_multiline_string_containing_comment.snap => ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__triple_quoted_multiline_string_containing_comment.snap} (70%) create mode 100644 crates/ruff_python_trivia_integration_tests/tests/whitespace.rs diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 69624dcb21d08c..259ab5c1d9509c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -27,9 +27,20 @@ // Group upload/download artifact updates, the versions are dependent groupName: "Artifact GitHub Actions dependencies", matchManagers: ["github-actions"], + matchDatasources: ["gitea-tags", "github-tags"], matchPackagePatterns: ["actions/.*-artifact"], description: "Weekly update of artifact-related GitHub Actions dependencies", }, + { + // This package rule disables updates for GitHub runners: + // we'd only pin them to a specific version + // if there was a deliberate reason to do so + groupName: "GitHub runners", + matchManagers: ["github-actions"], + matchDatasources: ["github-runners"], + description: "Disable PRs updating GitHub runners (e.g. 'runs-on: macos-14')", + enabled: false, + }, { groupName: "pre-commit dependencies", matchManagers: ["pre-commit"], diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6a49f7b657ba9f..de3816484439f5 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -47,7 +47,7 @@ jobs: run: mkdocs build --strict -f mkdocs.public.yml - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@v3.4.1 + uses: cloudflare/wrangler-action@v3.5.0 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} diff --git a/.github/workflows/playground.yaml b/.github/workflows/playground.yaml index f1f381361f60b6..96bf4788f4bcdc 100644 --- a/.github/workflows/playground.yaml +++ b/.github/workflows/playground.yaml @@ -40,7 +40,7 @@ jobs: working-directory: playground - name: "Deploy to Cloudflare Pages" if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }} - uses: cloudflare/wrangler-action@v3.4.1 + uses: cloudflare/wrangler-action@v3.5.0 with: apiToken: ${{ secrets.CF_API_TOKEN }} accountId: ${{ secrets.CF_ACCOUNT_ID }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 032a67f4850fd9..10088688d31f94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: )$ - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.39.0 + rev: v0.40.0 hooks: - id: markdownlint-fix exclude: | @@ -41,7 +41,7 @@ repos: )$ - repo: https://github.com/crate-ci/typos - rev: v1.20.10 + rev: v1.21.0 hooks: - id: typos @@ -55,7 +55,7 @@ repos: pass_filenames: false # This makes it a lot faster - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.2 + rev: v0.4.3 hooks: - id: ruff-format - id: ruff diff --git a/Cargo.lock b/Cargo.lock index 0a94b85a3f5d5a..aba89c15987357 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,9 +1208,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libcst" @@ -2204,7 +2204,6 @@ dependencies = [ "is-macro", "itertools 0.12.1", "once_cell", - "ruff_python_parser", "ruff_python_trivia", "ruff_source_file", "ruff_text_size", @@ -2212,6 +2211,17 @@ dependencies = [ "serde", ] +[[package]] +name = "ruff_python_ast_integration_tests" +version = "0.0.0" +dependencies = [ + "insta", + "ruff_python_ast", + "ruff_python_parser", + "ruff_python_trivia", + "ruff_text_size", +] + [[package]] name = "ruff_python_codegen" version = "0.0.0" @@ -2340,11 +2350,21 @@ version = "0.0.0" dependencies = [ "insta", "itertools 0.12.1", + "ruff_source_file", + "ruff_text_size", + "unicode-ident", +] + +[[package]] +name = "ruff_python_trivia_integration_tests" +version = "0.0.0" +dependencies = [ + "insta", "ruff_python_index", "ruff_python_parser", + "ruff_python_trivia", "ruff_source_file", "ruff_text_size", - "unicode-ident", ] [[package]] @@ -2582,9 +2602,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -2602,9 +2622,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", diff --git a/_typos.toml b/_typos.toml index 8fb4dcfeb016f8..f6733f2bf920fc 100644 --- a/_typos.toml +++ b/_typos.toml @@ -11,6 +11,7 @@ ned = "ned" pn = "pn" # `import panel as pd` is a thing poit = "poit" BA = "BA" # acronym for "Bad Allowed", used in testing. +jod = "jod" # e.g., `jod-thread` [default] extend-ignore-re = [ diff --git a/clippy.toml b/clippy.toml index 28b63ab0d25c20..6cacb00103ac16 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,12 +1,13 @@ doc-valid-idents = [ - "StackOverflow", + "..", "CodeQL", + "FastAPI", "IPython", - "NumPy", + "LangChain", "LibCST", + "McCabe", + "NumPy", "SCREAMING_SNAKE_CASE", "SQLAlchemy", - "McCabe", - "FastAPI", - "..", + "StackOverflow", ] diff --git a/crates/red_knot/src/db/jars.rs b/crates/red_knot/src/db/jars.rs index f67b7cd651a103..7fd24e4dd3af11 100644 --- a/crates/red_knot/src/db/jars.rs +++ b/crates/red_knot/src/db/jars.rs @@ -10,7 +10,7 @@ use crate::db::query::QueryResult; /// `Database` trait and the jar allows to write queries in isolation without having to know how they get composed at the upper levels. /// /// Salsa further defines a `HasIngredient` trait which slices the jar to a specific storage (e.g. a specific cache). -/// We don't need this just jet because we write our queries by hand. We may want a similar trait if we decide +/// We don't need this just yet because we write our queries by hand. We may want a similar trait if we decide /// to use a macro to generate the queries. pub trait HasJar { /// Gives a read-only reference to the jar. diff --git a/crates/red_knot/src/hir.rs b/crates/red_knot/src/hir.rs index 030c7353d057d9..5b7eeeafdf149a 100644 --- a/crates/red_knot/src/hir.rs +++ b/crates/red_knot/src/hir.rs @@ -1,6 +1,6 @@ //! Key observations //! -//! The HIR avoids allocations to large extends by: +//! The HIR (High-Level Intermediate Representation) avoids allocations to large extends by: //! * Using an arena per node type //! * using ids and id ranges to reference items. //! diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_blind_except/BLE.py b/crates/ruff_linter/resources/test/fixtures/flake8_blind_except/BLE.py index 10cca835ccaff4..8282bd105911dc 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_blind_except/BLE.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_blind_except/BLE.py @@ -129,3 +129,12 @@ ... except Exception as e: raise ValueError from e + + +try: + pass +except Exception: + if True: + exception("An error occurred") + else: + exception("An error occurred") diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py index 55d32aa1bad6eb..bbca28a563da2e 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B019.py @@ -106,3 +106,15 @@ def called_lru_cached_instance_method(self, y): @lru_cache() def another_called_lru_cached_instance_method(self, y): ... + + +import enum + + +class Foo(enum.Enum): + ONE = enum.auto() + TWO = enum.auto() + + @functools.cache + def bar(self, arg: str) -> str: + return f"{self} - {arg}" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py new file mode 100644 index 00000000000000..0e4d877d4b485d --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.py @@ -0,0 +1,54 @@ +from typing import Container, Generic, Iterable, List, Sized, Tuple, TypeVar +import typing as t + +T = TypeVar('T') +K = TypeVar('K') +V = TypeVar('V') + +class LinkedList(Generic[T], Sized): # PYI059 + def __init__(self) -> None: + self._items: List[T] = [] + + def push(self, item: T) -> None: + self._items.append(item) + +class MyMapping( # PYI059 + t.Generic[K, V], + Iterable[Tuple[K, V]], + Container[Tuple[K, V]], +): + ... + + +# Inheriting from just `Generic` is a TypeError, but it's probably fine +# to flag this issue in this case as well, since after fixing the error +# the Generic's position issue persists. +class Foo(Generic, LinkedList): # PYI059 + pass + + +class Foo( # comment about the bracket + # Part 1 of multiline comment 1 + # Part 2 of multiline comment 1 + Generic[T] # comment about Generic[T] # PYI059 + # another comment? + , # comment about the comma? + # part 1 of multiline comment 2 + # part 2 of multiline comment 2 + int, # comment about int + # yet another comment? +): # and another one for good measure + ... + + +# in case of multiple Generic[] inheritance, don't fix it. +class C(Generic[T], Generic[K, V]): ... # PYI059 + + +# Negative cases +class MyList(Sized, Generic[T]): # Generic already in last place + def __init__(self) -> None: + self._items: List[T] = [] + +class SomeGeneric(Generic[T]): # Only one generic + pass diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.pyi new file mode 100644 index 00000000000000..1e0cbd14cbd316 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI059.pyi @@ -0,0 +1,48 @@ +from typing import Container, Generic, Iterable, Sized, Tuple, TypeVar +import typing as t + +T = TypeVar('T') +K = TypeVar('K') +V = TypeVar('V') + +class LinkedList(Generic[T], Sized): # PYI059 + def __init__(self) -> None: ... + def push(self, item: T) -> None: ... + +class MyMapping( # PYI059 + t.Generic[K, V], + Iterable[Tuple[K, V]], + Container[Tuple[K, V]], +): + ... + +# Inheriting from just `Generic` is a TypeError, but it's probably fine +# to flag this issue in this case as well, since after fixing the error +# the Generic's position issue persists. +class Foo(Generic, LinkedList): ... # PYI059 + + +class Foo( # comment about the bracket + # Part 1 of multiline comment 1 + # Part 2 of multiline comment 1 + Generic[T] # comment about Generic[T] # PYI059 + # another comment? + , # comment about the comma? + # part 1 of multiline comment 2 + # part 2 of multiline comment 2 + int, # comment about int + # yet another comment? +): # and another one for good measure + ... + + +# in case of multiple Generic[] inheritance, don't fix it. +class C(Generic[T], Generic[K, V]): ... # PYI059 + + +# Negative cases +class MyList(Sized, Generic[T]): # Generic already in last place + def __init__(self) -> None: ... + +class SomeGeneric(Generic[T]): # Only one generic + ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index a0462617256216..cc9fd59ed2b9f7 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -203,7 +203,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } if checker.enabled(Rule::CachedInstanceMethod) { - flake8_bugbear::rules::cached_instance_method(checker, decorator_list); + flake8_bugbear::rules::cached_instance_method(checker, function_def); } if checker.enabled(Rule::MutableArgumentDefault) { flake8_bugbear::rules::mutable_argument_default(checker, function_def); @@ -478,6 +478,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::EllipsisInNonEmptyClassBody) { flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, body); } + if checker.enabled(Rule::GenericNotLastBaseClass) { + flake8_pyi::rules::generic_not_last_base_class(checker, class_def); + } if checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle) { flake8_pytest_style::rules::marks(checker, decorator_list); } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 566924b532fff1..259eda1bde39b8 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1172,8 +1172,8 @@ impl<'a> Visitor<'a> for Checker<'a> { let Keyword { arg, value, .. } = keyword; match (arg.as_ref(), value) { // Ex) NamedTuple("a", **{"a": int}) - (None, Expr::Dict(ast::ExprDict { keys, values, .. })) => { - for (key, value) in keys.iter().zip(values) { + (None, Expr::Dict(ast::ExprDict { items, .. })) => { + for ast::DictItem { key, value } in items { if let Some(key) = key.as_ref() { self.visit_non_type_definition(key); self.visit_type_definition(value); @@ -1200,16 +1200,11 @@ impl<'a> Visitor<'a> for Checker<'a> { self.visit_non_type_definition(arg); } for arg in args { - if let Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) = arg - { - for key in keys.iter().flatten() { - self.visit_non_type_definition(key); - } - for value in values { + if let Expr::Dict(ast::ExprDict { items, range: _ }) = arg { + for ast::DictItem { key, value } in items { + if let Some(key) = key { + self.visit_non_type_definition(key); + } self.visit_type_definition(value); } } else { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index be1aa79edf2ef9..05906db3c906b0 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -808,6 +808,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Pyi, "055") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnnecessaryTypeUnion), (Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll), (Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod), + (Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass), (Flake8Pyi, "062") => (RuleGroup::Preview, rules::flake8_pyi::rules::DuplicateLiteralMember), // flake8-pytest-style diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs index 34ceaf90708309..258b2381fad67a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_extra.rs @@ -54,11 +54,12 @@ fn is_call_insecure(call: &ast::ExprCall) -> bool { if let Some(argument) = call.arguments.find_argument(argument_name, position) { match argument_name { "select" => match argument { - Expr::Dict(ExprDict { keys, values, .. }) => { - if !keys.iter().flatten().all(Expr::is_string_literal_expr) { - return true; - } - if !values.iter().all(Expr::is_string_literal_expr) { + Expr::Dict(ExprDict { items, .. }) => { + if items.iter().any(|ast::DictItem { key, value }| { + key.as_ref() + .is_some_and(|key| !key.is_string_literal_expr()) + || !value.is_string_literal_expr() + }) { return true; } } diff --git a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs index be2262c130f307..35945d9c1d84ce 100644 --- a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs +++ b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs @@ -1,8 +1,10 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::is_const_true; +use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor}; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::analyze::logging; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -87,94 +89,143 @@ pub(crate) fn blind_except( if !matches!(builtin_exception_type, "BaseException" | "Exception") { return; } + // If the exception is re-raised, don't flag an error. - if body.iter().any(|stmt| { - if let Stmt::Raise(ast::StmtRaise { exc, cause, .. }) = stmt { - if let Some(cause) = cause { - if let Expr::Name(ast::ExprName { id, .. }) = cause.as_ref() { - name.is_some_and(|name| id == name) + let mut visitor = ReraiseVisitor::new(name); + visitor.visit_body(body); + if visitor.seen() { + return; + } + + // If the exception is logged, don't flag an error. + let mut visitor = LogExceptionVisitor::new(semantic, &checker.settings.logger_objects); + visitor.visit_body(body); + if visitor.seen() { + return; + } + + checker.diagnostics.push(Diagnostic::new( + BlindExcept { + name: builtin_exception_type.to_string(), + }, + type_.range(), + )); +} + +/// A visitor to detect whether the exception with the given name was re-raised. +struct ReraiseVisitor<'a> { + name: Option<&'a str>, + seen: bool, +} + +impl<'a> ReraiseVisitor<'a> { + /// Create a new [`ReraiseVisitor`] with the given exception name. + fn new(name: Option<&'a str>) -> Self { + Self { name, seen: false } + } + + /// Returns `true` if the exception was re-raised. + fn seen(&self) -> bool { + self.seen + } +} + +impl<'a> StatementVisitor<'a> for ReraiseVisitor<'a> { + fn visit_stmt(&mut self, stmt: &'a Stmt) { + match stmt { + Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => { + if let Some(cause) = cause { + if let Expr::Name(ast::ExprName { id, .. }) = cause.as_ref() { + if self.name.is_some_and(|name| id == name) { + self.seen = true; + } + } } else { - false - } - } else { - if let Some(exc) = exc { - if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() { - name.is_some_and(|name| id == name) + if let Some(exc) = exc { + if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() { + if self.name.is_some_and(|name| id == name) { + self.seen = true; + } + } } else { - false + self.seen = true; } - } else { - true } } - } else { - false + Stmt::Try(_) | Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {} + _ => walk_stmt(self, stmt), } - }) { - return; } +} - // If the exception is logged, don't flag an error. - if body.iter().any(|stmt| { - if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt { - if let Expr::Call(ast::ExprCall { - func, arguments, .. - }) = value.as_ref() - { - match func.as_ref() { - Expr::Attribute(ast::ExprAttribute { attr, .. }) => { - if logging::is_logger_candidate( - func, - semantic, - &checker.settings.logger_objects, - ) { - match attr.as_str() { - "exception" => return true, - "error" => { - if let Some(keyword) = arguments.find_keyword("exc_info") { - if is_const_true(&keyword.value) { - return true; - } - } +/// A visitor to detect whether the exception was logged. +struct LogExceptionVisitor<'a> { + semantic: &'a SemanticModel<'a>, + logger_objects: &'a [String], + seen: bool, +} + +impl<'a> LogExceptionVisitor<'a> { + /// Create a new [`LogExceptionVisitor`] with the given exception name. + fn new(semantic: &'a SemanticModel<'a>, logger_objects: &'a [String]) -> Self { + Self { + semantic, + logger_objects, + seen: false, + } + } + + /// Returns `true` if the exception was logged. + fn seen(&self) -> bool { + self.seen + } +} + +impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> { + fn visit_stmt(&mut self, stmt: &'a Stmt) { + match stmt { + Stmt::Expr(ast::StmtExpr { value, .. }) => { + if let Expr::Call(ast::ExprCall { + func, arguments, .. + }) = value.as_ref() + { + match func.as_ref() { + Expr::Attribute(ast::ExprAttribute { attr, .. }) => { + if logging::is_logger_candidate( + func, + self.semantic, + self.logger_objects, + ) { + if match attr.as_str() { + "exception" => true, + "error" => arguments + .find_keyword("exc_info") + .is_some_and(|keyword| is_const_true(&keyword.value)), + _ => false, + } { + self.seen = true; } - _ => {} } } - } - Expr::Name(ast::ExprName { .. }) => { - if semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| match qualified_name.segments() { - ["logging", "exception"] => true, - ["logging", "error"] => { - if let Some(keyword) = arguments.find_keyword("exc_info") { - if is_const_true(&keyword.value) { - return true; - } - } - false - } - _ => false, - }) - { - return true; + Expr::Name(ast::ExprName { .. }) => { + if self.semantic.resolve_qualified_name(func).is_some_and( + |qualified_name| match qualified_name.segments() { + ["logging", "exception"] => true, + ["logging", "error"] => arguments + .find_keyword("exc_info") + .is_some_and(|keyword| is_const_true(&keyword.value)), + _ => false, + }, + ) { + self.seen = true; + } } - } - _ => { - return false; + _ => {} } } } + Stmt::Try(_) | Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {} + _ => walk_stmt(self, stmt), } - false - }) { - return; } - - checker.diagnostics.push(Diagnostic::new( - BlindExcept { - name: builtin_exception_type.to_string(), - }, - type_.range(), - )); } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 016bc6bac54121..0ebdec98d4a372 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -1,8 +1,9 @@ -use ruff_python_ast::{self as ast, Decorator, Expr}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_semantic::SemanticModel; +use ruff_python_ast::helpers::map_callable; +use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::analyze::{class, function_type}; +use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -20,6 +21,9 @@ use crate::checkers::ast::Checker; /// instance of the class, or use the `@lru_cache` decorator on a function /// outside of the class. /// +/// This rule ignores instance methods on enumeration classes, as enum members +/// are singletons. +/// /// ## Example /// ```python /// from functools import lru_cache @@ -70,42 +74,50 @@ impl Violation for CachedInstanceMethod { } } -fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(expr) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["functools", "lru_cache" | "cache"] - ) - }) -} - /// B019 -pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) { - if !checker.semantic().current_scope().kind.is_class() { +pub(crate) fn cached_instance_method(checker: &mut Checker, function_def: &ast::StmtFunctionDef) { + let scope = checker.semantic().current_scope(); + + // Parent scope _must_ be a class. + let ScopeKind::Class(class_def) = scope.kind else { + return; + }; + + // The function must be an _instance_ method. + let type_ = function_type::classify( + &function_def.name, + &function_def.decorator_list, + scope, + checker.semantic(), + &checker.settings.pep8_naming.classmethod_decorators, + &checker.settings.pep8_naming.staticmethod_decorators, + ); + if !matches!(type_, function_type::FunctionType::Method) { return; } - for decorator in decorator_list { - // TODO(charlie): This should take into account `classmethod-decorators` and - // `staticmethod-decorators`. - if let Expr::Name(ast::ExprName { id, .. }) = &decorator.expression { - if id == "classmethod" || id == "staticmethod" { + + for decorator in &function_def.decorator_list { + if is_cache_func(map_callable(&decorator.expression), checker.semantic()) { + // If we found a cached instance method, validate (lazily) that the class is not an enum. + if class::is_enumeration(class_def, checker.semantic()) { return; } - } - } - for decorator in decorator_list { - if is_cache_func( - match &decorator.expression { - Expr::Call(ast::ExprCall { func, .. }) => func, - _ => &decorator.expression, - }, - checker.semantic(), - ) { + checker .diagnostics .push(Diagnostic::new(CachedInstanceMethod, decorator.range())); } } } + +/// Returns `true` if the given expression is a call to `functools.lru_cache` or `functools.cache`. +fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(expr) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + ["functools", "lru_cache" | "cache"] + ) + }) +} diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs index 2f6801f9f922eb..9b7c4ac0e8fd56 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs @@ -89,18 +89,19 @@ fn check_msg(checker: &mut Checker, msg: &Expr) { /// Check contents of the `extra` argument to logging calls. fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) { match &extra.value { - Expr::Dict(ast::ExprDict { keys, .. }) => { - for key in keys { - if let Some(key) = &key { - if let Expr::StringLiteral(ast::ExprStringLiteral { value: attr, .. }) = key { - if is_reserved_attr(attr.to_str()) { - checker.diagnostics.push(Diagnostic::new( - LoggingExtraAttrClash(attr.to_string()), - key.range(), - )); - } - } + Expr::Dict(dict) => { + for invalid_key in dict.iter_keys().filter_map(|key| { + let string_key = key?.as_string_literal_expr()?; + if is_reserved_attr(string_key.value.to_str()) { + Some(string_key) + } else { + None } + }) { + checker.diagnostics.push(Diagnostic::new( + LoggingExtraAttrClash(invalid_key.value.to_string()), + invalid_key.range(), + )); } } Expr::Call(ast::ExprCall { diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs index 9c351846451ec5..730bb8415804cf 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/reimplemented_container_builtin.rs @@ -71,7 +71,7 @@ pub(crate) fn reimplemented_container_builtin(checker: &mut Checker, expr: &Expr let container = match &**body { Expr::List(ast::ExprList { elts, .. }) if elts.is_empty() => Container::List, - Expr::Dict(ast::ExprDict { values, .. }) if values.is_empty() => Container::Dict, + Expr::Dict(ast::ExprDict { items, .. }) if items.is_empty() => Container::Dict, _ => return, }; let mut diagnostic = Diagnostic::new(ReimplementedContainerBuiltin { container }, expr.range()); diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs index 0d462c17482557..81f34b0b18710d 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_dict_kwargs.rs @@ -65,36 +65,36 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal continue; } - let Expr::Dict(ast::ExprDict { keys, values, .. }) = &keyword.value else { + let Expr::Dict(dict) = &keyword.value else { continue; }; // Ex) `foo(**{**bar})` - if matches!(keys.as_slice(), [None]) { - let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); - - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - format!("**{}", checker.locator().slice(values[0].range())), + if let [ast::DictItem { key: None, value }] = dict.items.as_slice() { + let diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); + let edit = Edit::range_replacement( + format!("**{}", checker.locator().slice(value)), keyword.range(), - ))); - - checker.diagnostics.push(diagnostic); + ); + checker + .diagnostics + .push(diagnostic.with_fix(Fix::safe_edit(edit))); continue; } // Ensure that every keyword is a valid keyword argument (e.g., avoid errors for cases like // `foo(**{"bar-bar": 1})`). - let kwargs = keys - .iter() - .filter_map(|key| key.as_ref().and_then(as_kwarg)) - .collect::>(); - if kwargs.len() != keys.len() { + let kwargs: Vec<&str> = dict + .iter_keys() + .filter_map(|key| key.and_then(as_kwarg)) + .collect(); + if kwargs.len() != dict.items.len() { continue; } let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range()); - if values.is_empty() { + if dict.items.is_empty() { diagnostic.try_set_fix(|| { remove_argument( keyword, @@ -119,7 +119,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( kwargs .iter() - .zip(values.iter()) + .zip(dict.iter_values()) .map(|(kwarg, value)| { format!("{}={}", kwarg, checker.locator().slice(value.range())) }) @@ -150,9 +150,9 @@ fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> { if !seen.insert(name.as_str()) { duplicates.insert(name.as_str()); } - } else if let Expr::Dict(ast::ExprDict { keys, .. }) = &keyword.value { - for key in keys { - if let Some(name) = key.as_ref().and_then(as_kwarg) { + } else if let Expr::Dict(dict) = &keyword.value { + for key in dict.iter_keys() { + if let Some(name) = key.and_then(as_kwarg) { if !seen.insert(name) { duplicates.insert(name); } diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs index aa81145bd872ff..0fd2a96c5f7dd8 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs @@ -49,8 +49,8 @@ impl Violation for UnnecessarySpread { pub(crate) fn unnecessary_spread(checker: &mut Checker, dict: &ast::ExprDict) { // The first "end" is the start of the dictionary, immediately following the open bracket. let mut prev_end = dict.start() + TextSize::from(1); - for item in dict.keys.iter().zip(dict.values.iter()) { - if let (None, value) = item { + for ast::DictItem { key, value } in &dict.items { + if key.is_none() { // We only care about when the key is None which indicates a spread `**` // inside a dict. if let Expr::Dict(inner) = value { @@ -61,7 +61,7 @@ pub(crate) fn unnecessary_spread(checker: &mut Checker, dict: &ast::ExprDict) { checker.diagnostics.push(diagnostic); } } - prev_end = item.1.end(); + prev_end = value.end(); } } @@ -75,7 +75,7 @@ fn unnecessary_spread_fix( let doublestar = SimpleTokenizer::starts_at(prev_end, locator.contents()) .find(|tok| matches!(tok.kind(), SimpleTokenKind::DoubleStar))?; - if let Some(last) = dict.values.last() { + if let Some(last) = dict.iter_values().last() { // Ex) `**{a: 1, b: 2}` let mut edits = vec![]; for tok in SimpleTokenizer::starts_at(last.end(), locator.contents()).skip_trivia() { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 817af3f837fa48..e69606adfbd5c3 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -43,6 +43,8 @@ mod tests { #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))] #[test_case(Rule::GeneratorReturnFromIterMethod, Path::new("PYI058.py"))] #[test_case(Rule::GeneratorReturnFromIterMethod, Path::new("PYI058.pyi"))] + #[test_case(Rule::GenericNotLastBaseClass, Path::new("PYI059.py"))] + #[test_case(Rule::GenericNotLastBaseClass, Path::new("PYI059.pyi"))] #[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.py"))] #[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.pyi"))] #[test_case(Rule::NoReturnArgumentAnnotationInStub, Path::new("PYI050.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs new file mode 100644 index 00000000000000..c08f74870a867f --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/generic_not_last_base_class.rs @@ -0,0 +1,122 @@ +use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, helpers::map_subscript}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::fix::edits::{add_argument, remove_argument, Parentheses}; + +/// ## What it does +/// Checks for classes inheriting from `typing.Generic[]` where `Generic[]` is +/// not the last base class in the bases tuple. +/// +/// ## Why is this bad? +/// If `Generic[]` is not the final class in the bases tuple, unexpected +/// behaviour can occur at runtime (See [this CPython issue][1] for an example). +/// The rule is also applied to stub files, but, unlike at runtime, +/// in stubs it is purely enforced for stylistic consistency. +/// +/// For example: +/// ```python +/// class LinkedList(Generic[T], Sized): +/// def push(self, item: T) -> None: +/// self._items.append(item) +/// +/// class MyMapping( +/// Generic[K, V], +/// Iterable[Tuple[K, V]], +/// Container[Tuple[K, V]], +/// ): +/// ... +/// ``` +/// +/// Use instead: +/// ```python +/// class LinkedList(Sized, Generic[T]): +/// def push(self, item: T) -> None: +/// self._items.append(item) +/// +/// class MyMapping( +/// Iterable[Tuple[K, V]], +/// Container[Tuple[K, V]], +/// Generic[K, V], +/// ): +/// ... +/// ``` +/// ## References +/// - [`typing.Generic` documentation](https://docs.python.org/3/library/typing.html#typing.Generic) +/// +/// [1]: https://github.com/python/cpython/issues/106102 +#[violation] +pub struct GenericNotLastBaseClass; + +impl Violation for GenericNotLastBaseClass { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + format!("`Generic[]` should always be the last base class") + } + + fn fix_title(&self) -> Option { + Some("Move `Generic[]` to the end".to_string()) + } +} + +/// PYI059 +pub(crate) fn generic_not_last_base_class(checker: &mut Checker, class_def: &ast::StmtClassDef) { + let Some(bases) = class_def.arguments.as_deref() else { + return; + }; + + let semantic = checker.semantic(); + if !semantic.seen_typing() { + return; + } + + let Some(last_base) = bases.args.last() else { + return; + }; + + let mut generic_base_iter = bases + .args + .iter() + .filter(|base| semantic.match_typing_expr(map_subscript(base), "Generic")); + + let Some(generic_base) = generic_base_iter.next() else { + return; + }; + + // If `Generic[]` exists, but is the last base, don't emit a diagnostic. + if generic_base.range() == last_base.range() { + return; + } + + let mut diagnostic = Diagnostic::new(GenericNotLastBaseClass, bases.range()); + + // No fix if multiple `Generic[]`s are seen in the class bases. + if generic_base_iter.next().is_none() { + diagnostic.try_set_fix(|| generate_fix(generic_base, bases, checker)); + } + + checker.diagnostics.push(diagnostic); +} + +fn generate_fix( + generic_base: &ast::Expr, + arguments: &ast::Arguments, + checker: &Checker, +) -> anyhow::Result { + let locator = checker.locator(); + let source = locator.contents(); + + let deletion = remove_argument(generic_base, arguments, Parentheses::Preserve, source)?; + let insertion = add_argument( + locator.slice(generic_base), + arguments, + checker.indexer().comment_ranges(), + source, + ); + + Ok(Fix::safe_edits(deletion, [insertion])) +} diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs index 84c54f06376d28..18b50b8e784083 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/mod.rs @@ -11,6 +11,7 @@ pub(crate) use duplicate_union_member::*; pub(crate) use ellipsis_in_non_empty_class_body::*; pub(crate) use exit_annotations::*; pub(crate) use future_annotations_in_stub::*; +pub(crate) use generic_not_last_base_class::*; pub(crate) use iter_method_return_iterable::*; pub(crate) use no_return_argument_annotation::*; pub(crate) use non_empty_stub_body::*; @@ -50,6 +51,7 @@ mod duplicate_union_member; mod ellipsis_in_non_empty_class_body; mod exit_annotations; mod future_annotations_in_stub; +mod generic_not_last_base_class; mod iter_method_return_iterable; mod no_return_argument_annotation; mod non_empty_stub_body; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index 65bd78fad1faef..2d66fc868df40d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -298,17 +298,13 @@ fn is_valid_default_value_with_annotation( .iter() .all(|e| is_valid_default_value_with_annotation(e, false, locator, semantic)); } - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => { + Expr::Dict(ast::ExprDict { items, range: _ }) => { return allow_container - && keys.len() <= 10 - && keys.iter().zip(values).all(|(k, v)| { - k.as_ref().is_some_and(|k| { - is_valid_default_value_with_annotation(k, false, locator, semantic) - }) && is_valid_default_value_with_annotation(v, false, locator, semantic) + && items.len() <= 10 + && items.iter().all(|ast::DictItem { key, value }| { + key.as_ref().is_some_and(|key| { + is_valid_default_value_with_annotation(key, false, locator, semantic) + }) && is_valid_default_value_with_annotation(value, false, locator, semantic) }); } Expr::UnaryOp(ast::ExprUnaryOp { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap new file mode 100644 index 00000000000000..bcb28618ff3c85 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.py.snap @@ -0,0 +1,113 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI059.py:8:17: PYI059 [*] `Generic[]` should always be the last base class + | + 6 | V = TypeVar('V') + 7 | + 8 | class LinkedList(Generic[T], Sized): # PYI059 + | ^^^^^^^^^^^^^^^^^^^ PYI059 + 9 | def __init__(self) -> None: +10 | self._items: List[T] = [] + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +5 5 | K = TypeVar('K') +6 6 | V = TypeVar('V') +7 7 | +8 |-class LinkedList(Generic[T], Sized): # PYI059 + 8 |+class LinkedList(Sized, Generic[T]): # PYI059 +9 9 | def __init__(self) -> None: +10 10 | self._items: List[T] = [] +11 11 | + +PYI059.py:15:16: PYI059 [*] `Generic[]` should always be the last base class + | +13 | self._items.append(item) +14 | +15 | class MyMapping( # PYI059 + | ________________^ +16 | | t.Generic[K, V], +17 | | Iterable[Tuple[K, V]], +18 | | Container[Tuple[K, V]], +19 | | ): + | |_^ PYI059 +20 | ... + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +13 13 | self._items.append(item) +14 14 | +15 15 | class MyMapping( # PYI059 +16 |- t.Generic[K, V], +17 16 | Iterable[Tuple[K, V]], +18 |- Container[Tuple[K, V]], + 17 |+ Container[Tuple[K, V]], t.Generic[K, V], +19 18 | ): +20 19 | ... +21 20 | + +PYI059.py:26:10: PYI059 [*] `Generic[]` should always be the last base class + | +24 | # to flag this issue in this case as well, since after fixing the error +25 | # the Generic's position issue persists. +26 | class Foo(Generic, LinkedList): # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^ PYI059 +27 | pass + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +23 23 | # Inheriting from just `Generic` is a TypeError, but it's probably fine +24 24 | # to flag this issue in this case as well, since after fixing the error +25 25 | # the Generic's position issue persists. +26 |-class Foo(Generic, LinkedList): # PYI059 + 26 |+class Foo(LinkedList, Generic): # PYI059 +27 27 | pass +28 28 | +29 29 | + +PYI059.py:30:10: PYI059 [*] `Generic[]` should always be the last base class + | +30 | class Foo( # comment about the bracket + | __________^ +31 | | # Part 1 of multiline comment 1 +32 | | # Part 2 of multiline comment 1 +33 | | Generic[T] # comment about Generic[T] # PYI059 +34 | | # another comment? +35 | | , # comment about the comma? +36 | | # part 1 of multiline comment 2 +37 | | # part 2 of multiline comment 2 +38 | | int, # comment about int +39 | | # yet another comment? +40 | | ): # and another one for good measure + | |_^ PYI059 +41 | ... + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +30 30 | class Foo( # comment about the bracket +31 31 | # Part 1 of multiline comment 1 +32 32 | # Part 2 of multiline comment 1 +33 |- Generic[T] # comment about Generic[T] # PYI059 +34 |- # another comment? +35 |- , # comment about the comma? + 33 |+ # comment about the comma? +36 34 | # part 1 of multiline comment 2 +37 35 | # part 2 of multiline comment 2 +38 |- int, # comment about int + 36 |+ int, Generic[T], # comment about int +39 37 | # yet another comment? +40 38 | ): # and another one for good measure +41 39 | ... + +PYI059.py:45:8: PYI059 `Generic[]` should always be the last base class + | +44 | # in case of multiple Generic[] inheritance, don't fix it. +45 | class C(Generic[T], Generic[K, V]): ... # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 + | + = help: Move `Generic[]` to the end diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap new file mode 100644 index 00000000000000..eec9fa9f22c4c6 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI059_PYI059.pyi.snap @@ -0,0 +1,112 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI059.pyi:8:17: PYI059 [*] `Generic[]` should always be the last base class + | + 6 | V = TypeVar('V') + 7 | + 8 | class LinkedList(Generic[T], Sized): # PYI059 + | ^^^^^^^^^^^^^^^^^^^ PYI059 + 9 | def __init__(self) -> None: ... +10 | def push(self, item: T) -> None: ... + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +5 5 | K = TypeVar('K') +6 6 | V = TypeVar('V') +7 7 | +8 |-class LinkedList(Generic[T], Sized): # PYI059 + 8 |+class LinkedList(Sized, Generic[T]): # PYI059 +9 9 | def __init__(self) -> None: ... +10 10 | def push(self, item: T) -> None: ... +11 11 | + +PYI059.pyi:12:16: PYI059 [*] `Generic[]` should always be the last base class + | +10 | def push(self, item: T) -> None: ... +11 | +12 | class MyMapping( # PYI059 + | ________________^ +13 | | t.Generic[K, V], +14 | | Iterable[Tuple[K, V]], +15 | | Container[Tuple[K, V]], +16 | | ): + | |_^ PYI059 +17 | ... + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +10 10 | def push(self, item: T) -> None: ... +11 11 | +12 12 | class MyMapping( # PYI059 +13 |- t.Generic[K, V], +14 13 | Iterable[Tuple[K, V]], +15 |- Container[Tuple[K, V]], + 14 |+ Container[Tuple[K, V]], t.Generic[K, V], +16 15 | ): +17 16 | ... +18 17 | + +PYI059.pyi:22:10: PYI059 [*] `Generic[]` should always be the last base class + | +20 | # to flag this issue in this case as well, since after fixing the error +21 | # the Generic's position issue persists. +22 | class Foo(Generic, LinkedList): ... # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^ PYI059 + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +19 19 | # Inheriting from just `Generic` is a TypeError, but it's probably fine +20 20 | # to flag this issue in this case as well, since after fixing the error +21 21 | # the Generic's position issue persists. +22 |-class Foo(Generic, LinkedList): ... # PYI059 + 22 |+class Foo(LinkedList, Generic): ... # PYI059 +23 23 | +24 24 | +25 25 | class Foo( # comment about the bracket + +PYI059.pyi:25:10: PYI059 [*] `Generic[]` should always be the last base class + | +25 | class Foo( # comment about the bracket + | __________^ +26 | | # Part 1 of multiline comment 1 +27 | | # Part 2 of multiline comment 1 +28 | | Generic[T] # comment about Generic[T] # PYI059 +29 | | # another comment? +30 | | , # comment about the comma? +31 | | # part 1 of multiline comment 2 +32 | | # part 2 of multiline comment 2 +33 | | int, # comment about int +34 | | # yet another comment? +35 | | ): # and another one for good measure + | |_^ PYI059 +36 | ... + | + = help: Move `Generic[]` to the end + +ℹ Safe fix +25 25 | class Foo( # comment about the bracket +26 26 | # Part 1 of multiline comment 1 +27 27 | # Part 2 of multiline comment 1 +28 |- Generic[T] # comment about Generic[T] # PYI059 +29 |- # another comment? +30 |- , # comment about the comma? + 28 |+ # comment about the comma? +31 29 | # part 1 of multiline comment 2 +32 30 | # part 2 of multiline comment 2 +33 |- int, # comment about int + 31 |+ int, Generic[T], # comment about int +34 32 | # yet another comment? +35 33 | ): # and another one for good measure +36 34 | ... + +PYI059.pyi:40:8: PYI059 `Generic[]` should always be the last base class + | +39 | # in case of multiple Generic[] inheritance, don't fix it. +40 | class C(Generic[T], Generic[K, V]): ... # PYI059 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI059 + | + = help: Move `Generic[]` to the end diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs index 1dfbeb35597ac6..f8f88b1e050e2f 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs @@ -113,8 +113,8 @@ impl ConstantLikelihood { .map(|expr| ConstantLikelihood::from_expression(expr, preview)) .min() .unwrap_or(ConstantLikelihood::Definitely), - Expr::Dict(ast::ExprDict { values: vs, .. }) if preview.is_enabled() => { - if vs.is_empty() { + Expr::Dict(ast::ExprDict { items, .. }) if preview.is_enabled() => { + if items.is_empty() { ConstantLikelihood::Definitely } else { ConstantLikelihood::Probably diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs index 963ddd73925a9e..5575e15410f316 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/repeated_keys.rs @@ -132,10 +132,10 @@ impl Violation for MultiValueRepeatedKeyVariable { pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) { // Generate a map from key to (index, value). let mut seen: FxHashMap> = - FxHashMap::with_capacity_and_hasher(dict.keys.len(), BuildHasherDefault::default()); + FxHashMap::with_capacity_and_hasher(dict.items.len(), BuildHasherDefault::default()); // Detect duplicate keys. - for (i, (key, value)) in dict.keys.iter().zip(dict.values.iter()).enumerate() { + for (i, ast::DictItem { key, value }) in dict.items.iter().enumerate() { let Some(key) = key else { continue; }; @@ -167,20 +167,20 @@ pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) { if !seen_values.insert(comparable_value) { diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion( parenthesized_range( - (&dict.values[i - 1]).into(), + dict.value(i - 1).into(), dict.into(), checker.indexer().comment_ranges(), checker.locator().contents(), ) - .unwrap_or(dict.values[i - 1].range()) + .unwrap_or_else(|| dict.value(i - 1).range()) .end(), parenthesized_range( - (&dict.values[i]).into(), + dict.value(i).into(), dict.into(), checker.indexer().comment_ranges(), checker.locator().contents(), ) - .unwrap_or(dict.values[i].range()) + .unwrap_or_else(|| dict.value(i).range()) .end(), ))); } @@ -195,24 +195,24 @@ pub(crate) fn repeated_keys(checker: &mut Checker, dict: &ast::ExprDict) { }, key.range(), ); - let comparable_value: ComparableExpr = (&dict.values[i]).into(); + let comparable_value: ComparableExpr = dict.value(i).into(); if !seen_values.insert(comparable_value) { diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion( parenthesized_range( - (&dict.values[i - 1]).into(), + dict.value(i - 1).into(), dict.into(), checker.indexer().comment_ranges(), checker.locator().contents(), ) - .unwrap_or(dict.values[i - 1].range()) + .unwrap_or_else(|| dict.value(i - 1).range()) .end(), parenthesized_range( - (&dict.values[i]).into(), + dict.value(i).into(), dict.into(), checker.indexer().comment_ranges(), checker.locator().contents(), ) - .unwrap_or(dict.values[i].range()) + .unwrap_or_else(|| dict.value(i).range()) .end(), ))); } diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs index f4f5f94ed9d0a2..d61c501bde69f6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/strings.rs @@ -573,13 +573,12 @@ pub(crate) fn percent_format_extra_named_arguments( return; }; // If any of the keys are spread, abort. - if dict.keys.iter().any(Option::is_none) { + if dict.iter_keys().any(|key| key.is_none()) { return; } let missing: Vec<(usize, &str)> = dict - .keys - .iter() + .iter_keys() .enumerate() .filter_map(|(index, key)| match key { Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) => { @@ -629,16 +628,16 @@ pub(crate) fn percent_format_missing_arguments( return; } - let Expr::Dict(ast::ExprDict { keys, .. }) = &right else { + let Expr::Dict(dict) = &right else { return; }; - if keys.iter().any(Option::is_none) { + if dict.iter_keys().any(|key| key.is_none()) { return; // contains **x splat } let mut keywords = FxHashSet::default(); - for key in keys.iter().flatten() { + for key in dict.iter_keys().flatten() { match key { Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { keywords.insert(value.to_str()); diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs index 37f38e4bc32b77..46d242e95e2ab2 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs @@ -170,16 +170,12 @@ fn is_valid_tuple(formats: &[CFormatStrOrBytes], elts: &[Expr]) -> bool } /// Return `true` if the dictionary values align with the format types. -fn is_valid_dict( - formats: &[CFormatStrOrBytes], - keys: &[Option], - values: &[Expr], -) -> bool { +fn is_valid_dict(formats: &[CFormatStrOrBytes], items: &[ast::DictItem]) -> bool { let formats = collect_specs(formats); // If there are more formats that values, the statement is invalid. Avoid // checking the values. - if formats.len() > values.len() { + if formats.len() > items.len() { return true; } @@ -192,7 +188,7 @@ fn is_valid_dict( .map(|mapping_key| (mapping_key.as_str(), format)) }) .collect(); - for (key, value) in keys.iter().zip(values) { + for ast::DictItem { key, value } in items { let Some(key) = key else { return true; }; @@ -252,11 +248,7 @@ pub(crate) fn bad_string_format_type(checker: &mut Checker, expr: &Expr, right: // Parse the parameters. let is_valid = match right { Expr::Tuple(ast::ExprTuple { elts, .. }) => is_valid_tuple(&format_strings, elts), - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => is_valid_dict(&format_strings, keys, values), + Expr::Dict(ast::ExprDict { items, range: _ }) => is_valid_dict(&format_strings, items), _ => is_valid_constant(&format_strings, right), }; if !is_valid { diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs index e5063808df116b..28870dda84c7de 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs @@ -99,10 +99,10 @@ fn is_dict_key_tuple_with_two_elements(semantic: &SemanticModel, binding: &Bindi return false; }; - dict_expr.keys.iter().all(|elt| { - elt.as_ref().is_some_and(|x| { - if let Some(tuple) = x.as_tuple_expr() { - return tuple.elts.len() == 2; + dict_expr.iter_keys().all(|elt| { + elt.is_some_and(|x| { + if let Expr::Tuple(ExprTuple { elts, .. }) = x { + return elts.len() == 2; } false }) diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs index 4c0a98948e6277..1fa567ff4fa994 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs @@ -229,7 +229,7 @@ fn slots_attributes(expr: &Expr) -> impl Iterator { // Ex) `__slots__ = {"name": ...}` let keys_iter = match expr { - Expr::Dict(ast::ExprDict { keys, .. }) => Some(keys.iter().filter_map(|key| match key { + Expr::Dict(dict) => Some(dict.iter_keys().filter_map(|key| match key { Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) => Some(value.to_str()), _ => None, })), diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs index 53099c879d782c..1ae894a96d17e3 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_keyword_argument.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashSet; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{Expr, ExprCall, ExprDict, ExprStringLiteral}; +use ruff_python_ast::{Expr, ExprCall, ExprStringLiteral}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -56,9 +56,9 @@ pub(crate) fn repeated_keyword_argument(checker: &mut Checker, call: &ExprCall) keyword.range(), )); } - } else if let Expr::Dict(ExprDict { keys, .. }) = &keyword.value { + } else if let Expr::Dict(dict) = &keyword.value { // Ex) `func(**{"a": 1, "a": 2})` - for key in keys.iter().flatten() { + for key in dict.iter_keys().flatten() { if let Expr::StringLiteral(ExprStringLiteral { value, .. }) = key { if !seen.insert(value.to_str()) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index baf1b4c140228d..cb9948fbfa5686 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -163,16 +163,16 @@ fn create_class_def_stmt( .into() } -fn fields_from_dict_literal(keys: &[Option], values: &[Expr]) -> Option> { - if keys.is_empty() { +fn fields_from_dict_literal(items: &[ast::DictItem]) -> Option> { + if items.is_empty() { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), }); Some(vec![node]) } else { - keys.iter() - .zip(values.iter()) - .map(|(key, value)| match key { + items + .iter() + .map(|ast::DictItem { key, value }| match key { Some(Expr::StringLiteral(ast::ExprStringLiteral { value: field, .. })) => { if !is_identifier(field.to_str()) { return None; @@ -231,11 +231,9 @@ fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec, Option<&K ([_typename, fields], [..]) => { let total = arguments.find_keyword("total"); match fields { - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => Some((fields_from_dict_literal(keys, values)?, total)), + Expr::Dict(ast::ExprDict { items, range: _ }) => { + Some((fields_from_dict_literal(items)?, total)) + } Expr::Call(ast::ExprCall { func, arguments: Arguments { keywords, .. }, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index 3c53924d6df4f7..75d325c37510b1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -212,16 +212,11 @@ fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> { fn clean_params_dictionary(right: &Expr, locator: &Locator, stylist: &Stylist) -> Option { let is_multi_line = locator.contains_line_break(right.range()); let mut contents = String::new(); - if let Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) = &right - { + if let Expr::Dict(ast::ExprDict { items, range: _ }) = &right { let mut arguments: Vec = vec![]; let mut seen: Vec<&str> = vec![]; let mut indent = None; - for (key, value) in keys.iter().zip(values.iter()) { + for ast::DictItem { key, value } in items { match key { Some(key) => { if let Expr::StringLiteral(ast::ExprStringLiteral { diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index 01e4300727892b..ed308557ae1805 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -174,13 +174,9 @@ impl<'a> StringLiteralDisplay<'a> { display_kind, } } - ast::Expr::Dict(ast::ExprDict { - keys, - values, - range, - }) => { - let mut narrowed_keys = Vec::with_capacity(values.len()); - for key in keys { + ast::Expr::Dict(dict @ ast::ExprDict { items, range }) => { + let mut narrowed_keys = Vec::with_capacity(items.len()); + for key in dict.iter_keys() { if let Some(key) = key { // This is somewhat unfortunate, // *but* using a dict for __slots__ is very rare @@ -193,12 +189,11 @@ impl<'a> StringLiteralDisplay<'a> { // `__slots__ = {"foo": "bar", **other_dict}` // If `None` wasn't present in the keys, // the length of the keys should always equal the length of the values - assert_eq!(narrowed_keys.len(), values.len()); - let display_kind = DisplayKind::Dict { values }; + assert_eq!(narrowed_keys.len(), items.len()); Self { elts: Cow::Owned(narrowed_keys), range: *range, - display_kind, + display_kind: DisplayKind::Dict { items }, } } _ => return None, @@ -206,7 +201,7 @@ impl<'a> StringLiteralDisplay<'a> { Some(result) } - fn generate_fix(&self, items: &[&str], checker: &Checker) -> Option { + fn generate_fix(&self, elements: &[&str], checker: &Checker) -> Option { let locator = checker.locator(); let is_multiline = locator.contains_line_break(self.range()); let sorted_source_code = match (&self.display_kind, is_multiline) { @@ -224,12 +219,12 @@ impl<'a> StringLiteralDisplay<'a> { (DisplayKind::Sequence(sequence_kind), false) => sort_single_line_elements_sequence( *sequence_kind, &self.elts, - items, + elements, locator, SORTING_STYLE, ), - (DisplayKind::Dict { values }, false) => { - sort_single_line_elements_dict(&self.elts, items, values, locator) + (DisplayKind::Dict { items }, false) => { + sort_single_line_elements_dict(&self.elts, elements, items, locator) } }; Some(Fix::safe_edit(Edit::range_replacement( @@ -245,7 +240,7 @@ impl<'a> StringLiteralDisplay<'a> { #[derive(Debug)] enum DisplayKind<'a> { Sequence(SequenceKind), - Dict { values: &'a [ast::Expr] }, + Dict { items: &'a [ast::DictItem] }, } /// A newtype that zips together three iterables: @@ -262,14 +257,14 @@ enum DisplayKind<'a> { struct DictElements<'a>(Vec<(&'a &'a str, &'a ast::Expr, &'a ast::Expr)>); impl<'a> DictElements<'a> { - fn new(elements: &'a [&str], key_elts: &'a [ast::Expr], value_elts: &'a [ast::Expr]) -> Self { + fn new(elements: &'a [&str], key_elts: &'a [ast::Expr], items: &'a [ast::DictItem]) -> Self { assert_eq!(key_elts.len(), elements.len()); - assert_eq!(elements.len(), value_elts.len()); + assert_eq!(elements.len(), items.len()); assert!( elements.len() >= 2, "A sequence with < 2 elements cannot be unsorted" ); - Self(izip!(elements, key_elts, value_elts).collect()) + Self(izip!(elements, key_elts, items.iter().map(|item| &item.value)).collect()) } fn last_item_index(&self) -> usize { @@ -294,13 +289,13 @@ impl<'a> DictElements<'a> { /// `sequence_sorting.rs` if any other modules need it, /// but stays here for now, since this is currently the /// only module that needs it -fn sort_single_line_elements_dict( - key_elts: &[ast::Expr], - elements: &[&str], - value_elts: &[ast::Expr], +fn sort_single_line_elements_dict<'a>( + key_elts: &'a [ast::Expr], + elements: &'a [&str], + original_items: &'a [ast::DictItem], locator: &Locator, ) -> String { - let element_trios = DictElements::new(elements, key_elts, value_elts); + let element_trios = DictElements::new(elements, key_elts, original_items); let last_item_index = element_trios.last_item_index(); let mut result = String::from('{'); // We grab the original source-code ranges using `locator.slice()` diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs index b1747f63e32628..4d975889d1df6a 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/verbose_raise.rs @@ -49,29 +49,6 @@ impl AlwaysFixableViolation for VerboseRaise { } } -#[derive(Default)] -struct RaiseStatementVisitor<'a> { - raises: Vec<&'a ast::StmtRaise>, -} - -impl<'a> StatementVisitor<'a> for RaiseStatementVisitor<'a> { - fn visit_stmt(&mut self, stmt: &'a Stmt) { - match stmt { - Stmt::Raise(raise @ ast::StmtRaise { .. }) => { - self.raises.push(raise); - } - Stmt::Try(ast::StmtTry { - body, finalbody, .. - }) => { - for stmt in body.iter().chain(finalbody.iter()) { - walk_stmt(self, stmt); - } - } - _ => walk_stmt(self, stmt), - } - } -} - /// TRY201 pub(crate) fn verbose_raise(checker: &mut Checker, handlers: &[ExceptHandler]) { for handler in handlers { @@ -108,3 +85,26 @@ pub(crate) fn verbose_raise(checker: &mut Checker, handlers: &[ExceptHandler]) { } } } + +#[derive(Default)] +struct RaiseStatementVisitor<'a> { + raises: Vec<&'a ast::StmtRaise>, +} + +impl<'a> StatementVisitor<'a> for RaiseStatementVisitor<'a> { + fn visit_stmt(&mut self, stmt: &'a Stmt) { + match stmt { + Stmt::Raise(raise @ ast::StmtRaise { .. }) => { + self.raises.push(raise); + } + Stmt::Try(ast::StmtTry { + body, finalbody, .. + }) => { + for stmt in body.iter().chain(finalbody.iter()) { + walk_stmt(self, stmt); + } + } + _ => walk_stmt(self, stmt), + } + } +} diff --git a/crates/ruff_python_ast/Cargo.toml b/crates/ruff_python_ast/Cargo.toml index 6918e6aed95501..675deb7b2e7d18 100644 --- a/crates/ruff_python_ast/Cargo.toml +++ b/crates/ruff_python_ast/Cargo.toml @@ -27,7 +27,6 @@ serde = { workspace = true, optional = true } [dev-dependencies] insta = { workspace = true } -ruff_python_parser = { path = "../ruff_python_parser" } [features] serde = ["dep:serde", "ruff_text_size/serde"] diff --git a/crates/ruff_python_ast/src/comparable.rs b/crates/ruff_python_ast/src/comparable.rs index b19c320344fa59..369bc84aafbe25 100644 --- a/crates/ruff_python_ast/src/comparable.rs +++ b/crates/ruff_python_ast/src/comparable.rs @@ -687,10 +687,24 @@ pub struct ExprIf<'a> { orelse: Box>, } +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ComparableDictItem<'a> { + key: Option>, + value: ComparableExpr<'a>, +} + +impl<'a> From<&'a ast::DictItem> for ComparableDictItem<'a> { + fn from(ast::DictItem { key, value }: &'a ast::DictItem) -> Self { + Self { + key: key.as_ref().map(ComparableExpr::from), + value: value.into(), + } + } +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprDict<'a> { - keys: Vec>>, - values: Vec>, + items: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] @@ -933,16 +947,8 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { body: body.into(), orelse: orelse.into(), }), - ast::Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => Self::Dict(ExprDict { - keys: keys - .iter() - .map(|expr| expr.as_ref().map(Into::into)) - .collect(), - values: values.iter().map(Into::into).collect(), + ast::Expr::Dict(ast::ExprDict { items, range: _ }) => Self::Dict(ExprDict { + items: items.iter().map(ComparableDictItem::from).collect(), }), ast::Expr::Set(ast::ExprSet { elts, range: _ }) => Self::Set(ExprSet { elts: elts.iter().map(Into::into).collect(), diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 7213355ff6af81..eab3d17060df78 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -155,14 +155,12 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool { orelse, range: _, }) => any_over_expr(test, func) || any_over_expr(body, func) || any_over_expr(orelse, func), - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => values - .iter() - .chain(keys.iter().flatten()) - .any(|expr| any_over_expr(expr, func)), + Expr::Dict(ast::ExprDict { items, range: _ }) => { + items.iter().any(|ast::DictItem { key, value }| { + any_over_expr(value, func) + || key.as_ref().is_some_and(|key| any_over_expr(key, func)) + }) + } Expr::Set(ast::ExprSet { elts, range: _ }) | Expr::List(ast::ExprList { elts, range: _, .. }) | Expr::Tuple(ast::ExprTuple { elts, range: _, .. }) => { @@ -1188,8 +1186,8 @@ impl Truthiness { Self::Truthy } } - Expr::Dict(ast::ExprDict { keys, .. }) => { - if keys.is_empty() { + Expr::Dict(ast::ExprDict { items, .. }) => { + if items.is_empty() { Self::Falsey } else { Self::Truthy diff --git a/crates/ruff_python_ast/src/node.rs b/crates/ruff_python_ast/src/node.rs index d3192e27500368..d8c2e3eacf6305 100644 --- a/crates/ruff_python_ast/src/node.rs +++ b/crates/ruff_python_ast/src/node.rs @@ -2301,13 +2301,9 @@ impl AstNode for ast::ExprDict { where V: PreorderVisitor<'a> + ?Sized, { - let ast::ExprDict { - keys, - values, - range: _, - } = self; + let ast::ExprDict { items, range: _ } = self; - for (key, value) in keys.iter().zip(values) { + for ast::DictItem { key, value } in items { if let Some(key) = key { visitor.visit_expr(key); } diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 25c00f0dbe4130..babba89fc262fe 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -767,12 +767,89 @@ impl From for Expr { } } +/// Represents an item in a [dictionary literal display][1]. +/// +/// Consider the following Python dictionary literal: +/// ```python +/// {key1: value1, **other_dictionary} +/// ``` +/// +/// In our AST, this would be represented using an `ExprDict` node containing +/// two `DictItem` nodes inside it: +/// ```ignore +/// [ +/// DictItem { +/// key: Some(Expr::Name(ExprName { id: "key1" })), +/// value: Expr::Name(ExprName { id: "value1" }), +/// }, +/// DictItem { +/// key: None, +/// value: Expr::Name(ExprName { id: "other_dictionary" }), +/// } +/// ] +/// ``` +/// +/// [1]: https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries +#[derive(Debug, Clone, PartialEq)] +pub struct DictItem { + pub key: Option, + pub value: Expr, +} + +impl DictItem { + fn key(&self) -> Option<&Expr> { + self.key.as_ref() + } + + fn value(&self) -> &Expr { + &self.value + } +} + +impl Ranged for DictItem { + fn range(&self) -> TextRange { + TextRange::new( + self.key.as_ref().map_or(self.value.start(), Ranged::start), + self.value.end(), + ) + } +} + /// See also [Dict](https://docs.python.org/3/library/ast.html#ast.Dict) #[derive(Clone, Debug, PartialEq)] pub struct ExprDict { pub range: TextRange, - pub keys: Vec>, - pub values: Vec, + pub items: Vec, +} + +impl ExprDict { + /// Returns an `Iterator` over the AST nodes representing the + /// dictionary's keys. + pub fn iter_keys(&self) -> DictKeyIterator { + DictKeyIterator::new(&self.items) + } + + /// Returns an `Iterator` over the AST nodes representing the + /// dictionary's values. + pub fn iter_values(&self) -> DictValueIterator { + DictValueIterator::new(&self.items) + } + + /// Returns the AST node representing the *n*th key of this + /// dictionary. + /// + /// Panics: If the index `n` is out of bounds. + pub fn key(&self, n: usize) -> Option<&Expr> { + self.items[n].key() + } + + /// Returns the AST node representing the *n*th value of this + /// dictionary. + /// + /// Panics: If the index `n` is out of bounds. + pub fn value(&self, n: usize) -> &Expr { + self.items[n].value() + } } impl From for Expr { @@ -781,6 +858,90 @@ impl From for Expr { } } +#[derive(Debug, Clone)] +pub struct DictKeyIterator<'a> { + items: Iter<'a, DictItem>, +} + +impl<'a> DictKeyIterator<'a> { + fn new(items: &'a [DictItem]) -> Self { + Self { + items: items.iter(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a> Iterator for DictKeyIterator<'a> { + type Item = Option<&'a Expr>; + + fn next(&mut self) -> Option { + self.items.next().map(DictItem::key) + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn size_hint(&self) -> (usize, Option) { + self.items.size_hint() + } +} + +impl<'a> DoubleEndedIterator for DictKeyIterator<'a> { + fn next_back(&mut self) -> Option { + self.items.next_back().map(DictItem::key) + } +} + +impl<'a> FusedIterator for DictKeyIterator<'a> {} +impl<'a> ExactSizeIterator for DictKeyIterator<'a> {} + +#[derive(Debug, Clone)] +pub struct DictValueIterator<'a> { + items: Iter<'a, DictItem>, +} + +impl<'a> DictValueIterator<'a> { + fn new(items: &'a [DictItem]) -> Self { + Self { + items: items.iter(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a> Iterator for DictValueIterator<'a> { + type Item = &'a Expr; + + fn next(&mut self) -> Option { + self.items.next().map(DictItem::value) + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn size_hint(&self) -> (usize, Option) { + self.items.size_hint() + } +} + +impl<'a> DoubleEndedIterator for DictValueIterator<'a> { + fn next_back(&mut self) -> Option { + self.items.next_back().map(DictItem::value) + } +} + +impl<'a> FusedIterator for DictValueIterator<'a> {} +impl<'a> ExactSizeIterator for DictValueIterator<'a> {} + /// See also [Set](https://docs.python.org/3/library/ast.html#ast.Set) #[derive(Clone, Debug, PartialEq)] pub struct ExprSet { @@ -4358,7 +4519,7 @@ mod tests { assert_eq!(std::mem::size_of::(), 40); assert_eq!(std::mem::size_of::(), 56); assert_eq!(std::mem::size_of::(), 48); - assert_eq!(std::mem::size_of::(), 56); + assert_eq!(std::mem::size_of::(), 32); assert_eq!(std::mem::size_of::(), 48); assert_eq!(std::mem::size_of::(), 8); // 56 for Rustc < 1.76 diff --git a/crates/ruff_python_ast/src/visitor.rs b/crates/ruff_python_ast/src/visitor.rs index 86471765cc9638..bf14e5a0cf6130 100644 --- a/crates/ruff_python_ast/src/visitor.rs +++ b/crates/ruff_python_ast/src/visitor.rs @@ -389,16 +389,12 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { visitor.visit_expr(body); visitor.visit_expr(orelse); } - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => { - for expr in keys.iter().flatten() { - visitor.visit_expr(expr); - } - for expr in values { - visitor.visit_expr(expr); + Expr::Dict(ast::ExprDict { items, range: _ }) => { + for ast::DictItem { key, value } in items { + if let Some(key) = key { + visitor.visit_expr(key); + } + visitor.visit_expr(value); } } Expr::Set(ast::ExprSet { elts, range: _ }) => { diff --git a/crates/ruff_python_ast/src/visitor/transformer.rs b/crates/ruff_python_ast/src/visitor/transformer.rs index 046630605fce6b..9589617ee06c27 100644 --- a/crates/ruff_python_ast/src/visitor/transformer.rs +++ b/crates/ruff_python_ast/src/visitor/transformer.rs @@ -376,16 +376,12 @@ pub fn walk_expr(visitor: &V, expr: &mut Expr) { visitor.visit_expr(body); visitor.visit_expr(orelse); } - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => { - for expr in keys.iter_mut().flatten() { - visitor.visit_expr(expr); - } - for expr in values { - visitor.visit_expr(expr); + Expr::Dict(ast::ExprDict { items, range: _ }) => { + for ast::DictItem { key, value } in items { + if let Some(key) = key { + visitor.visit_expr(key); + } + visitor.visit_expr(value); } } Expr::Set(ast::ExprSet { elts, range: _ }) => { diff --git a/crates/ruff_python_ast_integration_tests/Cargo.toml b/crates/ruff_python_ast_integration_tests/Cargo.toml new file mode 100644 index 00000000000000..f8c8efc6cda178 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ruff_python_ast_integration_tests" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] + +[dev-dependencies] +ruff_python_parser = { path = "../ruff_python_parser" } +ruff_python_ast = { path = "../ruff_python_ast" } +ruff_python_trivia = { path = "../ruff_python_trivia" } +ruff_text_size = { path = "../ruff_text_size" } + +insta = { workspace = true } + +[lints] +workspace = true diff --git a/crates/ruff_python_ast_integration_tests/README.md b/crates/ruff_python_ast_integration_tests/README.md new file mode 100644 index 00000000000000..68ac4e429812e6 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/README.md @@ -0,0 +1,13 @@ +# Integration tests for `ruff_python_ast` + +This crate includes integration tests for the `ruff_python_ast` crate. + +The reason for having a separate crate is to avoid introducing a dev circular +dependency between the `ruff_python_parser` crate and the `ruff_python_ast` crate. + +This crate shouldn't include any code, only tests. + +**Reference:** + +- `rust-analyzer` issue: +- Ruff's pull request: diff --git a/crates/ruff_python_ast_integration_tests/src/lib.rs b/crates/ruff_python_ast_integration_tests/src/lib.rs new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/ruff_python_ast/tests/identifier.rs b/crates/ruff_python_ast_integration_tests/tests/identifier.rs similarity index 99% rename from crates/ruff_python_ast/tests/identifier.rs rename to crates/ruff_python_ast_integration_tests/tests/identifier.rs index aaececf89c6d47..1e70c4fd65f50b 100644 --- a/crates/ruff_python_ast/tests/identifier.rs +++ b/crates/ruff_python_ast_integration_tests/tests/identifier.rs @@ -1,8 +1,7 @@ +use ruff_python_ast::identifier; use ruff_python_parser::{parse_suite, ParseError}; use ruff_text_size::{TextRange, TextSize}; -use ruff_python_ast::identifier; - #[test] fn extract_else_range() -> Result<(), ParseError> { let contents = r" diff --git a/crates/ruff_python_ast/tests/parenthesize.rs b/crates/ruff_python_ast_integration_tests/tests/parenthesize.rs similarity index 100% rename from crates/ruff_python_ast/tests/parenthesize.rs rename to crates/ruff_python_ast_integration_tests/tests/parenthesize.rs diff --git a/crates/ruff_python_ast/tests/preorder.rs b/crates/ruff_python_ast_integration_tests/tests/preorder.rs similarity index 100% rename from crates/ruff_python_ast/tests/preorder.rs rename to crates/ruff_python_ast_integration_tests/tests/preorder.rs diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__bytes_literals.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__bytes_literals.snap similarity index 67% rename from crates/ruff_python_ast/tests/snapshots/preorder__bytes_literals.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__bytes_literals.snap index d71ea07e19274a..11df74981c0429 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__bytes_literals.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__bytes_literals.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -8,4 +8,3 @@ expression: trace - BytesLiteral - BytesLiteral - BytesLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__class_type_parameters.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__class_type_parameters.snap similarity index 78% rename from crates/ruff_python_ast/tests/snapshots/preorder__class_type_parameters.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__class_type_parameters.snap index 20ef6b71fa4422..7b72f072ca01c0 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__class_type_parameters.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__class_type_parameters.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -12,4 +12,3 @@ expression: trace - TypeParamParamSpec - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__compare.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__compare.snap similarity index 70% rename from crates/ruff_python_ast/tests/snapshots/preorder__compare.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__compare.snap index 848886f7676523..7982572267a292 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__compare.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__compare.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -10,4 +10,3 @@ expression: trace - ExprName - Lt - ExprNumberLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__decorators.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__decorators.snap similarity index 73% rename from crates/ruff_python_ast/tests/snapshots/preorder__decorators.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__decorators.snap index e91bd2e83b9bb1..f6b6c175ae9bb2 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__decorators.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__decorators.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -12,4 +12,3 @@ expression: trace - Decorator - ExprName - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__dict_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__dict_comprehension.snap similarity index 77% rename from crates/ruff_python_ast/tests/snapshots/preorder__dict_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__dict_comprehension.snap index d1479d6fed158d..2fa8d035d3f496 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__dict_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__dict_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -13,4 +13,3 @@ expression: trace - Comprehension - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__f_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__f_strings.snap similarity index 84% rename from crates/ruff_python_ast/tests/snapshots/preorder__f_strings.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__f_strings.snap index 043c58064b48e6..296252a2667842 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__f_strings.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__f_strings.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -15,4 +15,3 @@ expression: trace - ExprName - FStringLiteralElement - FStringLiteralElement - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__function_arguments.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_arguments.snap similarity index 88% rename from crates/ruff_python_ast/tests/snapshots/preorder__function_arguments.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_arguments.snap index da83fc10f81299..d743ba0fa2a549 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__function_arguments.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_arguments.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -23,4 +23,3 @@ expression: trace - ExprNumberLiteral - Parameter - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__function_positional_only_with_default.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_positional_only_with_default.snap similarity index 82% rename from crates/ruff_python_ast/tests/snapshots/preorder__function_positional_only_with_default.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_positional_only_with_default.snap index 6511aa3468297a..198e3b2aa304df 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__function_positional_only_with_default.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_positional_only_with_default.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -15,4 +15,3 @@ expression: trace - ExprNumberLiteral - Parameter - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__function_type_parameters.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_type_parameters.snap similarity index 79% rename from crates/ruff_python_ast/tests/snapshots/preorder__function_type_parameters.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_type_parameters.snap index 11b98d0fa78e32..d30516bf7bd500 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__function_type_parameters.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__function_type_parameters.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -13,4 +13,3 @@ expression: trace - Parameters - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__list_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__list_comprehension.snap similarity index 68% rename from crates/ruff_python_ast/tests/snapshots/preorder__list_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__list_comprehension.snap index dc15fb0197021d..d7fa6bda2f4790 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__list_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__list_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -9,4 +9,3 @@ expression: trace - Comprehension - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__match_class_pattern.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__match_class_pattern.snap similarity index 91% rename from crates/ruff_python_ast/tests/snapshots/preorder__match_class_pattern.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__match_class_pattern.snap index dbe56f11ff3bd4..70fa1276fe55f4 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__match_class_pattern.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__match_class_pattern.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -30,4 +30,3 @@ expression: trace - ExprNumberLiteral - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__set_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__set_comprehension.snap similarity index 68% rename from crates/ruff_python_ast/tests/snapshots/preorder__set_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__set_comprehension.snap index e9abdf12456aa2..47c7344d6155d1 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__set_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__set_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -9,4 +9,3 @@ expression: trace - Comprehension - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__string_literals.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__string_literals.snap similarity index 67% rename from crates/ruff_python_ast/tests/snapshots/preorder__string_literals.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__string_literals.snap index 7b62ce78b72f72..fae44b6ed9abaf 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__string_literals.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__string_literals.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -8,4 +8,3 @@ expression: trace - StringLiteral - StringLiteral - StringLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/preorder__type_aliases.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__type_aliases.snap similarity index 80% rename from crates/ruff_python_ast/tests/snapshots/preorder__type_aliases.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__type_aliases.snap index 56d6aec5c0decb..5c986912e2de5b 100644 --- a/crates/ruff_python_ast/tests/snapshots/preorder__type_aliases.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/preorder__type_aliases.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/preorder.rs +source: crates/ruff_python_ast_integration_tests/tests/preorder.rs expression: trace --- - ModModule @@ -14,4 +14,3 @@ expression: trace - ExprSubscript - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__bytes_literals.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__bytes_literals.snap similarity index 63% rename from crates/ruff_python_ast/tests/snapshots/visitor__bytes_literals.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__bytes_literals.snap index 57bed3a9c7502f..0ad5f4f890800b 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__bytes_literals.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__bytes_literals.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -7,4 +7,3 @@ expression: trace - BytesLiteral - BytesLiteral - BytesLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__class_type_parameters.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__class_type_parameters.snap similarity index 73% rename from crates/ruff_python_ast/tests/snapshots/visitor__class_type_parameters.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__class_type_parameters.snap index 5ca38f249ffbdd..cf16702250f540 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__class_type_parameters.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__class_type_parameters.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtClassDef @@ -10,4 +10,3 @@ expression: trace - TypeParamParamSpec - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__compare.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__compare.snap similarity index 67% rename from crates/ruff_python_ast/tests/snapshots/visitor__compare.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__compare.snap index 6a5f99eb4d8e4f..8c0425fa43ca26 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__compare.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__compare.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -9,4 +9,3 @@ expression: trace - Lt - ExprName - ExprNumberLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__decorators.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__decorators.snap similarity index 65% rename from crates/ruff_python_ast/tests/snapshots/visitor__decorators.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__decorators.snap index c633132e04b4dc..799385941d8b9d 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__decorators.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__decorators.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtFunctionDef @@ -9,4 +9,3 @@ expression: trace - StmtClassDef - ExprName - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__dict_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__dict_comprehension.snap similarity index 74% rename from crates/ruff_python_ast/tests/snapshots/visitor__dict_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__dict_comprehension.snap index 138cb45b385ba2..40fe6e72eb67df 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__dict_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__dict_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -12,4 +12,3 @@ expression: trace - ExprName - Pow - ExprNumberLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__f_strings.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap similarity index 82% rename from crates/ruff_python_ast/tests/snapshots/visitor__f_strings.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap index a5c8a8b9059276..2e9b7dccbc06f2 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__f_strings.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__f_strings.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -14,4 +14,3 @@ expression: trace - ExprName - FStringLiteralElement - FStringLiteralElement - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__function_arguments.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_arguments.snap similarity index 80% rename from crates/ruff_python_ast/tests/snapshots/visitor__function_arguments.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_arguments.snap index fa6ebc6ee4ee3a..a526cdd1444029 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__function_arguments.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_arguments.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtFunctionDef @@ -16,4 +16,3 @@ expression: trace - Parameter - Parameter - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__function_positional_only_with_default.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_positional_only_with_default.snap similarity index 73% rename from crates/ruff_python_ast/tests/snapshots/visitor__function_positional_only_with_default.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_positional_only_with_default.snap index a92798a086572f..c7501b8ae868e2 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__function_positional_only_with_default.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_positional_only_with_default.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtFunctionDef @@ -11,4 +11,3 @@ expression: trace - Parameter - Parameter - StmtPass - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__function_type_parameters.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_type_parameters.snap similarity index 75% rename from crates/ruff_python_ast/tests/snapshots/visitor__function_type_parameters.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_type_parameters.snap index 60a1abe6be50ca..2af2fa85aa2bbd 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__function_type_parameters.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__function_type_parameters.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtFunctionDef @@ -11,4 +11,3 @@ expression: trace - Parameters - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__list_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__list_comprehension.snap similarity index 65% rename from crates/ruff_python_ast/tests/snapshots/visitor__list_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__list_comprehension.snap index 05f75bd8df0ff6..7af26ca3c431d6 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__list_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__list_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -8,4 +8,3 @@ expression: trace - ExprName - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__match_class_pattern.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__match_class_pattern.snap similarity index 88% rename from crates/ruff_python_ast/tests/snapshots/visitor__match_class_pattern.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__match_class_pattern.snap index 0c3513b0a648c0..d91b865a4f5a2f 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__match_class_pattern.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__match_class_pattern.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtMatch @@ -24,4 +24,3 @@ expression: trace - ExprNumberLiteral - StmtExpr - ExprEllipsisLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__set_comprehension.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__set_comprehension.snap similarity index 64% rename from crates/ruff_python_ast/tests/snapshots/visitor__set_comprehension.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__set_comprehension.snap index 337ef4624f858e..799b67aefac7c1 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__set_comprehension.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__set_comprehension.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -8,4 +8,3 @@ expression: trace - ExprName - ExprName - ExprName - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__string_literals.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__string_literals.snap similarity index 64% rename from crates/ruff_python_ast/tests/snapshots/visitor__string_literals.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__string_literals.snap index 7c066eae56989d..08cd420a9955b6 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__string_literals.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__string_literals.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtExpr @@ -7,4 +7,3 @@ expression: trace - StringLiteral - StringLiteral - StringLiteral - diff --git a/crates/ruff_python_ast/tests/snapshots/visitor__type_aliases.snap b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__type_aliases.snap similarity index 76% rename from crates/ruff_python_ast/tests/snapshots/visitor__type_aliases.snap rename to crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__type_aliases.snap index 10b624fbb09d09..539a38562d3579 100644 --- a/crates/ruff_python_ast/tests/snapshots/visitor__type_aliases.snap +++ b/crates/ruff_python_ast_integration_tests/tests/snapshots/visitor__type_aliases.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_ast/tests/visitor.rs +source: crates/ruff_python_ast_integration_tests/tests/visitor.rs expression: trace --- - StmtTypeAlias @@ -12,4 +12,3 @@ expression: trace - TypeParamTypeVarTuple - TypeParamParamSpec - ExprName - diff --git a/crates/ruff_python_ast/tests/stmt_if.rs b/crates/ruff_python_ast_integration_tests/tests/stmt_if.rs similarity index 99% rename from crates/ruff_python_ast/tests/stmt_if.rs rename to crates/ruff_python_ast_integration_tests/tests/stmt_if.rs index fe6c4a85c17ea5..cacf964996f5b5 100644 --- a/crates/ruff_python_ast/tests/stmt_if.rs +++ b/crates/ruff_python_ast_integration_tests/tests/stmt_if.rs @@ -1,5 +1,4 @@ use ruff_python_ast::stmt_if::elif_else_range; - use ruff_python_parser::{parse_suite, ParseError}; use ruff_text_size::TextSize; diff --git a/crates/ruff_python_ast/tests/visitor.rs b/crates/ruff_python_ast_integration_tests/tests/visitor.rs similarity index 96% rename from crates/ruff_python_ast/tests/visitor.rs rename to crates/ruff_python_ast_integration_tests/tests/visitor.rs index e039e78d96ad68..1c1bf0d0f7fb38 100644 --- a/crates/ruff_python_ast/tests/visitor.rs +++ b/crates/ruff_python_ast_integration_tests/tests/visitor.rs @@ -1,9 +1,6 @@ use std::fmt::{Debug, Write}; use insta::assert_snapshot; -use ruff_python_ast as ast; -use ruff_python_parser::lexer::lex; -use ruff_python_parser::{parse_tokens, Mode}; use ruff_python_ast::visitor::{ walk_alias, walk_bytes_literal, walk_comprehension, walk_except_handler, walk_expr, @@ -11,12 +8,13 @@ use ruff_python_ast::visitor::{ walk_parameters, walk_pattern, walk_stmt, walk_string_literal, walk_type_param, walk_with_item, Visitor, }; -use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{ - Alias, BoolOp, BytesLiteral, CmpOp, Comprehension, ExceptHandler, Expr, FString, - FStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, Stmt, - StringLiteral, TypeParam, UnaryOp, WithItem, + self as ast, Alias, AnyNodeRef, BoolOp, BytesLiteral, CmpOp, Comprehension, ExceptHandler, + Expr, FString, FStringElement, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, + Stmt, StringLiteral, TypeParam, UnaryOp, WithItem, }; +use ruff_python_parser::lexer::lex; +use ruff_python_parser::{parse_tokens, Mode}; #[test] fn function_arguments() { diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index 80f7978449cca2..1c95db1f9c3c07 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -919,22 +919,18 @@ impl<'a> Generator<'a> { self.unparse_expr(orelse, precedence::IF_EXP); }); } - Expr::Dict(ast::ExprDict { - keys, - values, - range: _, - }) => { + Expr::Dict(ast::ExprDict { items, range: _ }) => { self.p("{"); let mut first = true; - for (k, v) in keys.iter().zip(values) { + for ast::DictItem { key, value } in items { self.p_delim(&mut first, ", "); - if let Some(k) = k { - self.unparse_expr(k, precedence::COMMA); + if let Some(key) = key { + self.unparse_expr(key, precedence::COMMA); self.p(": "); - self.unparse_expr(v, precedence::COMMA); + self.unparse_expr(value, precedence::COMMA); } else { self.p("**"); - self.unparse_expr(v, precedence::MAX); + self.unparse_expr(value, precedence::MAX); } } self.p("}"); diff --git a/crates/ruff_python_formatter/src/comments/mod.rs b/crates/ruff_python_formatter/src/comments/mod.rs index dbb07f2dce26cb..9717252a9b4358 100644 --- a/crates/ruff_python_formatter/src/comments/mod.rs +++ b/crates/ruff_python_formatter/src/comments/mod.rs @@ -96,7 +96,6 @@ pub(crate) use format::{ leading_alternate_branch_comments, leading_comments, leading_node_comments, trailing_comments, }; use ruff_formatter::{SourceCode, SourceCodeSlice}; -use ruff_python_ast::visitor::preorder::{PreorderVisitor, TraversalSignal}; use ruff_python_ast::AnyNodeRef; use ruff_python_trivia::{CommentLinePosition, CommentRanges, SuppressionKind}; use ruff_source_file::Locator; @@ -407,6 +406,20 @@ impl<'a> Comments<'a> { /// normally if `node` is the first or last node of a suppression range. #[cfg(debug_assertions)] pub(crate) fn mark_verbatim_node_comments_formatted(&self, node: AnyNodeRef) { + use ruff_python_ast::visitor::preorder::{PreorderVisitor, TraversalSignal}; + + struct MarkVerbatimCommentsAsFormattedVisitor<'a>(&'a Comments<'a>); + + impl<'a> PreorderVisitor<'a> for MarkVerbatimCommentsAsFormattedVisitor<'a> { + fn enter_node(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal { + for comment in self.0.leading_dangling_trailing(node) { + comment.mark_formatted(); + } + + TraversalSignal::Traverse + } + } + for dangling in self.dangling(node) { dangling.mark_formatted(); } @@ -452,18 +465,6 @@ struct CommentsData<'a> { comment_ranges: &'a CommentRanges, } -struct MarkVerbatimCommentsAsFormattedVisitor<'a>(&'a Comments<'a>); - -impl<'a> PreorderVisitor<'a> for MarkVerbatimCommentsAsFormattedVisitor<'a> { - fn enter_node(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal { - for comment in self.0.leading_dangling_trailing(node) { - comment.mark_formatted(); - } - - TraversalSignal::Traverse - } -} - pub(crate) fn has_skip_comment(trailing_comments: &[SourceComment], source: &str) -> bool { trailing_comments.iter().any(|comment| { comment.line_position().is_end_of_line() diff --git a/crates/ruff_python_formatter/src/expression/expr_dict.rs b/crates/ruff_python_formatter/src/expression/expr_dict.rs index b56eb5033e9dba..4e0e3abb5e5482 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict.rs @@ -1,6 +1,5 @@ use ruff_formatter::{format_args, write}; -use ruff_python_ast::AnyNodeRef; -use ruff_python_ast::{Expr, ExprDict}; +use ruff_python_ast::{AnyNodeRef, DictItem, Expr, ExprDict}; use ruff_text_size::{Ranged, TextRange}; use crate::comments::{dangling_comments, leading_comments, SourceComment}; @@ -14,18 +13,12 @@ pub struct FormatExprDict; impl FormatNodeRule for FormatExprDict { fn fmt_fields(&self, item: &ExprDict, f: &mut PyFormatter) -> FormatResult<()> { - let ExprDict { - range: _, - keys, - values, - } = item; - - debug_assert_eq!(keys.len(), values.len()); + let ExprDict { range: _, items } = item; let comments = f.context().comments().clone(); let dangling = comments.dangling(item); - let (Some(key), Some(value)) = (keys.first(), values.first()) else { + let Some(first_dict_item) = items.first() else { return empty_parenthesized("{", dangling, "}").fmt(f); }; @@ -37,17 +30,17 @@ impl FormatNodeRule for FormatExprDict { // y // } // ``` - let (open_parenthesis_comments, key_value_comments) = dangling.split_at( - dangling - .partition_point(|comment| comment.end() < KeyValuePair::new(key, value).start()), - ); + let (open_parenthesis_comments, key_value_comments) = + dangling.split_at(dangling.partition_point(|comment| { + comment.end() < KeyValuePair::new(first_dict_item).start() + })); let format_pairs = format_with(|f| { let mut joiner = f.join_comma_separated(item.end()); let mut key_value_comments = key_value_comments; - for (key, value) in keys.iter().zip(values) { - let mut key_value_pair = KeyValuePair::new(key, value); + for dict_item in items { + let mut key_value_pair = KeyValuePair::new(dict_item); let partition = key_value_comments .partition_point(|comment| comment.start() < key_value_pair.end()); @@ -84,10 +77,10 @@ struct KeyValuePair<'a> { } impl<'a> KeyValuePair<'a> { - fn new(key: &'a Option, value: &'a Expr) -> Self { + fn new(item: &'a DictItem) -> Self { Self { - key, - value, + key: &item.key, + value: &item.value, comments: &[], } } diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 2aea69895ffbbf..dd8b94c0275449 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -1059,8 +1059,8 @@ pub(crate) fn has_own_parentheses( } } - Expr::Dict(ast::ExprDict { keys, .. }) => { - if !keys.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { + Expr::Dict(ast::ExprDict { items, .. }) => { + if !items.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else { Some(OwnParentheses::Empty) @@ -1217,7 +1217,7 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) - // Sequence types can split if they contain at least one element. Expr::Tuple(tuple) => !tuple.elts.is_empty(), - Expr::Dict(dict) => !dict.values.is_empty(), + Expr::Dict(dict) => !dict.items.is_empty(), Expr::Set(set) => !set.elts.is_empty(), Expr::List(list) => !list.elts.is_empty(), diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 6f1caf399590f5..5e35eb4d8eab70 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -1544,8 +1544,7 @@ impl<'src> Parser<'src> { // Return an empty `DictExpr` when finding a `}` right after the `{` if self.eat(TokenKind::Rbrace) { return Expr::Dict(ast::ExprDict { - keys: vec![], - values: vec![], + items: vec![], range: self.node_range(start), }); } @@ -1794,21 +1793,24 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Comma); } - let mut keys = vec![key]; - let mut values = vec![value]; + let mut items = vec![ast::DictItem { key, value }]; self.parse_comma_separated_list(RecoveryContextKind::DictElements, |parser| { if parser.eat(TokenKind::DoubleStar) { - keys.push(None); - // Handle dictionary unpacking. Here, the grammar is `'**' bitwise_or` // which requires limiting the expression. - values.push(parser.parse_expression_with_bitwise_or_precedence().expr); + items.push(ast::DictItem { + key: None, + value: parser.parse_expression_with_bitwise_or_precedence().expr, + }); } else { - keys.push(Some(parser.parse_conditional_expression_or_higher().expr)); + let key = parser.parse_conditional_expression_or_higher().expr; parser.expect(TokenKind::Colon); - values.push(parser.parse_conditional_expression_or_higher().expr); + items.push(ast::DictItem { + key: Some(key), + value: parser.parse_conditional_expression_or_higher().expr, + }); } }); @@ -1816,8 +1818,7 @@ impl<'src> Parser<'src> { ast::ExprDict { range: self.node_range(start), - keys, - values, + items, } } diff --git a/crates/ruff_python_parser/src/parser/recovery.rs b/crates/ruff_python_parser/src/parser/recovery.rs index 9d25c99ccd82c0..8687b8c95f5eb1 100644 --- a/crates/ruff_python_parser/src/parser/recovery.rs +++ b/crates/ruff_python_parser/src/parser/recovery.rs @@ -51,24 +51,23 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr { patterns, rest, }) => { - let mut keys = keys.into_iter().map(Option::Some).collect::>(); - let mut values = patterns + let mut items: Vec = keys .into_iter() - .map(pattern_to_expr) - .collect::>(); + .zip(patterns) + .map(|(key, pattern)| ast::DictItem { + key: Some(key), + value: pattern_to_expr(pattern), + }) + .collect(); if let Some(rest) = rest { - keys.push(None); - values.push(Expr::Name(ast::ExprName { + let value = Expr::Name(ast::ExprName { range: rest.range, id: rest.id, ctx: ExprContext::Store, - })); + }); + items.push(ast::DictItem { key: None, value }); } - Expr::Dict(ast::ExprDict { - range, - keys, - values, - }) + Expr::Dict(ast::ExprDict { range, items }) } Pattern::MatchClass(ast::PatternMatchClass { range, diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap index 0919f80d12ce4b..bc87a11aba6025 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star.py.snap @@ -15,34 +15,36 @@ Module( value: Dict( ExprDict { range: 125..135, - keys: [ - None, - Some( - NumberLiteral( - ExprNumberLiteral { - range: 133..134, - value: Int( - 1, - ), + items: [ + DictItem { + key: None, + value: Name( + ExprName { + range: 128..129, + id: "x", + ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 128..129, - id: "x", - ctx: Load, - }, - ), - Name( - ExprName { - range: 134..134, - id: "", - ctx: Invalid, - }, - ), + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 133..134, + value: Int( + 1, + ), + }, + ), + ), + value: Name( + ExprName { + range: 134..134, + id: "", + ctx: Invalid, + }, + ), + }, ], }, ), @@ -54,52 +56,54 @@ Module( value: Dict( ExprDict { range: 136..162, - keys: [ - Some( - Name( - ExprName { - range: 137..138, - id: "a", - ctx: Load, - }, - ), - ), - None, - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 140..141, - value: Int( - 1, - ), - }, - ), - If( - ExprIf { - range: 145..161, - test: BooleanLiteral( - ExprBooleanLiteral { - range: 150..154, - value: true, - }, - ), - body: Name( + items: [ + DictItem { + key: Some( + Name( ExprName { - range: 145..146, - id: "x", + range: 137..138, + id: "a", ctx: Load, }, ), - orelse: Name( - ExprName { - range: 160..161, - id: "y", - ctx: Load, - }, - ), - }, - ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 140..141, + value: Int( + 1, + ), + }, + ), + }, + DictItem { + key: None, + value: If( + ExprIf { + range: 145..161, + test: BooleanLiteral( + ExprBooleanLiteral { + range: 150..154, + value: true, + }, + ), + body: Name( + ExprName { + range: 145..146, + id: "x", + ctx: Load, + }, + ), + orelse: Name( + ExprName { + range: 160..161, + id: "y", + ctx: Load, + }, + ), + }, + ), + }, ], }, ), @@ -111,62 +115,64 @@ Module( value: Dict( ExprDict { range: 163..184, - keys: [ - None, - Some( - Name( - ExprName { - range: 179..180, - id: "b", - ctx: Load, - }, - ), - ), - ], - values: [ - Lambda( - ExprLambda { - range: 166..177, - parameters: Some( - Parameters { - range: 173..174, - posonlyargs: [], - args: [ - ParameterWithDefault { - range: 173..174, - parameter: Parameter { + items: [ + DictItem { + key: None, + value: Lambda( + ExprLambda { + range: 166..177, + parameters: Some( + Parameters { + range: 173..174, + posonlyargs: [], + args: [ + ParameterWithDefault { range: 173..174, - name: Identifier { - id: "x", + parameter: Parameter { range: 173..174, + name: Identifier { + id: "x", + range: 173..174, + }, + annotation: None, }, - annotation: None, + default: None, }, - default: None, - }, - ], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - ), - body: Name( + ], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + ), + body: Name( + ExprName { + range: 176..177, + id: "x", + ctx: Load, + }, + ), + }, + ), + }, + DictItem { + key: Some( + Name( ExprName { - range: 176..177, - id: "x", + range: 179..180, + id: "b", ctx: Load, }, ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 182..183, - value: Int( - 2, - ), - }, - ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 182..183, + value: Int( + 2, + ), + }, + ), + }, ], }, ), @@ -178,49 +184,51 @@ Module( value: Dict( ExprDict { range: 185..201, - keys: [ - Some( - Name( - ExprName { - range: 186..187, - id: "a", - ctx: Load, - }, - ), - ), - None, - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 189..190, - value: Int( - 1, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 186..187, + id: "a", + ctx: Load, + }, ), - }, - ), - BoolOp( - ExprBoolOp { - range: 194..200, - op: Or, - values: [ - Name( - ExprName { - range: 194..195, - id: "x", - ctx: Load, - }, - ), - Name( - ExprName { - range: 199..200, - id: "y", - ctx: Load, - }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 189..190, + value: Int( + 1, ), - ], - }, - ), + }, + ), + }, + DictItem { + key: None, + value: BoolOp( + ExprBoolOp { + range: 194..200, + op: Or, + values: [ + Name( + ExprName { + range: 194..195, + id: "x", + ctx: Load, + }, + ), + Name( + ExprName { + range: 199..200, + id: "y", + ctx: Load, + }, + ), + ], + }, + ), + }, ], }, ), @@ -232,49 +240,51 @@ Module( value: Dict( ExprDict { range: 202..219, - keys: [ - None, - Some( - Name( - ExprName { - range: 214..215, - id: "b", - ctx: Load, + items: [ + DictItem { + key: None, + value: BoolOp( + ExprBoolOp { + range: 205..212, + op: And, + values: [ + Name( + ExprName { + range: 205..206, + id: "x", + ctx: Load, + }, + ), + Name( + ExprName { + range: 211..212, + id: "y", + ctx: Load, + }, + ), + ], }, ), - ), - ], - values: [ - BoolOp( - ExprBoolOp { - range: 205..212, - op: And, - values: [ - Name( - ExprName { - range: 205..206, - id: "x", - ctx: Load, - }, - ), - Name( - ExprName { - range: 211..212, - id: "y", - ctx: Load, - }, - ), - ], - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 217..218, - value: Int( - 2, + }, + DictItem { + key: Some( + Name( + ExprName { + range: 214..215, + id: "b", + ctx: Load, + }, ), - }, - ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 217..218, + value: Int( + 2, + ), + }, + ), + }, ], }, ), @@ -286,57 +296,61 @@ Module( value: Dict( ExprDict { range: 220..241, - keys: [ - Some( - Name( - ExprName { - range: 221..222, - id: "a", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 221..222, + id: "a", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 224..225, + value: Int( + 1, + ), }, ), - ), - None, - Some( - Name( - ExprName { - range: 236..237, - id: "b", - ctx: Load, + }, + DictItem { + key: None, + value: UnaryOp( + ExprUnaryOp { + range: 229..234, + op: Not, + operand: Name( + ExprName { + range: 233..234, + id: "x", + ctx: Load, + }, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 224..225, - value: Int( - 1, - ), - }, - ), - UnaryOp( - ExprUnaryOp { - range: 229..234, - op: Not, - operand: Name( + }, + DictItem { + key: Some( + Name( ExprName { - range: 233..234, - id: "x", + range: 236..237, + id: "b", ctx: Load, }, ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 239..240, - value: Int( - 2, - ), - }, - ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 239..240, + value: Int( + 2, + ), + }, + ), + }, ], }, ), @@ -348,34 +362,34 @@ Module( value: Dict( ExprDict { range: 242..252, - keys: [ - None, - ], - values: [ - Compare( - ExprCompare { - range: 245..251, - left: Name( - ExprName { - range: 245..246, - id: "x", - ctx: Load, - }, - ), - ops: [ - In, - ], - comparators: [ - Name( + items: [ + DictItem { + key: None, + value: Compare( + ExprCompare { + range: 245..251, + left: Name( ExprName { - range: 250..251, - id: "y", + range: 245..246, + id: "x", ctx: Load, }, ), - ], - }, - ), + ops: [ + In, + ], + comparators: [ + Name( + ExprName { + range: 250..251, + id: "y", + ctx: Load, + }, + ), + ], + }, + ), + }, ], }, ), @@ -387,34 +401,34 @@ Module( value: Dict( ExprDict { range: 253..267, - keys: [ - None, - ], - values: [ - Compare( - ExprCompare { - range: 256..266, - left: Name( - ExprName { - range: 256..257, - id: "x", - ctx: Load, - }, - ), - ops: [ - NotIn, - ], - comparators: [ - Name( + items: [ + DictItem { + key: None, + value: Compare( + ExprCompare { + range: 256..266, + left: Name( ExprName { - range: 265..266, - id: "y", + range: 256..257, + id: "x", ctx: Load, }, ), - ], - }, - ), + ops: [ + NotIn, + ], + comparators: [ + Name( + ExprName { + range: 265..266, + id: "y", + ctx: Load, + }, + ), + ], + }, + ), + }, ], }, ), @@ -426,34 +440,34 @@ Module( value: Dict( ExprDict { range: 268..277, - keys: [ - None, - ], - values: [ - Compare( - ExprCompare { - range: 271..276, - left: Name( - ExprName { - range: 271..272, - id: "x", - ctx: Load, - }, - ), - ops: [ - Lt, - ], - comparators: [ - Name( + items: [ + DictItem { + key: None, + value: Compare( + ExprCompare { + range: 271..276, + left: Name( ExprName { - range: 275..276, - id: "y", + range: 271..272, + id: "x", ctx: Load, }, ), - ], - }, - ), + ops: [ + Lt, + ], + comparators: [ + Name( + ExprName { + range: 275..276, + id: "y", + ctx: Load, + }, + ), + ], + }, + ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap index d1711b33cab2a6..a5e9951e6dfee4 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__double_star_comprehension.py.snap @@ -15,82 +15,88 @@ Module( value: Dict( ExprDict { range: 122..147, - keys: [ - None, - Some( - Name( + items: [ + DictItem { + key: None, + value: Name( ExprName { - range: 128..129, - id: "y", + range: 125..126, + id: "x", ctx: Load, }, ), - ), - Some( - Name( + }, + DictItem { + key: Some( + Name( + ExprName { + range: 128..129, + id: "y", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 134..135, - id: "x", + range: 130..133, + id: "for", ctx: Load, }, ), - ), - Some( - Compare( - ExprCompare { - range: 137..146, - left: Name( - ExprName { - range: 137..138, - id: "y", - ctx: Load, - }, - ), - ops: [ - In, - ], - comparators: [ - Name( + }, + DictItem { + key: Some( + Name( + ExprName { + range: 134..135, + id: "x", + ctx: Load, + }, + ), + ), + value: Name( + ExprName { + range: 135..135, + id: "", + ctx: Invalid, + }, + ), + }, + DictItem { + key: Some( + Compare( + ExprCompare { + range: 137..146, + left: Name( ExprName { - range: 142..146, - id: "data", + range: 137..138, + id: "y", ctx: Load, }, ), - ], + ops: [ + In, + ], + comparators: [ + Name( + ExprName { + range: 142..146, + id: "data", + ctx: Load, + }, + ), + ], + }, + ), + ), + value: Name( + ExprName { + range: 146..146, + id: "", + ctx: Invalid, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 125..126, - id: "x", - ctx: Load, - }, - ), - Name( - ExprName { - range: 130..133, - id: "for", - ctx: Load, - }, - ), - Name( - ExprName { - range: 135..135, - id: "", - ctx: Invalid, - }, - ), - Name( - ExprName { - range: 146..146, - id: "", - ctx: Invalid, - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap index 0075084040a8c0..f6729651186745 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_0.py.snap @@ -15,51 +15,53 @@ Module( value: Dict( ExprDict { range: 0..24, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 1..2, + id: "x", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 1..2, - id: "x", + range: 5..8, + id: "def", ctx: Load, }, ), - ), - Some( - Call( - ExprCall { - range: 9..14, - func: Name( - ExprName { - range: 9..12, - id: "foo", - ctx: Load, + }, + DictItem { + key: Some( + Call( + ExprCall { + range: 9..14, + func: Name( + ExprName { + range: 9..12, + id: "foo", + ctx: Load, + }, + ), + arguments: Arguments { + range: 12..14, + args: [], + keywords: [], }, - ), - arguments: Arguments { - range: 12..14, - args: [], - keywords: [], }, + ), + ), + value: Name( + ExprName { + range: 20..24, + id: "pass", + ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 5..8, - id: "def", - ctx: Load, - }, - ), - Name( - ExprName { - range: 20..24, - id: "pass", - ctx: Load, - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap index 98278802e7eeff..e475ac928d5775 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_1.py.snap @@ -15,40 +15,40 @@ Module( value: Dict( ExprDict { range: 0..10, - keys: [ - Some( - Name( - ExprName { - range: 1..2, - id: "x", - ctx: Load, - }, - ), - ), - ], - values: [ - BinOp( - ExprBinOp { - range: 5..10, - left: NumberLiteral( - ExprNumberLiteral { - range: 5..6, - value: Int( - 1, - ), - }, - ), - op: Add, - right: NumberLiteral( - ExprNumberLiteral { - range: 9..10, - value: Int( - 2, - ), + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 1..2, + id: "x", + ctx: Load, }, ), - }, - ), + ), + value: BinOp( + ExprBinOp { + range: 5..10, + left: NumberLiteral( + ExprNumberLiteral { + range: 5..6, + value: Int( + 1, + ), + }, + ), + op: Add, + right: NumberLiteral( + ExprNumberLiteral { + range: 9..10, + value: Int( + 2, + ), + }, + ), + }, + ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap index 1dd134ac77bf65..d60ca66d0a46f8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__missing_closing_brace_2.py.snap @@ -15,26 +15,26 @@ Module( value: Dict( ExprDict { range: 0..6, - keys: [ - Some( - Name( - ExprName { - range: 1..2, - id: "x", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 1..2, + id: "x", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 4..5, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 4..5, - value: Int( - 1, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap index bddf3d3b1dfdac..311a18cd33ddc2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_0.py.snap @@ -15,71 +15,75 @@ Module( value: Dict( ExprDict { range: 55..77, - keys: [ - Some( - Named( - ExprNamed { - range: 56..62, - target: Name( - ExprName { - range: 56..57, - id: "x", - ctx: Store, - }, - ), - value: NumberLiteral( - ExprNumberLiteral { - range: 61..62, - value: Int( - 1, - ), - }, - ), - }, + items: [ + DictItem { + key: Some( + Named( + ExprNamed { + range: 56..62, + target: Name( + ExprName { + range: 56..57, + id: "x", + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 61..62, + value: Int( + 1, + ), + }, + ), + }, + ), ), - ), - Some( - Name( + value: Name( ExprName { - range: 67..68, - id: "z", + range: 64..65, + id: "y", ctx: Load, }, ), - ), - Some( - NumberLiteral( - ExprNumberLiteral { - range: 72..73, - value: Int( - 2, - ), + }, + DictItem { + key: Some( + Name( + ExprName { + range: 67..68, + id: "z", + ctx: Load, + }, + ), + ), + value: Name( + ExprName { + range: 68..68, + id: "", + ctx: Invalid, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 64..65, - id: "y", - ctx: Load, - }, - ), - Name( - ExprName { - range: 68..68, - id: "", - ctx: Invalid, - }, - ), - Name( - ExprName { - range: 75..76, - id: "a", - ctx: Load, - }, - ), + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 72..73, + value: Int( + 2, + ), + }, + ), + ), + value: Name( + ExprName { + range: 75..76, + id: "a", + ctx: Load, + }, + ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap index e2aabe28da3653..09964f98bfd203 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__named_expression_1.py.snap @@ -15,75 +15,81 @@ Module( value: Dict( ExprDict { range: 57..79, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 58..59, + id: "x", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 58..59, - id: "x", + range: 61..62, + id: "y", ctx: Load, }, ), - ), - Some( - NumberLiteral( - ExprNumberLiteral { - range: 66..67, - value: Int( - 1, - ), + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 66..67, + value: Int( + 1, + ), + }, + ), + ), + value: Name( + ExprName { + range: 67..67, + id: "", + ctx: Invalid, }, ), - ), - Some( - Name( + }, + DictItem { + key: Some( + Name( + ExprName { + range: 69..70, + id: "z", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 69..70, - id: "z", + range: 72..73, + id: "a", ctx: Load, }, ), - ), - Some( - NumberLiteral( - ExprNumberLiteral { - range: 77..78, - value: Int( - 2, - ), + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 77..78, + value: Int( + 2, + ), + }, + ), + ), + value: Name( + ExprName { + range: 78..78, + id: "", + ctx: Invalid, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 61..62, - id: "y", - ctx: Load, - }, - ), - Name( - ExprName { - range: 67..67, - id: "", - ctx: Invalid, - }, - ), - Name( - ExprName { - range: 72..73, - id: "a", - ctx: Load, - }, - ), - Name( - ExprName { - range: 78..78, - id: "", - ctx: Invalid, - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap index c64d7ddf65af73..4c7b359e7ea0c8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__dict__recover.py.snap @@ -34,45 +34,47 @@ Module( value: Dict( ExprDict { range: 93..105, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 94..95, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 94..95, + range: 97..98, value: Int( - 1, + 2, ), }, ), - ), - Some( - NumberLiteral( + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 100..101, + value: Int( + 3, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 100..101, + range: 103..104, value: Int( - 3, + 4, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 97..98, - value: Int( - 2, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 103..104, - value: Int( - 4, - ), - }, - ), + }, ], }, ), @@ -84,27 +86,27 @@ Module( value: Dict( ExprDict { range: 107..115, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 108..109, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 108..109, + range: 111..112, value: Int( - 1, + 2, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 111..112, - value: Int( - 2, - ), - }, - ), + }, ], }, ), @@ -116,45 +118,47 @@ Module( value: Dict( ExprDict { range: 133..144, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 134..135, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 134..135, + range: 137..138, value: Int( - 1, + 2, ), }, ), - ), - Some( - NumberLiteral( + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 139..140, + value: Int( + 3, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 139..140, + range: 142..143, value: Int( - 3, + 4, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 137..138, - value: Int( - 2, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 142..143, - value: Int( - 4, - ), - }, - ), + }, ], }, ), @@ -166,26 +170,26 @@ Module( value: Dict( ExprDict { range: 157..162, - keys: [ - Some( - NumberLiteral( - ExprNumberLiteral { - range: 158..159, - value: Int( - 1, - ), + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 158..159, + value: Int( + 1, + ), + }, + ), + ), + value: Name( + ExprName { + range: 160..160, + id: "", + ctx: Invalid, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 160..160, - id: "", - ctx: Invalid, - }, - ), + }, ], }, ), @@ -197,17 +201,17 @@ Module( value: Dict( ExprDict { range: 201..205, - keys: [ - None, - ], - values: [ - Name( - ExprName { - range: 204..204, - id: "", - ctx: Invalid, - }, - ), + items: [ + DictItem { + key: None, + value: Name( + ExprName { + range: 204..204, + id: "", + ctx: Invalid, + }, + ), + }, ], }, ), @@ -219,49 +223,53 @@ Module( value: Dict( ExprDict { range: 206..222, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 207..208, + id: "x", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 207..208, - id: "x", + range: 210..211, + id: "y", ctx: Load, }, ), - ), - None, - Some( - Name( + }, + DictItem { + key: None, + value: Name( ExprName { - range: 217..218, - id: "a", + range: 215..215, + id: "", + ctx: Invalid, + }, + ), + }, + DictItem { + key: Some( + Name( + ExprName { + range: 217..218, + id: "a", + ctx: Load, + }, + ), + ), + value: Name( + ExprName { + range: 220..221, + id: "b", ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 210..211, - id: "y", - ctx: Load, - }, - ), - Name( - ExprName { - range: 215..215, - id: "", - ctx: Invalid, - }, - ), - Name( - ExprName { - range: 220..221, - id: "b", - ctx: Load, - }, - ), + }, ], }, ), @@ -273,69 +281,73 @@ Module( value: Dict( ExprDict { range: 310..330, - keys: [ - Some( - Starred( - ExprStarred { - range: 311..313, - value: Name( - ExprName { - range: 312..313, - id: "x", - ctx: Load, - }, - ), + items: [ + DictItem { + key: Some( + Starred( + ExprStarred { + range: 311..313, + value: Name( + ExprName { + range: 312..313, + id: "x", + ctx: Load, + }, + ), + ctx: Load, + }, + ), + ), + value: Name( + ExprName { + range: 315..316, + id: "y", ctx: Load, }, ), - ), - Some( - Name( + }, + DictItem { + key: Some( + Name( + ExprName { + range: 318..319, + id: "z", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 318..319, - id: "z", + range: 321..322, + id: "a", ctx: Load, }, ), - ), - Some( - Starred( - ExprStarred { - range: 324..326, - value: Name( - ExprName { - range: 325..326, - id: "b", - ctx: Load, - }, - ), + }, + DictItem { + key: Some( + Starred( + ExprStarred { + range: 324..326, + value: Name( + ExprName { + range: 325..326, + id: "b", + ctx: Load, + }, + ), + ctx: Load, + }, + ), + ), + value: Name( + ExprName { + range: 328..329, + id: "c", ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 315..316, - id: "y", - ctx: Load, - }, - ), - Name( - ExprName { - range: 321..322, - id: "a", - ctx: Load, - }, - ), - Name( - ExprName { - range: 328..329, - id: "c", - ctx: Load, - }, - ), + }, ], }, ), @@ -347,53 +359,55 @@ Module( value: Dict( ExprDict { range: 331..345, - keys: [ - Some( - Name( - ExprName { - range: 332..333, - id: "x", - ctx: Load, - }, - ), - ), - Some( - Name( - ExprName { - range: 339..340, - id: "z", - ctx: Load, - }, - ), - ), - ], - values: [ - Starred( - ExprStarred { - range: 335..337, - value: Name( + items: [ + DictItem { + key: Some( + Name( ExprName { - range: 336..337, - id: "y", + range: 332..333, + id: "x", ctx: Load, }, ), - ctx: Load, - }, - ), - Starred( - ExprStarred { - range: 342..344, - value: Name( + ), + value: Starred( + ExprStarred { + range: 335..337, + value: Name( + ExprName { + range: 336..337, + id: "y", + ctx: Load, + }, + ), + ctx: Load, + }, + ), + }, + DictItem { + key: Some( + Name( ExprName { - range: 343..344, - id: "a", + range: 339..340, + id: "z", ctx: Load, }, ), - ctx: Load, - }, - ), + ), + value: Starred( + ExprStarred { + range: 342..344, + value: Name( + ExprName { + range: 343..344, + id: "a", + ctx: Load, + }, + ), + ctx: Load, + }, + ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap index ba84d23b8954a2..946b061be1f436 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__set__recover.py.snap @@ -110,27 +110,27 @@ Module( value: Dict( ExprDict { range: 271..277, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 272..273, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 272..273, + range: 275..276, value: Int( - 1, + 2, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 275..276, - value: Int( - 2, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap index 1b2fcfa110017a..d32507dc88cdd6 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@invalid_del_target.py.snap @@ -44,37 +44,37 @@ Module( Dict( ExprDict { range: 14..22, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 15..18, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 15..18, - value: "x", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 15..18, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 15..18, + value: "x", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 20..21, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 20..21, - value: Int( - 1, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap index 1c83da2091dbfe..dd1e0636d5f8c0 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@params_var_keyword_with_default.py.snap @@ -57,65 +57,67 @@ Module( value: Dict( ExprDict { range: 20..36, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 21..24, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 21..24, - value: "b", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 21..24, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 21..24, + value: "b", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 26..27, + value: Int( + 1, + ), }, ), - ), - Some( - StringLiteral( - ExprStringLiteral { - range: 29..32, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 29..32, - value: "c", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + }, + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 29..32, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 29..32, + value: "c", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 34..35, + value: Int( + 2, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 26..27, - value: Int( - 1, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 34..35, - value: Int( - 2, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap index d7812bcae53889..393c745df591e8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_assignment_targets.py.snap @@ -319,37 +319,37 @@ Module( Dict( ExprDict { range: 386..394, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 387..390, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 387..390, - value: "a", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 387..390, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 387..390, + value: "a", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 392..393, + value: Int( + 5, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 392..393, - value: Int( - 5, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap index 9681d152d42f27..92fa9327512f32 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__invalid_augmented_assignment_target.py.snap @@ -236,37 +236,37 @@ Module( target: Dict( ExprDict { range: 186..194, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 187..190, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 187..190, - value: "a", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 187..190, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 187..190, + value: "a", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 192..193, + value: Int( + 5, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 192..193, - value: Int( - 5, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap index 857ca2e275d5ff..bd6b87ab9f5b14 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__as_pattern_3.py.snap @@ -28,8 +28,7 @@ Module( cls: Dict( ExprDict { range: 108..109, - keys: [], - values: [], + items: [], }, ), arguments: PatternArguments { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap index ef1241b3a7eb3d..2185fc051d68d5 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__invalid_lhs_or_rhs_pattern.py.snap @@ -263,25 +263,25 @@ Module( left: Dict( ExprDict { range: 201..210, - keys: [ - Some( - BooleanLiteral( - ExprBooleanLiteral { - range: 202..206, - value: true, + items: [ + DictItem { + key: Some( + BooleanLiteral( + ExprBooleanLiteral { + range: 202..206, + value: true, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 208..209, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 208..209, - value: Int( - 1, - ), - }, - ), + }, ], }, ), @@ -711,25 +711,25 @@ Module( right: Dict( ExprDict { range: 534..543, - keys: [ - Some( - BooleanLiteral( - ExprBooleanLiteral { - range: 535..539, - value: true, + items: [ + DictItem { + key: Some( + BooleanLiteral( + ExprBooleanLiteral { + range: 535..539, + value: true, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 541..542, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 541..542, - value: Int( - 1, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap index 120bcda08388b7..e000166f052dd4 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__await.py.snap @@ -192,26 +192,26 @@ Module( value: Dict( ExprDict { range: 76..82, - keys: [ - Some( - Name( - ExprName { - range: 77..78, - id: "i", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 77..78, + id: "i", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 80..81, + value: Int( + 5, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 80..81, - value: Int( - 5, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap index 968d3892858979..7234aa72add9d8 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__call.py.snap @@ -360,45 +360,47 @@ Module( func: Dict( ExprDict { range: 219..231, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 220..221, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 220..221, + range: 223..224, value: Int( - 1, + 2, ), }, ), - ), - Some( - NumberLiteral( + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 226..227, + value: Int( + 3, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 226..227, + range: 229..230, value: Int( - 3, + 4, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 223..224, - value: Int( - 2, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 229..230, - value: Int( - 4, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap index 55547247c89948..502274b405e345 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__dictionary.py.snap @@ -15,8 +15,7 @@ Module( value: Dict( ExprDict { range: 9..11, - keys: [], - values: [], + items: [], }, ), }, @@ -27,27 +26,27 @@ Module( value: Dict( ExprDict { range: 12..18, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 13..14, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 13..14, + range: 16..17, value: Int( - 1, + 2, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 16..17, - value: Int( - 2, - ), - }, - ), + }, ], }, ), @@ -59,71 +58,75 @@ Module( value: Dict( ExprDict { range: 19..43, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 20..21, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 20..21, + range: 23..24, value: Int( - 1, + 2, ), }, ), - ), - Some( - Name( - ExprName { - range: 26..27, - id: "a", - ctx: Load, - }, + }, + DictItem { + key: Some( + Name( + ExprName { + range: 26..27, + id: "a", + ctx: Load, + }, + ), ), - ), - Some( - Name( - ExprName { - range: 32..33, - id: "b", - ctx: Load, + value: NumberLiteral( + ExprNumberLiteral { + range: 29..30, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 23..24, - value: Int( - 2, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 29..30, - value: Int( - 1, + }, + DictItem { + key: Some( + Name( + ExprName { + range: 32..33, + id: "b", + ctx: Load, + }, ), - }, - ), - StringLiteral( - ExprStringLiteral { - range: 35..42, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 35..42, - value: "hello", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + ), + value: StringLiteral( + ExprStringLiteral { + range: 35..42, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 35..42, + value: "hello", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, - }, - ), + ), + }, ], }, ), @@ -135,8 +138,7 @@ Module( value: Dict( ExprDict { range: 66..69, - keys: [], - values: [], + items: [], }, ), }, @@ -147,45 +149,47 @@ Module( value: Dict( ExprDict { range: 70..100, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 76..77, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 76..77, + range: 83..84, value: Int( - 1, + 2, ), }, ), - ), - Some( - NumberLiteral( + }, + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 90..91, + value: Int( + 3, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 90..91, + range: 97..98, value: Int( - 3, + 4, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 83..84, - value: Int( - 2, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 97..98, - value: Int( - 4, - ), - }, - ), + }, ], }, ), @@ -197,84 +201,84 @@ Module( value: Dict( ExprDict { range: 111..132, - keys: [ - Some( - Dict( - ExprDict { - range: 112..118, - keys: [ - Some( - NumberLiteral( - ExprNumberLiteral { - range: 113..114, - value: Int( - 1, - ), - }, - ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 116..117, - value: Int( - 2, - ), - }, - ), - ], - }, - ), - ), - ], - values: [ - Dict( - ExprDict { - range: 120..131, - keys: [ - Some( - NumberLiteral( - ExprNumberLiteral { - range: 121..122, - value: Int( - 3, - ), - }, - ), - ), - ], - values: [ - Dict( - ExprDict { - range: 124..130, - keys: [ - Some( + items: [ + DictItem { + key: Some( + Dict( + ExprDict { + range: 112..118, + items: [ + DictItem { + key: Some( NumberLiteral( ExprNumberLiteral { - range: 125..126, + range: 113..114, value: Int( - 4, + 1, ), }, ), ), - ], - values: [ + value: NumberLiteral( + ExprNumberLiteral { + range: 116..117, + value: Int( + 2, + ), + }, + ), + }, + ], + }, + ), + ), + value: Dict( + ExprDict { + range: 120..131, + items: [ + DictItem { + key: Some( NumberLiteral( ExprNumberLiteral { - range: 128..129, + range: 121..122, value: Int( - 5, + 3, ), }, ), - ], + ), + value: Dict( + ExprDict { + range: 124..130, + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 125..126, + value: Int( + 4, + ), + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 128..129, + value: Int( + 5, + ), + }, + ), + }, + ], + }, + ), }, - ), - ], - }, - ), + ], + }, + ), + }, ], }, ), @@ -286,54 +290,54 @@ Module( value: Dict( ExprDict { range: 155..171, - keys: [ - Some( - Lambda( - ExprLambda { - range: 156..167, - parameters: Some( - Parameters { - range: 163..164, - posonlyargs: [], - args: [ - ParameterWithDefault { - range: 163..164, - parameter: Parameter { + items: [ + DictItem { + key: Some( + Lambda( + ExprLambda { + range: 156..167, + parameters: Some( + Parameters { + range: 163..164, + posonlyargs: [], + args: [ + ParameterWithDefault { range: 163..164, - name: Identifier { - id: "x", + parameter: Parameter { range: 163..164, + name: Identifier { + id: "x", + range: 163..164, + }, + annotation: None, }, - annotation: None, + default: None, }, - default: None, - }, - ], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - ), - body: Name( - ExprName { - range: 166..167, - id: "x", - ctx: Load, - }, + ], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + ), + body: Name( + ExprName { + range: 166..167, + id: "x", + ctx: Load, + }, + ), + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 169..170, + value: Int( + 1, ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 169..170, - value: Int( - 1, - ), - }, - ), + }, ], }, ), @@ -345,89 +349,91 @@ Module( value: Dict( ExprDict { range: 172..202, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 173..176, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 173..176, - value: "A", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 173..176, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 173..176, + value: "A", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, - }, + ), ), - ), - Some( - StringLiteral( - ExprStringLiteral { - range: 194..197, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 194..197, - value: "B", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + value: Lambda( + ExprLambda { + range: 178..192, + parameters: Some( + Parameters { + range: 185..186, + posonlyargs: [], + args: [ + ParameterWithDefault { + range: 185..186, + parameter: Parameter { + range: 185..186, + name: Identifier { + id: "p", + range: 185..186, + }, + annotation: None, + }, + default: None, }, - }, - ), - }, + ], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + ), + body: NoneLiteral( + ExprNoneLiteral { + range: 188..192, + }, + ), }, ), - ), - ], - values: [ - Lambda( - ExprLambda { - range: 178..192, - parameters: Some( - Parameters { - range: 185..186, - posonlyargs: [], - args: [ - ParameterWithDefault { - range: 185..186, - parameter: Parameter { - range: 185..186, - name: Identifier { - id: "p", - range: 185..186, + }, + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 194..197, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 194..197, + value: "B", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, }, - annotation: None, }, - default: None, - }, - ], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - ), - body: NoneLiteral( - ExprNoneLiteral { - range: 188..192, + ), + }, }, ), - }, - ), - Name( - ExprName { - range: 199..200, - id: "C", - ctx: Load, - }, - ), + ), + value: Name( + ExprName { + range: 199..200, + id: "C", + ctx: Load, + }, + ), + }, ], }, ), @@ -439,38 +445,38 @@ Module( value: Dict( ExprDict { range: 224..237, - keys: [ - Some( - Named( - ExprNamed { - range: 226..232, - target: Name( - ExprName { - range: 226..227, - id: "x", - ctx: Store, - }, - ), - value: NumberLiteral( - ExprNumberLiteral { - range: 231..232, - value: Int( - 1, - ), - }, - ), + items: [ + DictItem { + key: Some( + Named( + ExprNamed { + range: 226..232, + target: Name( + ExprName { + range: 226..227, + id: "x", + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 231..232, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + value: Name( + ExprName { + range: 235..236, + id: "y", + ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 235..236, - id: "y", - ctx: Load, - }, - ), + }, ], }, ), @@ -482,51 +488,51 @@ Module( value: Dict( ExprDict { range: 238..258, - keys: [ - Some( - Named( + items: [ + DictItem { + key: Some( + Named( + ExprNamed { + range: 240..246, + target: Name( + ExprName { + range: 240..241, + id: "x", + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 245..246, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + value: Named( ExprNamed { - range: 240..246, + range: 250..256, target: Name( ExprName { - range: 240..241, - id: "x", + range: 250..251, + id: "y", ctx: Store, }, ), value: NumberLiteral( ExprNumberLiteral { - range: 245..246, + range: 255..256, value: Int( - 1, + 2, ), }, ), }, ), - ), - ], - values: [ - Named( - ExprNamed { - range: 250..256, - target: Name( - ExprName { - range: 250..251, - id: "y", - ctx: Store, - }, - ), - value: NumberLiteral( - ExprNumberLiteral { - range: 255..256, - value: Int( - 2, - ), - }, - ), - }, - ), + }, ], }, ), @@ -538,17 +544,17 @@ Module( value: Dict( ExprDict { range: 284..289, - keys: [ - None, - ], - values: [ - Name( - ExprName { - range: 287..288, - id: "d", - ctx: Load, - }, - ), + items: [ + DictItem { + key: None, + value: Name( + ExprName { + range: 287..288, + id: "d", + ctx: Load, + }, + ), + }, ], }, ), @@ -560,33 +566,35 @@ Module( value: Dict( ExprDict { range: 290..301, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 291..292, + id: "a", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 291..292, - id: "a", + range: 294..295, + id: "b", ctx: Load, }, ), - ), - None, - ], - values: [ - Name( - ExprName { - range: 294..295, - id: "b", - ctx: Load, - }, - ), - Name( - ExprName { - range: 299..300, - id: "d", - ctx: Load, - }, - ), + }, + DictItem { + key: None, + value: Name( + ExprName { + range: 299..300, + id: "d", + ctx: Load, + }, + ), + }, ], }, ), @@ -598,25 +606,27 @@ Module( value: Dict( ExprDict { range: 302..312, - keys: [ - None, - None, - ], - values: [ - Name( - ExprName { - range: 305..306, - id: "a", - ctx: Load, - }, - ), - Name( - ExprName { - range: 310..311, - id: "b", - ctx: Load, - }, - ), + items: [ + DictItem { + key: None, + value: Name( + ExprName { + range: 305..306, + id: "a", + ctx: Load, + }, + ), + }, + DictItem { + key: None, + value: Name( + ExprName { + range: 310..311, + id: "b", + ctx: Load, + }, + ), + }, ], }, ), @@ -628,16 +638,36 @@ Module( value: Dict( ExprDict { range: 313..338, - keys: [ - Some( - StringLiteral( + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 314..317, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 314..317, + value: "a", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ), + value: StringLiteral( ExprStringLiteral { - range: 314..317, + range: 319..322, value: StringLiteralValue { inner: Single( StringLiteral { - range: 314..317, - value: "a", + range: 319..322, + value: "b", flags: StringLiteralFlags { quote_style: Double, prefix: Empty, @@ -648,17 +678,46 @@ Module( }, }, ), - ), - None, - Some( - StringLiteral( + }, + DictItem { + key: None, + value: Name( + ExprName { + range: 326..327, + id: "c", + ctx: Load, + }, + ), + }, + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 329..332, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 329..332, + value: "d", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ), + value: StringLiteral( ExprStringLiteral { - range: 329..332, + range: 334..337, value: StringLiteralValue { inner: Single( StringLiteral { - range: 329..332, - value: "d", + range: 334..337, + value: "e", flags: StringLiteralFlags { quote_style: Double, prefix: Empty, @@ -669,52 +728,7 @@ Module( }, }, ), - ), - ], - values: [ - StringLiteral( - ExprStringLiteral { - range: 319..322, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 319..322, - value: "b", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, - }, - }, - ), - }, - }, - ), - Name( - ExprName { - range: 326..327, - id: "c", - ctx: Load, - }, - ), - StringLiteral( - ExprStringLiteral { - range: 334..337, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 334..337, - value: "e", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, - }, - }, - ), - }, - }, - ), + }, ], }, ), @@ -726,75 +740,77 @@ Module( value: Dict( ExprDict { range: 339..367, - keys: [ - Some( - NumberLiteral( + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 340..341, + value: Int( + 1, + ), + }, + ), + ), + value: NumberLiteral( ExprNumberLiteral { - range: 340..341, + range: 343..344, value: Int( - 1, + 2, ), }, ), - ), - None, - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 343..344, - value: Int( - 2, - ), - }, - ), - Dict( - ExprDict { - range: 348..366, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 349..357, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 349..357, - value: "nested", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, - }, - }, - ), - }, - }, - ), - ), - ], - values: [ - StringLiteral( - ExprStringLiteral { - range: 359..365, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 359..365, - value: "dict", - flags: StringLiteralFlags { - quote_style: Single, - prefix: Empty, - triple_quoted: false, + }, + DictItem { + key: None, + value: Dict( + ExprDict { + range: 348..366, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 349..357, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 349..357, + value: "nested", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), }, }, ), - }, + ), + value: StringLiteral( + ExprStringLiteral { + range: 359..365, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 359..365, + value: "dict", + flags: StringLiteralFlags { + quote_style: Single, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), }, - ), - ], - }, - ), + ], + }, + ), + }, ], }, ), @@ -806,71 +822,73 @@ Module( value: Dict( ExprDict { range: 368..393, - keys: [ - Some( - BinOp( + items: [ + DictItem { + key: Some( + BinOp( + ExprBinOp { + range: 369..374, + left: Name( + ExprName { + range: 369..370, + id: "x", + ctx: Load, + }, + ), + op: Mult, + right: NumberLiteral( + ExprNumberLiteral { + range: 373..374, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + value: BinOp( ExprBinOp { - range: 369..374, + range: 376..382, left: Name( ExprName { - range: 369..370, - id: "x", + range: 376..377, + id: "y", ctx: Load, }, ), - op: Mult, + op: Pow, right: NumberLiteral( ExprNumberLiteral { - range: 373..374, + range: 381..382, value: Int( - 1, + 2, ), }, ), }, ), - ), - None, - ], - values: [ - BinOp( - ExprBinOp { - range: 376..382, - left: Name( - ExprName { - range: 376..377, - id: "y", - ctx: Load, - }, - ), - op: Pow, - right: NumberLiteral( - ExprNumberLiteral { - range: 381..382, - value: Int( - 2, - ), - }, - ), - }, - ), - Call( - ExprCall { - range: 386..392, - func: Name( - ExprName { - range: 386..390, - id: "call", - ctx: Load, + }, + DictItem { + key: None, + value: Call( + ExprCall { + range: 386..392, + func: Name( + ExprName { + range: 386..390, + id: "call", + ctx: Load, + }, + ), + arguments: Arguments { + range: 390..392, + args: [], + keywords: [], }, - ), - arguments: Arguments { - range: 390..392, - args: [], - keywords: [], }, - }, - ), + ), + }, ], }, ), @@ -882,23 +900,23 @@ Module( value: Dict( ExprDict { range: 460..471, - keys: [ - None, - ], - values: [ - UnaryOp( - ExprUnaryOp { - range: 464..469, - op: Not, - operand: Name( - ExprName { - range: 468..469, - id: "x", - ctx: Load, - }, - ), - }, - ), + items: [ + DictItem { + key: None, + value: UnaryOp( + ExprUnaryOp { + range: 464..469, + op: Not, + operand: Name( + ExprName { + range: 468..469, + id: "x", + ctx: Load, + }, + ), + }, + ), + }, ], }, ), @@ -910,44 +928,44 @@ Module( value: Dict( ExprDict { range: 494..515, - keys: [ - Some( - NumberLiteral( - ExprNumberLiteral { - range: 495..496, - value: Int( - 1, + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 495..496, + value: Int( + 1, + ), + }, + ), + ), + value: If( + ExprIf { + range: 498..514, + test: BooleanLiteral( + ExprBooleanLiteral { + range: 503..507, + value: true, + }, + ), + body: Name( + ExprName { + range: 498..499, + id: "x", + ctx: Load, + }, + ), + orelse: Name( + ExprName { + range: 513..514, + id: "y", + ctx: Load, + }, ), }, ), - ), - ], - values: [ - If( - ExprIf { - range: 498..514, - test: BooleanLiteral( - ExprBooleanLiteral { - range: 503..507, - value: true, - }, - ), - body: Name( - ExprName { - range: 498..499, - id: "x", - ctx: Load, - }, - ), - orelse: Name( - ExprName { - range: 513..514, - id: "y", - ctx: Load, - }, - ), - }, - ), + }, ], }, ), @@ -1079,78 +1097,80 @@ Module( value: Dict( ExprDict { range: 576..600, - keys: [ - Some( - Set( - ExprSet { - range: 577..583, - elts: [ - NumberLiteral( - ExprNumberLiteral { - range: 578..579, - value: Int( - 1, - ), - }, - ), - NumberLiteral( - ExprNumberLiteral { - range: 581..582, - value: Int( - 2, - ), - }, - ), - ], - }, + items: [ + DictItem { + key: Some( + Set( + ExprSet { + range: 577..583, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 578..579, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 581..582, + value: Int( + 2, + ), + }, + ), + ], + }, + ), ), - ), - Some( - Name( - ExprName { - range: 588..589, - id: "x", - ctx: Load, + value: NumberLiteral( + ExprNumberLiteral { + range: 585..586, + value: Int( + 3, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 585..586, - value: Int( - 3, + }, + DictItem { + key: Some( + Name( + ExprName { + range: 588..589, + id: "x", + ctx: Load, + }, ), - }, - ), - Dict( - ExprDict { - range: 591..598, - keys: [ - Some( - NumberLiteral( - ExprNumberLiteral { - range: 592..593, - value: Int( - 1, + ), + value: Dict( + ExprDict { + range: 591..598, + items: [ + DictItem { + key: Some( + NumberLiteral( + ExprNumberLiteral { + range: 592..593, + value: Int( + 1, + ), + }, ), - }, - ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 595..596, - value: Int( - 2, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 595..596, + value: Int( + 2, + ), + }, ), }, - ), - ], - }, - ), + ], + }, + ), + }, ], }, ), @@ -1162,41 +1182,43 @@ Module( value: Dict( ExprDict { range: 601..621, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 603..604, + id: "x", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 603..604, - id: "x", + range: 608..609, + id: "y", ctx: Load, }, ), - ), - Some( - Name( + }, + DictItem { + key: Some( + Name( + ExprName { + range: 613..614, + id: "z", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 613..614, - id: "z", + range: 618..619, + id: "a", ctx: Load, }, ), - ), - ], - values: [ - Name( - ExprName { - range: 608..609, - id: "y", - ctx: Load, - }, - ), - Name( - ExprName { - range: 618..619, - id: "a", - ctx: Load, - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap index 4f94e707968f5b..cc523d29a6d896 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__f_string.py.snap @@ -823,104 +823,104 @@ Module( value: Dict( ExprDict { range: 219..253, - keys: [ - Some( - FString( - ExprFString { - range: 220..248, - value: FStringValue { - inner: Concatenated( - [ - Literal( - StringLiteral { - range: 220..226, - value: "foo ", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + FString( + ExprFString { + range: 220..248, + value: FStringValue { + inner: Concatenated( + [ + Literal( + StringLiteral { + range: 220..226, + value: "foo ", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), - FString( - FString { - range: 227..242, - elements: [ - Literal( - FStringLiteralElement { - range: 229..233, - value: "bar ", - }, - ), - Expression( - FStringExpressionElement { - range: 233..240, - expression: BinOp( - ExprBinOp { - range: 234..239, - left: Name( - ExprName { - range: 234..235, - id: "x", - ctx: Load, - }, - ), - op: Add, - right: Name( - ExprName { - range: 238..239, - id: "y", - ctx: Load, - }, - ), - }, - ), - debug_text: None, - conversion: None, - format_spec: None, - }, - ), - Literal( - FStringLiteralElement { - range: 240..241, - value: " ", - }, - ), - ], - flags: FStringFlags { - quote_style: Double, - prefix: Regular, - triple_quoted: false, + ), + FString( + FString { + range: 227..242, + elements: [ + Literal( + FStringLiteralElement { + range: 229..233, + value: "bar ", + }, + ), + Expression( + FStringExpressionElement { + range: 233..240, + expression: BinOp( + ExprBinOp { + range: 234..239, + left: Name( + ExprName { + range: 234..235, + id: "x", + ctx: Load, + }, + ), + op: Add, + right: Name( + ExprName { + range: 238..239, + id: "y", + ctx: Load, + }, + ), + }, + ), + debug_text: None, + conversion: None, + format_spec: None, + }, + ), + Literal( + FStringLiteralElement { + range: 240..241, + value: " ", + }, + ), + ], + flags: FStringFlags { + quote_style: Double, + prefix: Regular, + triple_quoted: false, + }, }, - }, - ), - Literal( - StringLiteral { - range: 243..248, - value: "baz", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, + ), + Literal( + StringLiteral { + range: 243..248, + value: "baz", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), - ], - ), + ), + ], + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 250..252, + value: Int( + 10, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 250..252, - value: Int( - 10, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap index 8d9b4f82925eec..ede492d09f77a6 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__list.py.snap @@ -652,26 +652,26 @@ Module( Dict( ExprDict { range: 319..325, - keys: [ - Some( - Name( - ExprName { - range: 320..321, - id: "a", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 320..321, + id: "a", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 323..324, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 323..324, - value: Int( - 1, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap index a607c403b10d17..38620ecf790596 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__set.py.snap @@ -15,8 +15,7 @@ Module( value: Dict( ExprDict { range: 14..16, - keys: [], - values: [], + items: [], }, ), }, @@ -139,8 +138,7 @@ Module( value: Dict( ExprDict { range: 74..77, - keys: [], - values: [], + items: [], }, ), }, @@ -579,33 +577,35 @@ Module( Dict( ExprDict { range: 300..311, - keys: [ - Some( - Name( + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 301..302, + id: "a", + ctx: Load, + }, + ), + ), + value: Name( ExprName { - range: 301..302, - id: "a", + range: 304..305, + id: "b", ctx: Load, }, ), - ), - None, - ], - values: [ - Name( - ExprName { - range: 304..305, - id: "b", - ctx: Load, - }, - ), - Name( - ExprName { - range: 309..310, - id: "d", - ctx: Load, - }, - ), + }, + DictItem { + key: None, + value: Name( + ExprName { + range: 309..310, + id: "d", + ctx: Load, + }, + ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap index d65f741b01954c..8eda1ccf6082cb 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield.py.snap @@ -216,26 +216,26 @@ Module( Dict( ExprDict { range: 85..91, - keys: [ - Some( - Name( - ExprName { - range: 86..87, - id: "x", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 86..87, + id: "x", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 89..90, + value: Int( + 5, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 89..90, - value: Int( - 5, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap index fe161c3851dfbc..d069483300c357 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@expressions__yield_from.py.snap @@ -192,26 +192,26 @@ Module( value: Dict( ExprDict { range: 114..120, - keys: [ - Some( - Name( - ExprName { - range: 115..116, - id: "x", - ctx: Load, + items: [ + DictItem { + key: Some( + Name( + ExprName { + range: 115..116, + id: "x", + ctx: Load, + }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 118..119, + value: Int( + 5, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 118..119, - value: Int( - 5, - ), - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap index 1b255036824e93..c28a10dae8260e 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@statement__match.py.snap @@ -3814,37 +3814,37 @@ Module( subject: Dict( ExprDict { range: 2959..2970, - keys: [ - Some( - StringLiteral( - ExprStringLiteral { - range: 2960..2966, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 2960..2966, - value: "test", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 2960..2966, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 2960..2966, + value: "test", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, }, - }, - ), + ), + }, }, + ), + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 2968..2969, + value: Int( + 1, + ), }, ), - ), - ], - values: [ - NumberLiteral( - ExprNumberLiteral { - range: 2968..2969, - value: Int( - 1, - ), - }, - ), + }, ], }, ), @@ -3907,16 +3907,36 @@ Module( subject: Dict( ExprDict { range: 3032..3049, - keys: [ - Some( - StringLiteral( + items: [ + DictItem { + key: Some( + StringLiteral( + ExprStringLiteral { + range: 3033..3040, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 3033..3040, + value: "label", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ), + value: StringLiteral( ExprStringLiteral { - range: 3033..3040, + range: 3042..3048, value: StringLiteralValue { inner: Single( StringLiteral { - range: 3033..3040, - value: "label", + range: 3042..3048, + value: "test", flags: StringLiteralFlags { quote_style: Double, prefix: Empty, @@ -3927,27 +3947,7 @@ Module( }, }, ), - ), - ], - values: [ - StringLiteral( - ExprStringLiteral { - range: 3042..3048, - value: StringLiteralValue { - inner: Single( - StringLiteral { - range: 3042..3048, - value: "test", - flags: StringLiteralFlags { - quote_style: Double, - prefix: Empty, - triple_quoted: false, - }, - }, - ), - }, - }, - ), + }, ], }, ), diff --git a/crates/ruff_python_trivia/Cargo.toml b/crates/ruff_python_trivia/Cargo.toml index 9e92e1eb521dec..98d5b60220f6be 100644 --- a/crates/ruff_python_trivia/Cargo.toml +++ b/crates/ruff_python_trivia/Cargo.toml @@ -21,8 +21,6 @@ unicode-ident = { workspace = true } [dev-dependencies] insta = { workspace = true } -ruff_python_parser = { path = "../ruff_python_parser" } -ruff_python_index = { path = "../ruff_python_index" } [lints] workspace = true diff --git a/crates/ruff_python_trivia/src/comment_ranges.rs b/crates/ruff_python_trivia/src/comment_ranges.rs index bdec7c428e1d4e..2c5c0606572a36 100644 --- a/crates/ruff_python_trivia/src/comment_ranges.rs +++ b/crates/ruff_python_trivia/src/comment_ranges.rs @@ -203,158 +203,3 @@ impl<'a> IntoIterator for &'a CommentRanges { self.raw.iter() } } - -#[cfg(test)] -mod tests { - use ruff_python_index::Indexer; - use ruff_python_parser::lexer::LexResult; - use ruff_python_parser::{tokenize, Mode}; - use ruff_source_file::Locator; - use ruff_text_size::TextSize; - - #[test] - fn block_comments_two_line_block_at_start() { - // arrange - let source = "# line 1\n# line 2\n"; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, vec![TextSize::new(0), TextSize::new(9)]); - } - - #[test] - fn block_comments_indented_block() { - // arrange - let source = " # line 1\n # line 2\n"; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, vec![TextSize::new(4), TextSize::new(17)]); - } - - #[test] - fn block_comments_single_line_is_not_a_block() { - // arrange - let source = "\n"; - let tokens: Vec = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, Vec::::new()); - } - - #[test] - fn block_comments_lines_with_code_not_a_block() { - // arrange - let source = "x = 1 # line 1\ny = 2 # line 2\n"; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, Vec::::new()); - } - - #[test] - fn block_comments_sequential_lines_not_in_block() { - // arrange - let source = " # line 1\n # line 2\n"; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, Vec::::new()); - } - - #[test] - fn block_comments_lines_in_triple_quotes_not_a_block() { - // arrange - let source = r#" - """ - # line 1 - # line 2 - """ - "#; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!(block_comments, Vec::::new()); - } - - #[test] - fn block_comments_stress_test() { - // arrange - let source = r#" -# block comment 1 line 1 -# block comment 2 line 2 - -# these lines - # do not form -# a block comment - -x = 1 # these lines also do not -y = 2 # do not form a block comment - -# these lines do form a block comment -# - - # - # and so do these - # - -""" -# these lines are in triple quotes and -# therefore do not form a block comment -""" - "#; - let tokens = tokenize(source, Mode::Module); - let locator = Locator::new(source); - let indexer = Indexer::from_tokens(&tokens, &locator); - - // act - let block_comments = indexer.comment_ranges().block_comments(&locator); - - // assert - assert_eq!( - block_comments, - vec![ - // Block #1 - TextSize::new(1), - TextSize::new(26), - // Block #2 - TextSize::new(174), - TextSize::new(212), - // Block #3 - TextSize::new(219), - TextSize::new(225), - TextSize::new(247) - ] - ); - } -} diff --git a/crates/ruff_python_trivia/src/tokenizer.rs b/crates/ruff_python_trivia/src/tokenizer.rs index d1135bb9bca4f7..13181a74891926 100644 --- a/crates/ruff_python_trivia/src/tokenizer.rs +++ b/crates/ruff_python_trivia/src/tokenizer.rs @@ -1024,426 +1024,3 @@ impl Iterator for BackwardsTokenizer<'_> { } } } - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - - use ruff_python_parser::lexer::lex; - use ruff_python_parser::{Mode, Tok}; - use ruff_text_size::{TextLen, TextRange, TextSize}; - - use crate::tokenizer::{lines_after, lines_before, SimpleToken, SimpleTokenizer}; - use crate::{BackwardsTokenizer, SimpleTokenKind}; - - struct TokenizationTestCase { - source: &'static str, - range: TextRange, - tokens: Vec, - } - - impl TokenizationTestCase { - fn assert_reverse_tokenization(&self) { - let mut backwards = self.tokenize_reverse(); - - // Re-reverse to get the tokens in forward order. - backwards.reverse(); - - assert_eq!(&backwards, &self.tokens); - } - - fn tokenize_reverse(&self) -> Vec { - let comment_ranges: Vec<_> = lex(self.source, Mode::Module) - .filter_map(|result| { - let (token, range) = result.expect("Input to be a valid python program."); - if matches!(token, Tok::Comment(_)) { - Some(range) - } else { - None - } - }) - .collect(); - BackwardsTokenizer::new(self.source, self.range, &comment_ranges).collect() - } - - fn tokens(&self) -> &[SimpleToken] { - &self.tokens - } - } - - fn tokenize_range(source: &'static str, range: TextRange) -> TokenizationTestCase { - let tokens: Vec<_> = SimpleTokenizer::new(source, range).collect(); - - TokenizationTestCase { - source, - range, - tokens, - } - } - - fn tokenize(source: &'static str) -> TokenizationTestCase { - tokenize_range(source, TextRange::new(TextSize::new(0), source.text_len())) - } - - #[test] - fn tokenize_trivia() { - let source = "# comment\n # comment"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_parentheses() { - let source = "([{}])"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_comma() { - let source = ",,,,"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_eq() { - // Should tokenize as `==`, then `=`, regardless of whether we're lexing forwards or - // backwards. - let source = "==="; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_not_eq() { - // Should tokenize as `!=`, then `=`, regardless of whether we're lexing forwards or - // backwards. - let source = "!=="; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_continuation() { - let source = "( \\\n )"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_operators() { - let source = "-> *= ( -= ) ~ // ** **= ^ ^= | |="; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_invalid_operators() { - let source = "-> $="; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - - // note: not reversible: [other, bogus, bogus] vs [bogus, bogus, other] - } - - #[test] - fn tricky_unicode() { - let source = "មុ"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn identifier_ending_in_non_start_char() { - let source = "i5"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn string_with_kind() { - let source = "f'foo'"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - - // note: not reversible: [other, bogus] vs [bogus, other] - } - - #[test] - fn string_with_byte_kind() { - let source = "BR'foo'"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - - // note: not reversible: [other, bogus] vs [bogus, other] - } - - #[test] - fn string_with_invalid_kind() { - let source = "abc'foo'"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - - // note: not reversible: [other, bogus] vs [bogus, other] - } - - #[test] - fn identifier_starting_with_string_kind() { - let source = "foo bar"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn ignore_word_with_only_id_continuing_chars() { - let source = "555"; - - let test_case = tokenize(source); - assert_debug_snapshot!(test_case.tokens()); - - // note: not reversible: [other, bogus, bogus] vs [bogus, bogus, other] - } - - #[test] - fn tokenize_multichar() { - let source = "if in else match"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_substring() { - let source = "('some string') # comment"; - - let test_case = - tokenize_range(source, TextRange::new(TextSize::new(14), source.text_len())); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_slash() { - let source = r" # trailing positional comment - # Positional arguments only after here - ,/"; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - test_case.assert_reverse_tokenization(); - } - - #[test] - fn tokenize_bogus() { - let source = r#"# leading comment - "a string" - a = (10)"#; - - let test_case = tokenize(source); - - assert_debug_snapshot!(test_case.tokens()); - assert_debug_snapshot!("Reverse", test_case.tokenize_reverse()); - } - - #[test] - fn single_quoted_multiline_string_containing_comment() { - let test_case = tokenize( - r"'This string contains a hash looking like a comment\ -# This is not a comment'", - ); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn single_quoted_multiline_string_implicit_concatenation() { - let test_case = tokenize( - r#"'This string contains a hash looking like a comment\ -# This is' "not_a_comment""#, - ); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn triple_quoted_multiline_string_containing_comment() { - let test_case = tokenize( - r"'''This string contains a hash looking like a comment -# This is not a comment'''", - ); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn comment_containing_triple_quoted_string() { - let test_case = tokenize("'''leading string''' # a comment '''not a string'''"); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn comment_containing_single_quoted_string() { - let test_case = tokenize("'leading string' # a comment 'not a string'"); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn string_followed_by_multiple_comments() { - let test_case = - tokenize(r#"'a string # containing a hash " # and another hash ' # finally a comment"#); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn string_with_escaped_quote() { - let test_case = tokenize(r"'a string \' # containing a hash ' # finally a comment"); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn string_with_double_escaped_backslash() { - let test_case = tokenize(r"'a string \\' # a comment '"); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn empty_string_literal() { - let test_case = tokenize(r"'' # a comment '"); - - assert_debug_snapshot!(test_case.tokenize_reverse()); - } - - #[test] - fn lines_before_empty_string() { - assert_eq!(lines_before(TextSize::new(0), ""), 0); - } - - #[test] - fn lines_before_in_the_middle_of_a_line() { - assert_eq!(lines_before(TextSize::new(4), "a = 20"), 0); - } - - #[test] - fn lines_before_on_a_new_line() { - assert_eq!(lines_before(TextSize::new(7), "a = 20\nb = 10"), 1); - } - - #[test] - fn lines_before_multiple_leading_newlines() { - assert_eq!(lines_before(TextSize::new(9), "a = 20\n\r\nb = 10"), 2); - } - - #[test] - fn lines_before_with_comment_offset() { - assert_eq!(lines_before(TextSize::new(8), "a = 20\n# a comment"), 0); - } - - #[test] - fn lines_before_with_trailing_comment() { - assert_eq!( - lines_before(TextSize::new(22), "a = 20 # some comment\nb = 10"), - 1 - ); - } - - #[test] - fn lines_before_with_comment_only_line() { - assert_eq!( - lines_before(TextSize::new(22), "a = 20\n# some comment\nb = 10"), - 1 - ); - } - - #[test] - fn lines_after_empty_string() { - assert_eq!(lines_after(TextSize::new(0), ""), 0); - } - - #[test] - fn lines_after_in_the_middle_of_a_line() { - assert_eq!(lines_after(TextSize::new(4), "a = 20"), 0); - } - - #[test] - fn lines_after_before_a_new_line() { - assert_eq!(lines_after(TextSize::new(6), "a = 20\nb = 10"), 1); - } - - #[test] - fn lines_after_multiple_newlines() { - assert_eq!(lines_after(TextSize::new(6), "a = 20\n\r\nb = 10"), 2); - } - - #[test] - fn lines_after_before_comment_offset() { - assert_eq!(lines_after(TextSize::new(7), "a = 20 # a comment\n"), 0); - } - - #[test] - fn lines_after_with_comment_only_line() { - assert_eq!( - lines_after(TextSize::new(6), "a = 20\n# some comment\nb = 10"), - 1 - ); - } - - #[test] - fn test_previous_token_simple() { - let cases = &["x = (", "x = ( ", "x = (\n"]; - for source in cases { - let token = BackwardsTokenizer::up_to(source.text_len(), source, &[]) - .skip_trivia() - .next() - .unwrap(); - assert_eq!( - token, - SimpleToken { - kind: SimpleTokenKind::LParen, - range: TextRange::new(TextSize::new(4), TextSize::new(5)), - } - ); - } - } -} diff --git a/crates/ruff_python_trivia/src/whitespace.rs b/crates/ruff_python_trivia/src/whitespace.rs index 9e91cfb69eead9..7b8b5e90ea0052 100644 --- a/crates/ruff_python_trivia/src/whitespace.rs +++ b/crates/ruff_python_trivia/src/whitespace.rs @@ -79,51 +79,3 @@ impl PythonWhitespace for str { self.trim_end_matches(is_python_whitespace) } } - -#[cfg(test)] -mod tests { - use ruff_python_parser::{parse_suite, ParseError}; - use ruff_source_file::Locator; - use ruff_text_size::Ranged; - - use crate::has_trailing_content; - - #[test] - fn trailing_content() -> Result<(), ParseError> { - let contents = "x = 1"; - let program = parse_suite(contents)?; - let stmt = program.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); - - let contents = "x = 1; y = 2"; - let program = parse_suite(contents)?; - let stmt = program.first().unwrap(); - let locator = Locator::new(contents); - assert!(has_trailing_content(stmt.end(), &locator)); - - let contents = "x = 1 "; - let program = parse_suite(contents)?; - let stmt = program.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); - - let contents = "x = 1 # Comment"; - let program = parse_suite(contents)?; - let stmt = program.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); - - let contents = r" -x = 1 -y = 2 -" - .trim(); - let program = parse_suite(contents)?; - let stmt = program.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); - - Ok(()) - } -} diff --git a/crates/ruff_python_trivia_integration_tests/Cargo.toml b/crates/ruff_python_trivia_integration_tests/Cargo.toml new file mode 100644 index 00000000000000..cb144472fb432f --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ruff_python_trivia_integration_tests" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] + +[dev-dependencies] +ruff_python_index = { path = "../ruff_python_index" } +ruff_python_parser = { path = "../ruff_python_parser" } +ruff_python_trivia = { path = "../ruff_python_trivia" } +ruff_source_file = { path = "../ruff_source_file" } +ruff_text_size = { path = "../ruff_text_size" } + +insta = { workspace = true } + +[lints] +workspace = true diff --git a/crates/ruff_python_trivia_integration_tests/README.md b/crates/ruff_python_trivia_integration_tests/README.md new file mode 100644 index 00000000000000..78024349eaa0db --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/README.md @@ -0,0 +1,13 @@ +# Integration tests for `ruff_python_trivia` + +This crate includes integration tests for the `ruff_python_trivia` crate. + +The reason for having a separate crate is to avoid introducing a dev circular +dependency between the `ruff_python_parser` crate and the `ruff_python_trivia` crate. + +This crate shouldn't include any code, only tests. + +**Reference:** + +- `rust-analyzer` issue: +- Ruff's pull request: diff --git a/crates/ruff_python_trivia_integration_tests/src/lib.rs b/crates/ruff_python_trivia_integration_tests/src/lib.rs new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs b/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs new file mode 100644 index 00000000000000..df0142b3c17980 --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs @@ -0,0 +1,151 @@ +use ruff_python_index::Indexer; +use ruff_python_parser::lexer::LexResult; +use ruff_python_parser::{tokenize, Mode}; +use ruff_source_file::Locator; +use ruff_text_size::TextSize; + +#[test] +fn block_comments_two_line_block_at_start() { + // arrange + let source = "# line 1\n# line 2\n"; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, vec![TextSize::new(0), TextSize::new(9)]); +} + +#[test] +fn block_comments_indented_block() { + // arrange + let source = " # line 1\n # line 2\n"; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, vec![TextSize::new(4), TextSize::new(17)]); +} + +#[test] +fn block_comments_single_line_is_not_a_block() { + // arrange + let source = "\n"; + let tokens: Vec = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, Vec::::new()); +} + +#[test] +fn block_comments_lines_with_code_not_a_block() { + // arrange + let source = "x = 1 # line 1\ny = 2 # line 2\n"; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, Vec::::new()); +} + +#[test] +fn block_comments_sequential_lines_not_in_block() { + // arrange + let source = " # line 1\n # line 2\n"; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, Vec::::new()); +} + +#[test] +fn block_comments_lines_in_triple_quotes_not_a_block() { + // arrange + let source = r#" + """ + # line 1 + # line 2 + """ + "#; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!(block_comments, Vec::::new()); +} + +#[test] +fn block_comments_stress_test() { + // arrange + let source = r#" +# block comment 1 line 1 +# block comment 2 line 2 + +# these lines + # do not form +# a block comment + +x = 1 # these lines also do not +y = 2 # do not form a block comment + +# these lines do form a block comment +# + + # + # and so do these + # + +""" +# these lines are in triple quotes and +# therefore do not form a block comment +""" + "#; + let tokens = tokenize(source, Mode::Module); + let locator = Locator::new(source); + let indexer = Indexer::from_tokens(&tokens, &locator); + + // act + let block_comments = indexer.comment_ranges().block_comments(&locator); + + // assert + assert_eq!( + block_comments, + vec![ + // Block #1 + TextSize::new(1), + TextSize::new(26), + // Block #2 + TextSize::new(174), + TextSize::new(212), + // Block #3 + TextSize::new(219), + TextSize::new(225), + TextSize::new(247) + ] + ); +} diff --git a/crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs b/crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs new file mode 100644 index 00000000000000..5ac4296ea68fe2 --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs @@ -0,0 +1,417 @@ +use insta::assert_debug_snapshot; + +use ruff_python_parser::lexer::lex; +use ruff_python_parser::{Mode, Tok}; +use ruff_python_trivia::{lines_after, lines_before, SimpleToken, SimpleTokenizer}; +use ruff_python_trivia::{BackwardsTokenizer, SimpleTokenKind}; +use ruff_text_size::{TextLen, TextRange, TextSize}; + +struct TokenizationTestCase { + source: &'static str, + range: TextRange, + tokens: Vec, +} + +impl TokenizationTestCase { + fn assert_reverse_tokenization(&self) { + let mut backwards = self.tokenize_reverse(); + + // Re-reverse to get the tokens in forward order. + backwards.reverse(); + + assert_eq!(&backwards, &self.tokens); + } + + fn tokenize_reverse(&self) -> Vec { + let comment_ranges: Vec<_> = lex(self.source, Mode::Module) + .filter_map(|result| { + let (token, range) = result.expect("Input to be a valid python program."); + if matches!(token, Tok::Comment(_)) { + Some(range) + } else { + None + } + }) + .collect(); + BackwardsTokenizer::new(self.source, self.range, &comment_ranges).collect() + } + + fn tokens(&self) -> &[SimpleToken] { + &self.tokens + } +} + +fn tokenize_range(source: &'static str, range: TextRange) -> TokenizationTestCase { + let tokens: Vec<_> = SimpleTokenizer::new(source, range).collect(); + + TokenizationTestCase { + source, + range, + tokens, + } +} + +fn tokenize(source: &'static str) -> TokenizationTestCase { + tokenize_range(source, TextRange::new(TextSize::new(0), source.text_len())) +} + +#[test] +fn tokenize_trivia() { + let source = "# comment\n # comment"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_parentheses() { + let source = "([{}])"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_comma() { + let source = ",,,,"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_eq() { + // Should tokenize as `==`, then `=`, regardless of whether we're lexing forwards or + // backwards. + let source = "==="; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_not_eq() { + // Should tokenize as `!=`, then `=`, regardless of whether we're lexing forwards or + // backwards. + let source = "!=="; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_continuation() { + let source = "( \\\n )"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_operators() { + let source = "-> *= ( -= ) ~ // ** **= ^ ^= | |="; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_invalid_operators() { + let source = "-> $="; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + + // note: not reversible: [other, bogus, bogus] vs [bogus, bogus, other] +} + +#[test] +fn tricky_unicode() { + let source = "មុ"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn identifier_ending_in_non_start_char() { + let source = "i5"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn string_with_kind() { + let source = "f'foo'"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + + // note: not reversible: [other, bogus] vs [bogus, other] +} + +#[test] +fn string_with_byte_kind() { + let source = "BR'foo'"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + + // note: not reversible: [other, bogus] vs [bogus, other] +} + +#[test] +fn string_with_invalid_kind() { + let source = "abc'foo'"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + + // note: not reversible: [other, bogus] vs [bogus, other] +} + +#[test] +fn identifier_starting_with_string_kind() { + let source = "foo bar"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn ignore_word_with_only_id_continuing_chars() { + let source = "555"; + + let test_case = tokenize(source); + assert_debug_snapshot!(test_case.tokens()); + + // note: not reversible: [other, bogus, bogus] vs [bogus, bogus, other] +} + +#[test] +fn tokenize_multichar() { + let source = "if in else match"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_substring() { + let source = "('some string') # comment"; + + let test_case = tokenize_range(source, TextRange::new(TextSize::new(14), source.text_len())); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_slash() { + let source = r" # trailing positional comment + # Positional arguments only after here + ,/"; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + test_case.assert_reverse_tokenization(); +} + +#[test] +fn tokenize_bogus() { + let source = r#"# leading comment + "a string" + a = (10)"#; + + let test_case = tokenize(source); + + assert_debug_snapshot!(test_case.tokens()); + assert_debug_snapshot!("Reverse", test_case.tokenize_reverse()); +} + +#[test] +fn single_quoted_multiline_string_containing_comment() { + let test_case = tokenize( + r"'This string contains a hash looking like a comment\ +# This is not a comment'", + ); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn single_quoted_multiline_string_implicit_concatenation() { + let test_case = tokenize( + r#"'This string contains a hash looking like a comment\ +# This is' "not_a_comment""#, + ); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn triple_quoted_multiline_string_containing_comment() { + let test_case = tokenize( + r"'''This string contains a hash looking like a comment +# This is not a comment'''", + ); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn comment_containing_triple_quoted_string() { + let test_case = tokenize("'''leading string''' # a comment '''not a string'''"); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn comment_containing_single_quoted_string() { + let test_case = tokenize("'leading string' # a comment 'not a string'"); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn string_followed_by_multiple_comments() { + let test_case = + tokenize(r#"'a string # containing a hash " # and another hash ' # finally a comment"#); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn string_with_escaped_quote() { + let test_case = tokenize(r"'a string \' # containing a hash ' # finally a comment"); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn string_with_double_escaped_backslash() { + let test_case = tokenize(r"'a string \\' # a comment '"); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn empty_string_literal() { + let test_case = tokenize(r"'' # a comment '"); + + assert_debug_snapshot!(test_case.tokenize_reverse()); +} + +#[test] +fn lines_before_empty_string() { + assert_eq!(lines_before(TextSize::new(0), ""), 0); +} + +#[test] +fn lines_before_in_the_middle_of_a_line() { + assert_eq!(lines_before(TextSize::new(4), "a = 20"), 0); +} + +#[test] +fn lines_before_on_a_new_line() { + assert_eq!(lines_before(TextSize::new(7), "a = 20\nb = 10"), 1); +} + +#[test] +fn lines_before_multiple_leading_newlines() { + assert_eq!(lines_before(TextSize::new(9), "a = 20\n\r\nb = 10"), 2); +} + +#[test] +fn lines_before_with_comment_offset() { + assert_eq!(lines_before(TextSize::new(8), "a = 20\n# a comment"), 0); +} + +#[test] +fn lines_before_with_trailing_comment() { + assert_eq!( + lines_before(TextSize::new(22), "a = 20 # some comment\nb = 10"), + 1 + ); +} + +#[test] +fn lines_before_with_comment_only_line() { + assert_eq!( + lines_before(TextSize::new(22), "a = 20\n# some comment\nb = 10"), + 1 + ); +} + +#[test] +fn lines_after_empty_string() { + assert_eq!(lines_after(TextSize::new(0), ""), 0); +} + +#[test] +fn lines_after_in_the_middle_of_a_line() { + assert_eq!(lines_after(TextSize::new(4), "a = 20"), 0); +} + +#[test] +fn lines_after_before_a_new_line() { + assert_eq!(lines_after(TextSize::new(6), "a = 20\nb = 10"), 1); +} + +#[test] +fn lines_after_multiple_newlines() { + assert_eq!(lines_after(TextSize::new(6), "a = 20\n\r\nb = 10"), 2); +} + +#[test] +fn lines_after_before_comment_offset() { + assert_eq!(lines_after(TextSize::new(7), "a = 20 # a comment\n"), 0); +} + +#[test] +fn lines_after_with_comment_only_line() { + assert_eq!( + lines_after(TextSize::new(6), "a = 20\n# some comment\nb = 10"), + 1 + ); +} + +#[test] +fn test_previous_token_simple() { + let cases = &["x = (", "x = ( ", "x = (\n"]; + for source in cases { + let token = BackwardsTokenizer::up_to(source.text_len(), source, &[]) + .skip_trivia() + .next() + .unwrap(); + assert_eq!( + token, + SimpleToken { + kind: SimpleTokenKind::LParen, + range: TextRange::new(TextSize::new(4), TextSize::new(5)), + } + ); + } +} diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__Reverse.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__Reverse.snap similarity index 76% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__Reverse.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__Reverse.snap index 98b020e6e864f4..7763ffb690faa8 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__Reverse.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__Reverse.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_single_quoted_string.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_single_quoted_string.snap similarity index 81% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_single_quoted_string.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_single_quoted_string.snap index 1ebd8a5f481ef2..36ea89df615a6d 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_single_quoted_string.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_single_quoted_string.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_triple_quoted_string.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_triple_quoted_string.snap similarity index 81% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_triple_quoted_string.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_triple_quoted_string.snap index f2958e6691e9e6..a794749cff8b70 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__comment_containing_triple_quoted_string.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__comment_containing_triple_quoted_string.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__empty_string_literal.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__empty_string_literal.snap similarity index 80% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__empty_string_literal.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__empty_string_literal.snap index 7f3571ef279a2d..1d420e049fff86 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__empty_string_literal.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__empty_string_literal.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_ending_in_non_start_char.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_ending_in_non_start_char.snap similarity index 58% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_ending_in_non_start_char.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_ending_in_non_start_char.snap index 7b3494de286b63..32e4f33792166a 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_ending_in_non_start_char.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_ending_in_non_start_char.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_starting_with_string_kind.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_starting_with_string_kind.snap similarity index 76% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_starting_with_string_kind.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_starting_with_string_kind.snap index 6d8bfb8f06ad65..04086f5524d951 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__identifier_starting_with_string_kind.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__identifier_starting_with_string_kind.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__ignore_word_with_only_id_continuing_chars.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__ignore_word_with_only_id_continuing_chars.snap similarity index 69% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__ignore_word_with_only_id_continuing_chars.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__ignore_word_with_only_id_continuing_chars.snap index 24b9b16ccc32c0..a1248f3b41757f 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__ignore_word_with_only_id_continuing_chars.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__ignore_word_with_only_id_continuing_chars.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_containing_comment.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_containing_comment.snap similarity index 70% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_containing_comment.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_containing_comment.snap index 5836fce453d240..913b78999147b3 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_containing_comment.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_containing_comment.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_implicit_concatenation.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_implicit_concatenation.snap similarity index 70% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_implicit_concatenation.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_implicit_concatenation.snap index 774475fc23fa73..88771f8ce203c2 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__single_quoted_multiline_string_implicit_concatenation.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__single_quoted_multiline_string_implicit_concatenation.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_followed_by_multiple_comments.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_followed_by_multiple_comments.snap similarity index 81% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_followed_by_multiple_comments.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_followed_by_multiple_comments.snap index 01da0949432b2d..2c623fbaad788b 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_followed_by_multiple_comments.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_followed_by_multiple_comments.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_byte_kind.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_byte_kind.snap similarity index 69% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_byte_kind.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_byte_kind.snap index 5b535116cbb3fc..13e26326e639b9 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_byte_kind.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_byte_kind.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_double_escaped_backslash.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_double_escaped_backslash.snap similarity index 81% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_double_escaped_backslash.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_double_escaped_backslash.snap index ce46e6154a8947..84ac21254d58af 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_double_escaped_backslash.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_double_escaped_backslash.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_escaped_quote.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_escaped_quote.snap similarity index 81% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_escaped_quote.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_escaped_quote.snap index 7e1735c3edd0f8..1bf0706e3aa8d3 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_escaped_quote.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_escaped_quote.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_invalid_kind.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_invalid_kind.snap similarity index 75% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_invalid_kind.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_invalid_kind.snap index cab8e69c89f4af..cdcaad2ccb9ec8 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_invalid_kind.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_invalid_kind.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_kind.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_kind.snap similarity index 69% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_kind.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_kind.snap index a3030fc9af35b7..4f20942cc6adb7 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__string_with_kind.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__string_with_kind.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_bogus.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_bogus.snap similarity index 83% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_bogus.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_bogus.snap index 1b2a3834294b2d..ab66aa9c112468 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_bogus.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_bogus.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_comma.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_comma.snap similarity index 79% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_comma.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_comma.snap index d8e5794877d192..be4f7308d71190 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_comma.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_comma.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_continuation.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_continuation.snap similarity index 85% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_continuation.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_continuation.snap index 9c4f76b3e72a57..801a7a65dda011 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_continuation.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_continuation.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_eq.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_eq.snap similarity index 69% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_eq.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_eq.snap index 26f9c5ae2ce1a5..4bab77b6815ba9 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_eq.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_eq.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_invalid_operators.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_invalid_operators.snap similarity index 80% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_invalid_operators.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_invalid_operators.snap index cee59e0ba3d816..78f03749431e56 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_invalid_operators.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_invalid_operators.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_multichar.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_multichar.snap similarity index 87% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_multichar.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_multichar.snap index 1f2da93d5dee58..4cc2111b55a176 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_multichar.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_multichar.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_not_eq.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_not_eq.snap similarity index 69% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_not_eq.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_not_eq.snap index 00a7d9c1fcf251..e2db4103477f93 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_not_eq.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_not_eq.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_operators.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_operators.snap similarity index 95% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_operators.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_operators.snap index f0f92f80c0d558..e0430a95530c5f 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_operators.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_operators.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_parentheses.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_parentheses.snap similarity index 85% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_parentheses.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_parentheses.snap index 7a2fa7db1108c7..0e3bedccd50e14 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_parentheses.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_parentheses.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_slash.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_slash.snap similarity index 89% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_slash.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_slash.snap index fe0d7b1eaa4bb5..43c5d6d815586d 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_slash.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_slash.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_substring.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_substring.snap similarity index 76% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_substring.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_substring.snap index 66bd0853336b30..d3b74a107450e0 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_substring.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_substring.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_trivia.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_trivia.snap similarity index 80% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_trivia.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_trivia.snap index 476f426dec944d..4872572e9183a6 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tokenize_trivia.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tokenize_trivia.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tricky_unicode.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tricky_unicode.snap similarity index 58% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tricky_unicode.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tricky_unicode.snap index 81864953b5015f..bc8a977653192a 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__tricky_unicode.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__tricky_unicode.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokens() --- [ diff --git a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__triple_quoted_multiline_string_containing_comment.snap b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__triple_quoted_multiline_string_containing_comment.snap similarity index 70% rename from crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__triple_quoted_multiline_string_containing_comment.snap rename to crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__triple_quoted_multiline_string_containing_comment.snap index 2708eac19ee5c7..e2081323a19756 100644 --- a/crates/ruff_python_trivia/src/snapshots/ruff_python_trivia__tokenizer__tests__triple_quoted_multiline_string_containing_comment.snap +++ b/crates/ruff_python_trivia_integration_tests/tests/snapshots/simple_tokenizer__triple_quoted_multiline_string_containing_comment.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_python_trivia/src/tokenizer.rs +source: crates/ruff_python_trivia_integration_tests/tests/simple_tokenizer.rs expression: test_case.tokenize_reverse() --- [ diff --git a/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs b/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs new file mode 100644 index 00000000000000..709a3a3d189b62 --- /dev/null +++ b/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs @@ -0,0 +1,43 @@ +use ruff_python_parser::{parse_suite, ParseError}; +use ruff_python_trivia::has_trailing_content; +use ruff_source_file::Locator; +use ruff_text_size::Ranged; + +#[test] +fn trailing_content() -> Result<(), ParseError> { + let contents = "x = 1"; + let program = parse_suite(contents)?; + let stmt = program.first().unwrap(); + let locator = Locator::new(contents); + assert!(!has_trailing_content(stmt.end(), &locator)); + + let contents = "x = 1; y = 2"; + let program = parse_suite(contents)?; + let stmt = program.first().unwrap(); + let locator = Locator::new(contents); + assert!(has_trailing_content(stmt.end(), &locator)); + + let contents = "x = 1 "; + let program = parse_suite(contents)?; + let stmt = program.first().unwrap(); + let locator = Locator::new(contents); + assert!(!has_trailing_content(stmt.end(), &locator)); + + let contents = "x = 1 # Comment"; + let program = parse_suite(contents)?; + let stmt = program.first().unwrap(); + let locator = Locator::new(contents); + assert!(!has_trailing_content(stmt.end(), &locator)); + + let contents = r" +x = 1 +y = 2 +" + .trim(); + let program = parse_suite(contents)?; + let stmt = program.first().unwrap(); + let locator = Locator::new(contents); + assert!(!has_trailing_content(stmt.end(), &locator)); + + Ok(()) +} diff --git a/crates/ruff_source_file/Cargo.toml b/crates/ruff_source_file/Cargo.toml index c6910e4fa4e68d..f3cc40807aad84 100644 --- a/crates/ruff_source_file/Cargo.toml +++ b/crates/ruff_source_file/Cargo.toml @@ -13,9 +13,10 @@ license = { workspace = true } [lib] [dependencies] +ruff_text_size = { path = "../ruff_text_size" } + memchr = { workspace = true } once_cell = { workspace = true } -ruff_text_size = { path = "../ruff_text_size" } serde = { workspace = true, optional = true } [dev-dependencies] diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 2fd4bc2db3417c..fbd76b7e84e8f9 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2267,6 +2267,12 @@ pub struct IsortOptions { /// testing = ["pytest", "hypothesis"] /// ``` /// + /// The values in the list are treated as glob patterns. For example, to match all packages in + /// the LangChain ecosystem (`langchain-core`, `langchain-openai`, etc.): + /// ```toml + /// langchain = ["langchain-*"] + /// ``` + /// /// Custom sections should typically be inserted into the `section-order` list to ensure that /// they're displayed as a standalone group and in the intended order, as in: /// ```toml diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index 2c300fcacc2bd6..9dbe721ed75771 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -16,7 +16,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.52.0" + "wrangler": "3.53.1" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -112,9 +112,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240423.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240423.0.tgz", - "integrity": "sha512-ssuccb3j+URp6mP2p0PcQE9vmS3YeKBQnALHF9P3yQfUAFozuhTsDTbqmL+zPrJvUcG7SL2xVQkNDF9QJeKDZw==", + "version": "4.20240502.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240502.0.tgz", + "integrity": "sha512-OB1jIyPOzyOcuZFHWhsQnkRLN6u8+jmU9X3T4KZlGgn3Ivw8pBiswhLOp+yFeChR3Y4/5+V0hPFRko5SReordg==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -1516,9 +1516,9 @@ } }, "node_modules/wrangler": { - "version": "3.52.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.52.0.tgz", - "integrity": "sha512-HR06jTym+yr7+CI3Ggld3nfp1OM9vSC7h4B8vwWHwhi5K0sYg8p44rxV514Gmsv9dkFHegkRP70SM3sjuuxxpQ==", + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.1.tgz", + "integrity": "sha512-bdMRQdHYdvowIwOhEMFkARIZUh56aDw7HLUZ/2JreBjj760osXE4Fc4L1TCkfRRBWgB6/LKF5LA4OcvORMYmHg==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.2", diff --git a/playground/api/package.json b/playground/api/package.json index 077861c23c1847..38742893c11847 100644 --- a/playground/api/package.json +++ b/playground/api/package.json @@ -5,7 +5,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.52.0" + "wrangler": "3.53.1" }, "private": true, "scripts": { diff --git a/playground/package-lock.json b/playground/package-lock.json index 3a70d4ca4a56e2..cefb6d2250c326 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1071,16 +1071,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz", - "integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/type-utils": "7.7.1", - "@typescript-eslint/utils": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.3.1", @@ -1106,15 +1106,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz", - "integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/typescript-estree": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4" }, "engines": { @@ -1134,13 +1134,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz", - "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1" + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1151,13 +1151,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz", - "integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.7.1", - "@typescript-eslint/utils": "7.7.1", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1178,9 +1178,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz", - "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1191,13 +1191,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz", - "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1243,17 +1243,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz", - "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.15", "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/typescript-estree": "7.7.1", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", "semver": "^7.6.0" }, "engines": { @@ -1268,12 +1268,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz", - "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/types": "7.8.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -4255,9 +4255,9 @@ ] }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4266,15 +4266,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -4284,9 +4284,9 @@ "dev": true }, "node_modules/react-resizable-panels": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.18.tgz", - "integrity": "sha512-rKagCW6C8tTjWRq5jNsASsi4TB2a+IixL9++0G1+kPgAgvULPVf7kE0VbHysC3wdvcGYMa70O46C9YpG7CCkeA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.19.tgz", + "integrity": "sha512-v3E41kfKSuCPIvJVb4nL4mIZjjKIn/gh6YqZF/gDfQDolv/8XnhJBek4EiV2gOr3hhc5A3kOGOayk3DhanpaQw==", "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" @@ -4494,9 +4494,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -5083,9 +5083,9 @@ "dev": true }, "node_modules/vite": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", - "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", "dev": true, "dependencies": { "esbuild": "^0.20.1", diff --git a/ruff.schema.json b/ruff.schema.json index 8c620799c77e62..e9d9212a7f78a9 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -6,10 +6,7 @@ "allowed-confusables": { "description": "A list of allowed \"confusable\" Unicode characters to ignore when enforcing `RUF001`, `RUF002`, and `RUF003`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string", "maxLength": 1, @@ -18,35 +15,23 @@ }, "builtins": { "description": "A list of builtins to treat as defined references, in addition to the system builtins.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "cache-dir": { "description": "A path to the cache directory.\n\nBy default, Ruff stores cache results in a `.ruff_cache` directory in the current project root.\n\nHowever, Ruff will also respect the `RUFF_CACHE_DIR` environment variable, which takes precedence over that default.\n\nThis setting will override even the `RUFF_CACHE_DIR` environment variable, if set.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "dummy-variable-rgx": { "description": "A regular expression used to identify \"dummy\" variables, or those which should be ignored when enforcing (e.g.) unused-variable rules. The default expression matches `_`, `__`, and `_var`, but not `_var_`.", "deprecated": true, - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "exclude": { "description": "A list of file patterns to exclude from formatting and linting.\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).\n\nNote that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded paths.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -54,24 +39,15 @@ "explicit-preview-rules": { "description": "Whether to require exact codes to select preview rules. When enabled, preview rules will not be selected by prefixes — the full code of each preview rule will be required to enable the rule.", "deprecated": true, - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "extend": { "description": "A path to a local `pyproject.toml` file to merge into this configuration. User home directory and environment variables will be expanded.\n\nTo resolve the current `pyproject.toml` file, Ruff will first resolve this base configuration file, then merge in any properties defined in the current configuration file.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "extend-exclude": { "description": "A list of file patterns to omit from formatting and linting, in addition to those specified by `exclude`.\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -79,10 +55,7 @@ "extend-fixable": { "description": "A list of rule codes or prefixes to consider fixable, in addition to those specified by `fixable`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -90,20 +63,14 @@ "extend-ignore": { "description": "A list of rule codes or prefixes to ignore, in addition to those specified by `ignore`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "extend-include": { "description": "A list of file patterns to include when linting, in addition to those specified by `include`.\n\nInclusion are based on globs, and should be single-path patterns, like `*.pyw`, to include any file with the `.pyw` extension.\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -111,10 +78,7 @@ "extend-per-file-ignores": { "description": "A list of mappings from file pattern to rule codes or prefixes to exclude, in addition to any rules excluded by `per-file-ignores`.", "deprecated": true, - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { @@ -125,10 +89,7 @@ "extend-safe-fixes": { "description": "A list of rule codes or prefixes for which unsafe fixes should be considered safe.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -136,10 +97,7 @@ "extend-select": { "description": "A list of rule codes or prefixes to enable, in addition to those specified by `select`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -147,10 +105,7 @@ "extend-unfixable": { "description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those specified by `unfixable`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -158,10 +113,7 @@ "extend-unsafe-fixes": { "description": "A list of rule codes or prefixes for which safe fixes should be considered unsafe.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -169,35 +121,23 @@ "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "fix": { "description": "Enable fix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix` command-line flags). Only includes automatic fixes unless `--unsafe-fixes` is provided.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "fix-only": { "description": "Like `fix`, but disables reporting on leftover violation. Implies `fix`.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "fixable": { "description": "A list of rule codes or prefixes to consider fixable. By default, all rules are considered fixable.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -408,10 +348,7 @@ }, "force-exclude": { "description": "Whether to enforce `exclude` and `extend-exclude` patterns, even for paths that are passed to Ruff explicitly. Typically, Ruff will lint any paths passed in directly, even if they would typically be excluded. Setting `force-exclude = true` will cause Ruff to respect these exclusions unequivocally.\n\nThis is useful for [`pre-commit`](https://pre-commit.com/), which explicitly passes all changed files to the [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit) plugin, regardless of whether they're marked as excluded by Ruff's own settings.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "format": { "description": "Options to configure code formatting.", @@ -427,10 +364,7 @@ "ignore": { "description": "A list of rule codes or prefixes to ignore. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -438,17 +372,11 @@ "ignore-init-module-imports": { "description": "Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be flagged, but with a dedicated message suggesting that the import is either added to the module's `__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).\n\nThis option is enabled by default, but you can opt-in to removal of imports via an unsafe fix.", "deprecated": true, - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "include": { "description": "A list of file patterns to include when linting.\n\nInclusion are based on globs, and should be single-path patterns, like `*.pyw`, to include any file with the `.pyw` extension. `pyproject.toml` is included here not for configuration but because we lint whether e.g. the `[project]` matches the schema.\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -500,10 +428,7 @@ "logger-objects": { "description": "A list of objects that should be treated equivalently to a `logging.Logger` object.\n\nThis is useful for ensuring proper diagnostics (e.g., to identify `logging` deprecations and other best-practices) for projects that re-export a `logging.Logger` object from a common module.\n\nFor example, if you have a module `logging_setup.py` with the following contents: ```python import logging\n\nlogger = logging.getLogger(__name__) ```\n\nAdding `\"logging_setup.logger\"` to `logger-objects` will ensure that `logging_setup.logger` is treated as a `logging.Logger` object when imported from other modules (e.g., `from logging_setup import logger`).", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -522,10 +447,7 @@ }, "namespace-packages": { "description": "Mark the specified directories as namespace packages. For the purpose of module resolution, Ruff will treat those directories and all their subdirectories as if they contained an `__init__.py` file.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -556,10 +478,7 @@ "per-file-ignores": { "description": "A list of mappings from file pattern to rule codes or prefixes to exclude, when considering any matching files. An initial '!' negates the file pattern.", "deprecated": true, - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { @@ -569,10 +488,7 @@ }, "preview": { "description": "Whether to enable preview mode. When preview mode is enabled, Ruff will use unstable rules, fixes, and formatting.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "pycodestyle": { "description": "Options for the `pycodestyle` plugin.", @@ -647,43 +563,28 @@ }, "respect-gitignore": { "description": "Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. Enabled by default.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "select": { "description": "A list of rule codes or prefixes to enable. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "show-fixes": { "description": "Whether to show an enumeration of all fixed lint violations (overridden by the `--show-fixes` command-line flag).", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "show-source": { "description": "Whether to show source code snippets when reporting lint violations (overridden by the `--show-source` command-line flag).", "deprecated": true, - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "src": { "description": "The directories to consider when resolving first- vs. third-party imports.\n\nAs an example: given a Python package structure like:\n\n```text my_project ├── pyproject.toml └── src └── my_package ├── __init__.py ├── foo.py └── bar.py ```\n\nThe `./src` directory should be included in the `src` option (e.g., `src = [\"src\"]`), such that when resolving imports, `my_package.foo` is considered a first-party import.\n\nWhen omitted, the `src` directory will typically default to the directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the \"project root\"), unless a configuration file is explicitly provided (e.g., via the `--config` command-line flag).\n\nThis field supports globs. For example, if you have a series of Python packages in a `python_modules` directory, `src = [\"python_modules/*\"]` would expand to incorporate all of the packages in that directory. User home directory and environment variables will also be expanded.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -714,10 +615,7 @@ "task-tags": { "description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code detection (`ERA`), and skipped by line-length rules (`E501`) if `ignore-overlong-task-comments` is set to `true`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -725,10 +623,7 @@ "typing-modules": { "description": "A list of modules whose exports should be treated equivalently to members of the `typing` module.\n\nThis is useful for ensuring proper type annotation inference for projects that re-export `typing` and `typing_extensions` members from a compatibility module. If omitted, any members imported from modules apart from `typing` and `typing_extensions` will be treated as ordinary Python objects.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -736,29 +631,21 @@ "unfixable": { "description": "A list of rule codes or prefixes to consider non-fixable.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "unsafe-fixes": { "description": "Enable application of unsafe fixes. If excluded, a hint will be displayed when unsafe fixes are available. If set to false, the hint will be hidden.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false, "definitions": { "ApiBan": { "type": "object", - "required": [ - "msg" - ], + "required": ["msg"], "properties": { "msg": { "description": "The message to display when the API is used.", @@ -775,36 +662,24 @@ }, "ConstantType": { "type": "string", - "enum": [ - "bytes", - "complex", - "float", - "int", - "str" - ] + "enum": ["bytes", "complex", "float", "int", "str"] }, "Convention": { "oneOf": [ { "description": "Use Google-style docstrings.", "type": "string", - "enum": [ - "google" - ] + "enum": ["google"] }, { "description": "Use NumPy-style docstrings.", "type": "string", - "enum": [ - "numpy" - ] + "enum": ["numpy"] }, { "description": "Use PEP257-style docstrings.", "type": "string", - "enum": [ - "pep257" - ] + "enum": ["pep257"] } ] }, @@ -837,38 +712,23 @@ "properties": { "allow-star-arg-any": { "description": "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "ignore-fully-untyped": { "description": "Whether to suppress `ANN*` rules for any declaration that hasn't been typed at all. This makes it easier to gradually add types to a codebase.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "mypy-init-return": { "description": "Whether to allow the omission of a return type hint for `__init__` if at least one argument is annotated.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "suppress-dummy-args": { "description": "Whether to suppress `ANN000`-level violations for arguments matching the \"dummy\" variable regex (like `_`).", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "suppress-none-returning": { "description": "Whether to suppress `ANN200`-level violations for functions that meet either of the following criteria:\n\n- Contain no `return` statement. - Explicit `return` statement(s) all return `None` (explicitly or implicitly).", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -878,27 +738,18 @@ "properties": { "check-typed-exception": { "description": "Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default, `try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "hardcoded-tmp-directory": { "description": "A list of directories to consider temporary.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "hardcoded-tmp-directory-extend": { "description": "A list of directories to consider temporary, in addition to those specified by `hardcoded-tmp-directory`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -911,10 +762,7 @@ "properties": { "extend-allowed-calls": { "description": "Additional callable functions with which to allow boolean traps.\n\nExpects to receive a list of fully-qualified names (e.g., `pydantic.Field`, rather than `Field`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -927,10 +775,7 @@ "properties": { "extend-immutable-calls": { "description": "Additional callable functions to consider \"immutable\" when evaluating, e.g., the `function-call-in-default-argument` rule (`B008`) or `function-call-in-dataclass-defaults` rule (`RUF009`).\n\nExpects to receive a list of fully-qualified names (e.g., `fastapi.Query`, rather than `Query`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -943,10 +788,7 @@ "properties": { "builtins-ignorelist": { "description": "Ignore list of builtins.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -959,10 +801,7 @@ "properties": { "allow-dict-calls-with-keyword-arguments": { "description": "Allow `dict` calls that make use of keyword arguments (e.g., `dict(a=1, b=2)`).", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -972,26 +811,17 @@ "properties": { "author": { "description": "Author to enforce within the copyright notice. If provided, the author must be present immediately following the copyright notice.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "min-file-size": { "description": "A minimum file size (in bytes) required for a copyright notice to be enforced. By default, all files are validated.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "notice-rgx": { "description": "The regular expression used to match the copyright notice, compiled with the [`regex`](https://docs.rs/regex/latest/regex/) crate. Defaults to `(?i)Copyright\\s+((?:\\(C\\)|©)\\s+)?\\d{4}((-|,\\s)\\d{4})*`, which matches the following:\n\n- `Copyright 2023` - `Copyright (C) 2023` - `Copyright 2021-2023` - `Copyright (C) 2021-2023` - `Copyright (C) 2021, 2023`", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "additionalProperties": false @@ -1001,10 +831,7 @@ "properties": { "max-string-length": { "description": "Maximum string length for string literals in exception messages.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 } @@ -1016,20 +843,14 @@ "properties": { "extend-function-names": { "description": "Additional function names to consider as internationalization calls, in addition to those included in `function-names`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "function-names": { "description": "The function names to consider as internationalization calls.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1042,10 +863,7 @@ "properties": { "allow-multiline": { "description": "Whether to allow implicit string concatenations for multiline strings. By default, implicit concatenations of multiline strings are allowed (but continuation lines, delimited with a backslash, are prohibited).\n\nNote that setting `allow-multiline = false` should typically be coupled with disabling `explicit-string-concatenation` (`ISC003`). Otherwise, both explicit and implicit multiline string concatenations will be seen as violations.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -1055,30 +873,21 @@ "properties": { "aliases": { "description": "The conventional aliases for imports. These aliases can be extended by the `extend-aliases` option.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "string" } }, "banned-aliases": { "description": "A mapping from module to its banned import aliases.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "$ref": "#/definitions/BannedAliases" } }, "banned-from": { "description": "A list of modules that should not be imported from using the `from ... import ...` syntax.\n\nFor example, given `banned-from = [\"pandas\"]`, `from pandas import DataFrame` would be disallowed, while `import pandas` would be allowed.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" }, @@ -1086,10 +895,7 @@ }, "extend-aliases": { "description": "A mapping from module to conventional import alias. These aliases will be added to the `aliases` mapping.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "string" } @@ -1102,17 +908,11 @@ "properties": { "fixture-parentheses": { "description": "Boolean flag specifying whether `@pytest.fixture()` without parameters should have parentheses. If the option is set to `true` (the default), `@pytest.fixture()` is valid and `@pytest.fixture` is invalid. If set to `false`, `@pytest.fixture` is valid and `@pytest.fixture()` is invalid.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "mark-parentheses": { "description": "Boolean flag specifying whether `@pytest.mark.foo()` without parameters should have parentheses. If the option is set to `true` (the default), `@pytest.mark.foo()` is valid and `@pytest.mark.foo` is invalid. If set to `false`, `@pytest.fixture` is valid and `@pytest.mark.foo()` is invalid.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "parametrize-names-type": { "description": "Expected type for multiple argument names in `@pytest.mark.parametrize`. The following values are supported:\n\n- `csv` — a comma-separated list, e.g. `@pytest.mark.parametrize('name1,name2', ...)` - `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'), ...)` - `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`", @@ -1149,20 +949,14 @@ }, "raises-extend-require-match-for": { "description": "List of additional exception names that require a match= parameter in a `pytest.raises()` call. This extends the default list of exceptions that require a match= parameter. This option is useful if you want to extend the default list of exceptions that require a match= parameter without having to specify the entire list. Note that this option does not remove any exceptions from the default list.\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "raises-require-match-for": { "description": "List of exception names that require a match= parameter in a `pytest.raises()` call.\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1175,10 +969,7 @@ "properties": { "avoid-escape": { "description": "Whether to avoid using single quotes if a string contains single quotes, or vice-versa with double quotes, as per [PEP 8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the need to escape quotation marks within strings.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "docstring-quotes": { "description": "Quote style to prefer for docstrings (either \"single\" or \"double\").\n\nWhen using the formatter, only \"double\" is compatible, as the formatter enforces double quotes for docstrings strings.", @@ -1221,20 +1012,14 @@ "properties": { "extend-ignore-names": { "description": "Additional names to ignore when considering `flake8-self` violations, in addition to those included in `ignore-names`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "ignore-names": { "description": "A list of names to ignore when considering `flake8-self` violations.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1258,20 +1043,14 @@ }, "banned-api": { "description": "Specific modules or module members that may not be imported or accessed. Note that this rule is only meant to flag accidental uses, and can be circumvented via `eval` or `importlib`.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "$ref": "#/definitions/ApiBan" } }, "banned-module-level-imports": { "description": "List of specific modules that may not be imported at module level, and should instead be imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:` block, or some other nested context).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1284,47 +1063,32 @@ "properties": { "exempt-modules": { "description": "Exempt certain modules from needing to be moved into type-checking blocks.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "quote-annotations": { "description": "Whether to add quotes around type annotations, if doing so would allow the corresponding import to be moved into a type-checking block.\n\nFor example, in the following, Python requires that `Sequence` be available at runtime, despite the fact that it's only used in a type annotation:\n\n```python from collections.abc import Sequence\n\ndef func(value: Sequence[int]) -> None: ... ```\n\nIn other words, moving `from collections.abc import Sequence` into an `if TYPE_CHECKING:` block above would cause a runtime error, as the type would no longer be available at runtime.\n\nBy default, Ruff will respect such runtime semantics and avoid moving the import to prevent such runtime errors.\n\nSetting `quote-annotations` to `true` will instruct Ruff to add quotes around the annotation (e.g., `\"Sequence[int]\"`), which in turn enables Ruff to move the import into an `if TYPE_CHECKING:` block, like so:\n\n```python from typing import TYPE_CHECKING\n\nif TYPE_CHECKING: from collections.abc import Sequence\n\ndef func(value: \"Sequence[int]\") -> None: ... ```\n\nNote that this setting has no effect when `from __future__ import annotations` is present, as `__future__` annotations are always treated equivalently to quoted annotations.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "runtime-evaluated-base-classes": { "description": "Exempt classes that list any of the enumerated classes as a base class from needing to be moved into type-checking blocks.\n\nCommon examples include Pydantic's `pydantic.BaseModel` and SQLAlchemy's `sqlalchemy.orm.DeclarativeBase`, but can also support user-defined classes that inherit from those base classes. For example, if you define a common `DeclarativeBase` subclass that's used throughout your project (e.g., `class Base(DeclarativeBase) ...` in `base.py`), you can add it to this list (`runtime-evaluated-base-classes = [\"base.Base\"]`) to exempt models from being moved into type-checking blocks.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "runtime-evaluated-decorators": { "description": "Exempt classes and functions decorated with any of the enumerated decorators from being moved into type-checking blocks.\n\nCommon examples include Pydantic's `@pydantic.validate_call` decorator (for functions) and attrs' `@attrs.define` decorator (for classes).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "strict": { "description": "Enforce TC001, TC002, and TC003 rules even when valid runtime imports are present for the same module.\n\nSee flake8-type-checking's [strict](https://github.com/snok/flake8-type-checking#strict) option.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -1334,10 +1098,7 @@ "properties": { "ignore-variadic-names": { "description": "Whether to allow unused variadic arguments, like `*args` and `**kwargs`.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -1348,10 +1109,7 @@ "properties": { "docstring-code-format": { "description": "Whether to format code snippets in docstrings.\n\nWhen this is enabled, Python code examples within docstrings are automatically reformatted.\n\nFor example, when this is enabled, the following code:\n\n```python def f(x): \"\"\" Something about `f`. And an example in doctest format:\n\n>>> f( x )\n\nMarkdown is also supported:\n\n```py f( x ) ```\n\nAs are reStructuredText literal blocks::\n\nf( x )\n\nAnd reStructuredText code blocks:\n\n.. code-block:: python\n\nf( x ) \"\"\" pass ```\n\n... will be reformatted (assuming the rest of the options are set to their defaults) as:\n\n```python def f(x): \"\"\" Something about `f`. And an example in doctest format:\n\n>>> f(x)\n\nMarkdown is also supported:\n\n```py f(x) ```\n\nAs are reStructuredText literal blocks::\n\nf(x)\n\nAnd reStructuredText code blocks:\n\n.. code-block:: python\n\nf(x) \"\"\" pass ```\n\nIf a code snippet in a docstring contains invalid Python code or if the formatter would otherwise write invalid Python code, then the code example is ignored by the formatter and kept as-is.\n\nCurrently, doctest, Markdown, reStructuredText literal blocks, and reStructuredText code blocks are all supported and automatically recognized. In the case of unlabeled fenced code blocks in Markdown and reStructuredText literal blocks, the contents are assumed to be Python and reformatted. As with any other format, if the contents aren't valid Python, then the block is left untouched automatically.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "docstring-code-line-length": { "description": "Set the line length used when formatting code snippets in docstrings.\n\nThis only has an effect when the `docstring-code-format` setting is enabled.\n\nThe default value for this setting is `\"dynamic\"`, which has the effect of ensuring that any reformatted code examples in docstrings adhere to the global line length configuration that is used for the surrounding Python code. The point of this setting is that it takes the indentation of the docstring into account when reformatting code examples.\n\nAlternatively, this can be set to a fixed integer, which will result in the same line length limit being applied to all reformatted code examples in docstrings. When set to a fixed integer, the indent of the docstring is not taken into account. That is, this may result in lines in the reformatted code example that exceed the globally configured line length limit.\n\nFor example, when this is set to `20` and `docstring-code-format` is enabled, then this code:\n\n```python def f(x): ''' Something about `f`. And an example:\n\n.. code-block:: python\n\nfoo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) ''' pass ```\n\n... will be reformatted (assuming the rest of the options are set to their defaults) as:\n\n```python def f(x): \"\"\" Something about `f`. And an example:\n\n.. code-block:: python\n\n( foo, bar, quux, ) = this_is_a_long_line( lion, hippo, lemur, bear, ) \"\"\" pass ```", @@ -1366,10 +1124,7 @@ }, "exclude": { "description": "A list of file patterns to exclude from formatting in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1398,10 +1153,7 @@ }, "preview": { "description": "Whether to enable the unstable preview style formatting.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "quote-style": { "description": "Configures the preferred quote character for strings. The recommended options are\n\n* `double` (default): Use double quotes `\"` * `single`: Use single quotes `'`\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for triple quoted strings and docstrings even when using `quote-style = \"single\"`.\n\nRuff deviates from using the configured quotes if doing so prevents the need for escaping quote characters inside the string:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change the quotes of the string assigned to `a` to single quotes when using `quote-style = \"single\"`. However, ruff uses double quotes for he string assigned to `b` because using single quotes would require escaping the `'`, which leads to the less readable code: `'It\\'s monday morning'`.\n\nIn addition, Ruff supports the quote style `preserve` for projects that already use a mixture of single and double quotes and can't migrate to the `double` or `single` style. The quote style `preserve` leaves the quotes of all strings unchanged.", @@ -1416,10 +1168,7 @@ }, "skip-magic-trailing-comma": { "description": "Ruff uses existing trailing commas as an indication that short lines should be left separate. If this option is set to `true`, the magic trailing comma is ignored.\n\nFor example, Ruff leaves the arguments separate even though collapsing the arguments to a single line doesn't exceed the line length if `skip-magic-trailing-comma = false`:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test( a, b, ): pass ```\n\nSetting `skip-magic-trailing-comma = true` changes the formatting to:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test(a, b): pass ```", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -1449,16 +1198,12 @@ { "description": "Use tabs to indent code.", "type": "string", - "enum": [ - "tab" - ] + "enum": ["tab"] }, { "description": "Use [`IndentWidth`] spaces to indent code.", "type": "string", - "enum": [ - "space" - ] + "enum": ["space"] } ] }, @@ -1473,34 +1218,22 @@ "properties": { "case-sensitive": { "description": "Sort imports taking into account case sensitivity.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "classes": { "description": "An override list of tokens to always recognize as a Class for `order-by-type` regardless of casing.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "combine-as-imports": { "description": "Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports) option.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "constants": { "description": "An override list of tokens to always recognize as a CONSTANT for `order-by-type` regardless of casing.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1518,153 +1251,99 @@ }, "detect-same-package": { "description": "Whether to automatically mark imports from within the same package as first-party. For example, when `detect-same-package = true`, then when analyzing files within the `foo` package, any imports from within the `foo` package will be considered first-party.\n\nThis heuristic is often unnecessary when `src` is configured to detect all first-party sources; however, if `src` is _not_ configured, this heuristic can be useful to detect first-party imports from _within_ (but not _across_) first-party packages.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "extra-standard-library": { "description": "A list of modules to consider standard-library, in addition to those known to Ruff in advance.\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "force-single-line": { "description": "Forces all from imports to appear on their own line.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "force-sort-within-sections": { "description": "Don't sort straight-style imports (like `import sys`) before from-style imports (like `from itertools import groupby`). Instead, sort the imports by module, independent of import style.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "force-to-top": { "description": "Force specific imports to the top of their appropriate section.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "force-wrap-aliases": { "description": "Force `import from` statements with multiple members and at least one alias (e.g., `import A as B`) to wrap such that every line contains exactly one member. For example, this formatting would be retained, rather than condensing to a single line:\n\n```python from .utils import ( test_directory as test_directory, test_id as test_id ) ```\n\nNote that this setting is only effective when combined with `combine-as-imports = true`. When `combine-as-imports` isn't enabled, every aliased `import from` will be given its own line, in which case, wrapping is not necessary.\n\nWhen using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `force-wrap-aliases` to avoid that the formatter collapses members if they all fit on a single line.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "forced-separate": { "description": "A list of modules to separate into auxiliary block(s) of imports, in the order specified.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "from-first": { "description": "Whether to place `import from` imports before straight imports when sorting.\n\nFor example, by default, imports will be sorted such that straight imports appear before `import from` imports, as in: ```python import os import sys from typing import List ```\n\nSetting `from-first = true` will instead sort such that `import from` imports appear before straight imports, as in: ```python from typing import List import os import sys ```", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "known-first-party": { "description": "A list of modules to consider first-party, regardless of whether they can be identified as such via introspection of the local filesystem.\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "known-local-folder": { "description": "A list of modules to consider being a local folder. Generally, this is reserved for relative imports (`from . import module`).\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "known-third-party": { "description": "A list of modules to consider third-party, regardless of whether they can be identified as such via introspection of the local filesystem.\n\nSupports glob patterns. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "length-sort": { "description": "Sort imports by their string length, such that shorter imports appear before longer imports. For example, by default, imports will be sorted alphabetically, as in: ```python import collections import os ```\n\nSetting `length-sort = true` will instead sort such that shorter imports appear before longer imports, as in: ```python import os import collections ```", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "length-sort-straight": { "description": "Sort straight imports by their string length. Similar to `length-sort`, but applies only to straight imports and doesn't affect `from` imports.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "lines-after-imports": { "description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nRuff uses at most one blank line after imports in typing stub files (files with `.pyi` extension) in accordance to the typing style recommendations ([source](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)).\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int" }, "lines-between-types": { "description": "The number of lines to place between \"direct\" and `import from` imports.\n\nWhen using the formatter, only the values `0` and `1` are compatible because it preserves up to one empty line after imports in nested blocks.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "no-lines-before": { "description": "A list of sections that should _not_ be delineated from the previous section via empty lines.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/ImportSection" } }, "no-sections": { "description": "Put all imports into the same section bucket.\n\nFor example, rather than separating standard library and third-party imports, as in: ```python import os import sys\n\nimport numpy import pandas ```\n\nSetting `no-sections = true` will instead group all imports into a single section: ```python import os import numpy import pandas import sys ```", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "order-by-type": { "description": "Order imports by type, which is determined by case, in addition to alphabetically.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "relative-imports-order": { "description": "Whether to place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local), or vice versa.\n\nThe default (\"furthest-to-closest\") is equivalent to isort's `reverse-relative` default (`reverse-relative = false`); setting this to \"closest-to-furthest\" is equivalent to isort's `reverse-relative = true`.", @@ -1679,30 +1358,21 @@ }, "required-imports": { "description": "Add the specified import line to all files.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "section-order": { "description": "Override in which order the sections should be output. Can be used to move custom sections.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/ImportSection" } }, "sections": { - "description": "A list of mappings from section names to modules.\n\nBy default, imports are categorized according to their type (e.g., `future`, `third-party`, and so on). This setting allows you to group modules into custom sections, to augment or override the built-in sections.\n\nFor example, to group all testing utilities, you could create a `testing` section: ```toml testing = [\"pytest\", \"hypothesis\"] ```\n\nCustom sections should typically be inserted into the `section-order` list to ensure that they're displayed as a standalone group and in the intended order, as in: ```toml section-order = [ \"future\", \"standard-library\", \"third-party\", \"first-party\", \"local-folder\", \"testing\" ] ```\n\nIf a custom section is omitted from `section-order`, imports in that section will be assigned to the `default-section` (which defaults to `third-party`).", - "type": [ - "object", - "null" - ], + "description": "A list of mappings from section names to modules.\n\nBy default, imports are categorized according to their type (e.g., `future`, `third-party`, and so on). This setting allows you to group modules into custom sections, to augment or override the built-in sections.\n\nFor example, to group all testing utilities, you could create a `testing` section: ```toml testing = [\"pytest\", \"hypothesis\"] ```\n\nThe values in the list are treated as glob patterns. For example, to match all packages in the LangChain ecosystem (`langchain-core`, `langchain-openai`, etc.): ```toml langchain = [\"langchain-*\"] ```\n\nCustom sections should typically be inserted into the `section-order` list to ensure that they're displayed as a standalone group and in the intended order, as in: ```toml section-order = [ \"future\", \"standard-library\", \"third-party\", \"first-party\", \"local-folder\", \"testing\" ] ```\n\nIf a custom section is omitted from `section-order`, imports in that section will be assigned to the `default-section` (which defaults to `third-party`).", + "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { @@ -1712,27 +1382,18 @@ }, "single-line-exclusions": { "description": "One or more modules to exclude from the single line rule.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "split-on-trailing-comma": { "description": "If a comma is placed after the last member in a multi-line import, then the imports will never be folded into one line.\n\nSee isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option.\n\nWhen using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `split-on-trailing-comma` to avoid that the formatter removes the trailing commas.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "variables": { "description": "An override list of tokens to always recognize as a var for `order-by-type` regardless of casing.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -1745,30 +1406,22 @@ { "description": "The newline style is detected automatically on a file per file basis. Files with mixed line endings will be converted to the first detected line ending. Defaults to [`LineEnding::Lf`] for a files that contain no line endings.", "type": "string", - "enum": [ - "auto" - ] + "enum": ["auto"] }, { "description": "Line endings will be converted to `\\n` as is common on Unix.", "type": "string", - "enum": [ - "lf" - ] + "enum": ["lf"] }, { "description": "Line endings will be converted to `\\r\\n` as is common on Windows.", "type": "string", - "enum": [ - "cr-lf" - ] + "enum": ["cr-lf"] }, { "description": "Line endings will be converted to `\\n` on Unix and `\\r\\n` on Windows.", "type": "string", - "enum": [ - "native" - ] + "enum": ["native"] } ] }, @@ -1791,10 +1444,7 @@ "properties": { "allowed-confusables": { "description": "A list of allowed \"confusable\" Unicode characters to ignore when enforcing `RUF001`, `RUF002`, and `RUF003`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string", "maxLength": 1, @@ -1803,34 +1453,22 @@ }, "dummy-variable-rgx": { "description": "A regular expression used to identify \"dummy\" variables, or those which should be ignored when enforcing (e.g.) unused-variable rules. The default expression matches `_`, `__`, and `_var`, but not `_var_`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "exclude": { "description": "A list of file patterns to exclude from linting in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "explicit-preview-rules": { "description": "Whether to require exact codes to select preview rules. When enabled, preview rules will not be selected by prefixes — the full code of each preview rule will be required to enable the rule.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "extend-fixable": { "description": "A list of rule codes or prefixes to consider fixable, in addition to those specified by `fixable`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -1838,20 +1476,14 @@ "extend-ignore": { "description": "A list of rule codes or prefixes to ignore, in addition to those specified by `ignore`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "extend-per-file-ignores": { "description": "A list of mappings from file pattern to rule codes or prefixes to exclude, in addition to any rules excluded by `per-file-ignores`.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { @@ -1861,20 +1493,14 @@ }, "extend-safe-fixes": { "description": "A list of rule codes or prefixes for which unsafe fixes should be considered safe.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "extend-select": { "description": "A list of rule codes or prefixes to enable, in addition to those specified by `select`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -1882,40 +1508,28 @@ "extend-unfixable": { "description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those specified by `unfixable`.", "deprecated": true, - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "extend-unsafe-fixes": { "description": "A list of rule codes or prefixes for which safe fixes should be considered unsafe.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "external": { "description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "fixable": { "description": "A list of rule codes or prefixes to consider fixable. By default, all rules are considered fixable.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -2109,20 +1723,14 @@ }, "ignore": { "description": "A list of rule codes or prefixes to ignore. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "ignore-init-module-imports": { "description": "Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be flagged, but with a dedicated message suggesting that the import is either added to the module's `__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).\n\nThis option is enabled by default, but you can opt-in to removal of imports via an unsafe fix.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "isort": { "description": "Options for the `isort` plugin.", @@ -2137,10 +1745,7 @@ }, "logger-objects": { "description": "A list of objects that should be treated equivalently to a `logging.Logger` object.\n\nThis is useful for ensuring proper diagnostics (e.g., to identify `logging` deprecations and other best-practices) for projects that re-export a `logging.Logger` object from a common module.\n\nFor example, if you have a module `logging_setup.py` with the following contents: ```python import logging\n\nlogger = logging.getLogger(__name__) ```\n\nAdding `\"logging_setup.logger\"` to `logger-objects` will ensure that `logging_setup.logger` is treated as a `logging.Logger` object when imported from other modules (e.g., `from logging_setup import logger`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -2169,10 +1774,7 @@ }, "per-file-ignores": { "description": "A list of mappings from file pattern to rule codes or prefixes to exclude, when considering any matching files. An initial '!' negates the file pattern.", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": { "type": "array", "items": { @@ -2182,10 +1784,7 @@ }, "preview": { "description": "Whether to enable preview mode. When preview mode is enabled, Ruff will use unstable rules and fixes.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "pycodestyle": { "description": "Options for the `pycodestyle` plugin.", @@ -2244,40 +1843,28 @@ }, "select": { "description": "A list of rule codes or prefixes to enable. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } }, "task-tags": { "description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code detection (`ERA`), and skipped by line-length rules (`E501`) if `ignore-overlong-task-comments` is set to `true`.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "typing-modules": { "description": "A list of modules whose exports should be treated equivalently to members of the `typing` module.\n\nThis is useful for ensuring proper type annotation inference for projects that re-export `typing` and `typing_extensions` members from a compatibility module. If omitted, any members imported from modules apart from `typing` and `typing_extensions` will be treated as ordinary Python objects.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "unfixable": { "description": "A list of rule codes or prefixes to consider non-fixable.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/RuleSelector" } @@ -2290,10 +1877,7 @@ "properties": { "max-complexity": { "description": "The maximum McCabe complexity to allow before triggering `C901` errors.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 } @@ -2302,65 +1886,43 @@ }, "ParametrizeNameType": { "type": "string", - "enum": [ - "csv", - "tuple", - "list" - ] + "enum": ["csv", "tuple", "list"] }, "ParametrizeValuesRowType": { "type": "string", - "enum": [ - "tuple", - "list" - ] + "enum": ["tuple", "list"] }, "ParametrizeValuesType": { "type": "string", - "enum": [ - "tuple", - "list" - ] + "enum": ["tuple", "list"] }, "Pep8NamingOptions": { "type": "object", "properties": { "classmethod-decorators": { "description": "A list of decorators that, when applied to a method, indicate that the method should be treated as a class method (in addition to the builtin `@classmethod`).\n\nFor example, Ruff will expect that any method decorated by a decorator in this list takes a `cls` argument as its first argument.\n\nExpects to receive a list of fully-qualified names (e.g., `pydantic.validator`, rather than `validator`) or alternatively a plain name which is then matched against the last segment in case the decorator itself consists of a dotted name.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "extend-ignore-names": { "description": "Additional names (or patterns) to ignore when considering `pep8-naming` violations, in addition to those included in `ignore-names`\n\nSupports glob patterns. For example, to ignore all names starting with or ending with `_test`, you could use `ignore-names = [\"test_*\", \"*_test\"]`. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "ignore-names": { "description": "A list of names (or patterns) to ignore when considering `pep8-naming` violations.\n\nSupports glob patterns. For example, to ignore all names starting with or ending with `_test`, you could use `ignore-names = [\"test_*\", \"*_test\"]`. For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "staticmethod-decorators": { "description": "A list of decorators that, when applied to a method, indicate that the method should be treated as a static method (in addition to the builtin `@staticmethod`).\n\nFor example, Ruff will expect that any method decorated by a decorator in this list has no `self` or `cls` argument.\n\nExpects to receive a list of fully-qualified names (e.g., `belay.Device.teardown`, rather than `teardown`) or alternatively a plain name which is then matched against the last segment in case the decorator itself consists of a dotted name.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -2373,10 +1935,7 @@ "properties": { "keep-runtime-typing": { "description": "Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Union[str, int]` -> `str | int`) rewrites even if a file imports `from __future__ import annotations`.\n\nThis setting is only applicable when the target Python version is below 3.9 and 3.10 respectively, and is most commonly used when working with libraries like Pydantic and FastAPI, which rely on the ability to parse type annotations at runtime. The use of `from __future__ import annotations` causes Python to treat the type annotations as strings, which typically allows for the use of language features that appear in later Python versions but are not yet supported by the current version (e.g., `str | int`). However, libraries that rely on runtime type annotations will break if the annotations are incompatible with the current Python version.\n\nFor example, while the following is valid Python 3.8 code due to the presence of `from __future__ import annotations`, the use of `str| int` prior to Python 3.10 will cause Pydantic to raise a `TypeError` at runtime:\n\n```python from __future__ import annotations\n\nimport pydantic\n\nclass Foo(pydantic.BaseModel): bar: str | int ```", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } }, "additionalProperties": false @@ -2386,10 +1945,7 @@ "properties": { "ignore-overlong-task-comments": { "description": "Whether line-length violations (`E501`) should be triggered for comments starting with `task-tags` (by default: \\[\"TODO\", \"FIXME\", and \"XXX\"\\]).", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "max-doc-length": { "description": "The maximum line length to allow for [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) violations within documentation (`W505`), including standalone comments. By default, this is set to null which disables reporting violations.\n\nThe length is determined by the number of characters per line, except for lines containing Asian characters or emojis. For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.\n\nSee the [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) rule for more information.", @@ -2432,20 +1988,14 @@ }, "ignore-decorators": { "description": "Ignore docstrings for functions or methods decorated with the specified fully-qualified decorators.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "property-decorators": { "description": "A list of decorators that, when applied to a method, indicate that the method should be treated as a property (in addition to the builtin `@property` and standard-library `@functools.cached_property`).\n\nFor example, Ruff will expect that any method decorated by a decorator in this list can use a non-imperative summary line.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -2458,10 +2008,7 @@ "properties": { "extend-generics": { "description": "Additional functions or classes to consider generic, such that any subscripts should be treated as type annotation (e.g., `ForeignKey` in `django.db.models.ForeignKey[\"User\"]`.\n\nExpects to receive a list of fully-qualified names (e.g., `django.db.models.ForeignKey`, rather than `ForeignKey`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -2474,10 +2021,7 @@ "properties": { "allow-dunder-method-names": { "description": "Dunder methods name to allow, in addition to the default set from the Python standard library (see: `PLW3201`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" }, @@ -2485,92 +2029,62 @@ }, "allow-magic-value-types": { "description": "Constant types to ignore when used as \"magic values\" (see: `PLR2004`).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/definitions/ConstantType" } }, "max-args": { "description": "Maximum number of arguments allowed for a function or method definition (see: `PLR0913`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-bool-expr": { "description": "Maximum number of Boolean expressions allowed within a single `if` statement (see: `PLR0916`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-branches": { "description": "Maximum number of branches allowed for a function or method body (see: `PLR0912`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-locals": { "description": "Maximum number of local variables allowed for a function or method body (see: `PLR0914`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-nested-blocks": { "description": "Maximum number of nested blocks allowed within a function or method body (see: `PLR1702`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-positional-args": { "description": "Maximum number of positional arguments allowed for a function or method definition (see: `PLR0917`).\n\nIf not specified, defaults to the value of `max-args`.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-public-methods": { "description": "Maximum number of public methods allowed for a class (see: `PLR0904`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-returns": { "description": "Maximum number of return statements allowed for a function or method body (see `PLR0911`)", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 }, "max-statements": { "description": "Maximum number of statements allowed for a function or method body (see: `PLR0915`).", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0.0 } @@ -2579,56 +2093,37 @@ }, "PythonVersion": { "type": "string", - "enum": [ - "py37", - "py38", - "py39", - "py310", - "py311", - "py312" - ] + "enum": ["py37", "py38", "py39", "py310", "py311", "py312"] }, "Quote": { "oneOf": [ { "description": "Use double quotes.", "type": "string", - "enum": [ - "double" - ] + "enum": ["double"] }, { "description": "Use single quotes.", "type": "string", - "enum": [ - "single" - ] + "enum": ["single"] } ] }, "QuoteStyle": { "type": "string", - "enum": [ - "single", - "double", - "preserve" - ] + "enum": ["single", "double", "preserve"] }, "RelativeImportsOrder": { "oneOf": [ { "description": "Place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local).", "type": "string", - "enum": [ - "closest-to-furthest" - ] + "enum": ["closest-to-furthest"] }, { "description": "Place \"further\" imports (more `.` characters, least local) imports before \"closer\" imports (fewer `.` characters, most local).", "type": "string", - "enum": [ - "furthest-to-closest" - ] + "enum": ["furthest-to-closest"] } ] }, @@ -3573,6 +3068,7 @@ "PYI055", "PYI056", "PYI058", + "PYI059", "PYI06", "PYI062", "Q", @@ -3947,18 +3443,14 @@ { "description": "Ban imports that extend into the parent module or beyond.", "type": "string", - "enum": [ - "parents" - ] + "enum": ["parents"] }, { "description": "Ban all relative imports.", "type": "string", - "enum": [ - "all" - ] + "enum": ["all"] } ] } } -} \ No newline at end of file +}