From ebe52869a344ca11263934c208dc18412acd794b Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 1 Jul 2021 18:17:38 +0200 Subject: [PATCH] Merge commit '61eb38aeda6cb54b93b872bf503d70084c4d621c' into clippyup --- .github/workflows/clippy.yml | 2 +- .github/workflows/clippy_bors.yml | 7 +- .github/workflows/remark.yml | 2 +- .remarkrc | 1 + CHANGELOG.md | 139 ++++++- CONTRIBUTING.md | 2 +- Cargo.toml | 2 +- README.md | 26 +- clippy_dev/Cargo.toml | 2 +- clippy_dev/src/fmt.rs | 8 +- clippy_dev/src/ide_setup.rs | 103 ----- clippy_dev/src/lib.rs | 2 +- clippy_dev/src/main.rs | 81 +++- clippy_dev/src/setup/git_hook.rs | 85 ++++ clippy_dev/src/setup/intellij.rs | 223 +++++++++++ clippy_dev/src/setup/mod.rs | 23 ++ clippy_dev/src/setup/vscode.rs | 104 +++++ clippy_dev/src/update_lints.rs | 5 +- clippy_lints/Cargo.toml | 3 +- clippy_lints/src/assign_ops.rs | 2 +- clippy_lints/src/attrs.rs | 2 +- clippy_lints/src/blacklisted_name.rs | 31 +- clippy_lints/src/bytecount.rs | 70 ++-- clippy_lints/src/collapsible_match.rs | 20 +- clippy_lints/src/default.rs | 3 +- clippy_lints/src/default_numeric_fallback.rs | 4 +- clippy_lints/src/deprecated_lints.rs | 4 +- clippy_lints/src/derive.rs | 7 +- clippy_lints/src/disallowed_method.rs | 29 +- clippy_lints/src/disallowed_script_idents.rs | 112 ++++++ clippy_lints/src/disallowed_type.rs | 126 ++++++ clippy_lints/src/doc.rs | 64 ++- clippy_lints/src/eval_order_dependence.rs | 2 +- .../src/float_equality_without_abs.rs | 2 +- clippy_lints/src/formatting.rs | 6 +- clippy_lints/src/get_last_with_len.rs | 2 +- clippy_lints/src/if_let_mutex.rs | 2 +- clippy_lints/src/len_zero.rs | 12 +- clippy_lints/src/lib.rs | 82 ++-- clippy_lints/src/loops/manual_memcpy.rs | 2 +- clippy_lints/src/loops/mod.rs | 6 +- clippy_lints/src/loops/needless_collect.rs | 97 ++--- clippy_lints/src/loops/needless_range_loop.rs | 2 +- .../src/loops/while_let_on_iterator.rs | 11 +- clippy_lints/src/manual_map.rs | 20 +- clippy_lints/src/map_identity.rs | 126 ------ clippy_lints/src/matches.rs | 14 +- .../src/methods/append_instead_of_extend.rs | 41 ++ .../src/methods/filter_map_identity.rs | 40 +- clippy_lints/src/methods/flat_map_identity.rs | 44 +-- clippy_lints/src/methods/map_identity.rs | 38 ++ clippy_lints/src/methods/mod.rs | 65 +++- clippy_lints/src/methods/or_fun_call.rs | 2 +- .../src/missing_enforced_import_rename.rs | 102 +++++ clippy_lints/src/mut_key.rs | 2 +- clippy_lints/src/no_effect.rs | 2 +- clippy_lints/src/nonstandard_macro_braces.rs | 276 +++++++++++++ clippy_lints/src/ranges.rs | 2 +- clippy_lints/src/redundant_clone.rs | 2 +- .../src/semicolon_if_nothing_returned.rs | 2 + clippy_lints/src/suspicious_trait_impl.rs | 4 +- clippy_lints/src/unnested_or_patterns.rs | 5 +- clippy_lints/src/use_self.rs | 363 +++++------------- clippy_lints/src/useless_conversion.rs | 2 +- clippy_lints/src/utils/conf.rs | 24 +- .../internal_lints/metadata_collector.rs | 40 +- clippy_lints/src/wildcard_imports.rs | 12 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/src/ast_utils.rs | 11 +- clippy_utils/src/attrs.rs | 5 + clippy_utils/src/consts.rs | 10 +- clippy_utils/src/lib.rs | 106 +++-- clippy_utils/src/ptr.rs | 32 +- doc/basics.md | 4 +- doc/common_tools_writing_lints.md | 2 +- doc/release.md | 15 + lintcheck/README.md | 2 +- lintcheck/src/main.rs | 9 +- rust-toolchain | 2 +- src/main.rs | 26 +- tests/compile-test.rs | 19 +- .../clippy.toml | 10 + .../conf_missing_enforced_import_rename.rs | 16 + ...conf_missing_enforced_import_rename.stderr | 40 ++ .../nonstandard_macro_braces/clippy.toml | 6 + .../conf_nonstandard_macro_braces.rs | 44 +++ .../conf_nonstandard_macro_braces.stderr | 94 +++++ .../toml_disallowed_method/clippy.toml | 6 +- .../ui-toml/toml_disallowed_type/clippy.toml | 9 + .../conf_disallowed_type.rs | 35 ++ .../conf_disallowed_type.stderr | 88 +++++ .../toml_unknown_key/conf_unknown_key.stderr | 2 +- tests/ui/append_instead_of_extend.fixed | 55 +++ tests/ui/append_instead_of_extend.rs | 55 +++ tests/ui/append_instead_of_extend.stderr | 22 ++ tests/ui/assertions_on_constants.rs | 3 + tests/ui/auxiliary/macro_rules.rs | 7 + tests/ui/auxiliary/non-exhaustive-enum.rs | 8 + tests/ui/blacklisted_name.rs | 12 + tests/ui/default_numeric_fallback.rs | 23 ++ tests/ui/default_numeric_fallback.stderr | 61 +-- tests/ui/deprecated.stderr | 4 +- tests/ui/disallowed_script_idents.rs | 10 + tests/ui/disallowed_script_idents.stderr | 20 + tests/ui/{ => doc}/doc.rs | 2 +- tests/ui/{ => doc}/doc.stderr | 0 tests/ui/doc/unbalanced_ticks.rs | 36 ++ tests/ui/doc/unbalanced_ticks.stderr | 64 +++ tests/ui/field_reassign_with_default.rs | 20 + tests/ui/field_reassign_with_default.stderr | 36 +- tests/ui/filter_map_identity.fixed | 5 +- tests/ui/filter_map_identity.rs | 5 +- tests/ui/filter_map_identity.stderr | 14 +- tests/ui/flat_map_identity.fixed | 5 +- tests/ui/flat_map_identity.rs | 5 +- tests/ui/flat_map_identity.stderr | 12 +- tests/ui/match_same_arms.stderr | 7 + tests/ui/match_same_arms2.stderr | 7 + .../match_wildcard_for_single_variants.fixed | 7 + .../ui/match_wildcard_for_single_variants.rs | 7 + tests/ui/mut_key.stderr | 2 +- tests/ui/semicolon_if_nothing_returned.rs | 44 +++ tests/ui/semicolon_if_nothing_returned.stderr | 14 +- tests/ui/suspicious_arithmetic_impl.stderr | 2 +- tests/ui/unnecessary_cast_fixable.fixed | 2 +- tests/ui/unnecessary_cast_fixable.rs | 2 +- tests/ui/use_self.fixed | 23 ++ tests/ui/use_self.rs | 25 +- tests/ui/use_self.stderr | 4 +- tests/ui/vec.fixed | 2 +- tests/ui/vec.rs | 2 +- tests/ui/while_let_on_iterator.fixed | 14 + tests/ui/while_let_on_iterator.rs | 14 + tests/ui/while_let_on_iterator.stderr | 10 +- tests/ui/wildcard_enum_match_arm.fixed | 39 +- tests/ui/wildcard_enum_match_arm.rs | 37 +- tests/ui/wildcard_enum_match_arm.stderr | 22 +- tests/ui/zero_offset.rs | 20 +- tests/ui/zero_offset.stderr | 55 ++- util/etc/pre-commit.sh | 21 + util/etc/vscode-tasks.json | 57 +++ util/lintlib.py | 5 +- 142 files changed, 3185 insertions(+), 1086 deletions(-) delete mode 100644 clippy_dev/src/ide_setup.rs create mode 100644 clippy_dev/src/setup/git_hook.rs create mode 100644 clippy_dev/src/setup/intellij.rs create mode 100644 clippy_dev/src/setup/mod.rs create mode 100644 clippy_dev/src/setup/vscode.rs create mode 100644 clippy_lints/src/disallowed_script_idents.rs create mode 100644 clippy_lints/src/disallowed_type.rs delete mode 100644 clippy_lints/src/map_identity.rs create mode 100644 clippy_lints/src/methods/append_instead_of_extend.rs create mode 100644 clippy_lints/src/methods/map_identity.rs create mode 100644 clippy_lints/src/missing_enforced_import_rename.rs create mode 100644 clippy_lints/src/nonstandard_macro_braces.rs create mode 100644 tests/ui-toml/missing_enforced_import_rename/clippy.toml create mode 100644 tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs create mode 100644 tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.stderr create mode 100644 tests/ui-toml/nonstandard_macro_braces/clippy.toml create mode 100644 tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs create mode 100644 tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr create mode 100644 tests/ui-toml/toml_disallowed_type/clippy.toml create mode 100644 tests/ui-toml/toml_disallowed_type/conf_disallowed_type.rs create mode 100644 tests/ui-toml/toml_disallowed_type/conf_disallowed_type.stderr create mode 100644 tests/ui/append_instead_of_extend.fixed create mode 100644 tests/ui/append_instead_of_extend.rs create mode 100644 tests/ui/append_instead_of_extend.stderr create mode 100644 tests/ui/auxiliary/non-exhaustive-enum.rs create mode 100644 tests/ui/disallowed_script_idents.rs create mode 100644 tests/ui/disallowed_script_idents.stderr rename tests/ui/{ => doc}/doc.rs (99%) rename tests/ui/{ => doc}/doc.stderr (100%) create mode 100644 tests/ui/doc/unbalanced_ticks.rs create mode 100644 tests/ui/doc/unbalanced_ticks.stderr create mode 100755 util/etc/pre-commit.sh create mode 100644 util/etc/vscode-tasks.json diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 32103f59d8b..d856c55a41a 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -71,7 +71,7 @@ jobs: working-directory: clippy_workspace_tests - name: Test cargo-clippy --fix - run: ../target/debug/cargo-clippy clippy --fix -Zunstable-options + run: ../target/debug/cargo-clippy clippy --fix working-directory: clippy_workspace_tests - name: Test clippy-driver diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index f27fee87dc1..146b6fccd0c 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -90,11 +90,6 @@ jobs: - name: Checkout uses: actions/checkout@v2.3.3 - # FIXME: should not be necessary once 1.24.2 is the default version on the windows runner - - name: Update rustup - run: rustup self update - if: runner.os == 'Windows' - - name: Install toolchain run: rustup show active-toolchain @@ -139,7 +134,7 @@ jobs: working-directory: clippy_workspace_tests - name: Test cargo-clippy --fix - run: ../target/debug/cargo-clippy clippy --fix -Zunstable-options + run: ../target/debug/cargo-clippy clippy --fix working-directory: clippy_workspace_tests - name: Test clippy-driver diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 4f25a86b2e4..77efdec1e50 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -22,7 +22,7 @@ jobs: uses: actions/setup-node@v1.4.4 - name: Install remark - run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended + run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm # Run - name: Check *.md files diff --git a/.remarkrc b/.remarkrc index 0ede7ac75cb..04b82b8cc58 100644 --- a/.remarkrc +++ b/.remarkrc @@ -1,6 +1,7 @@ { "plugins": [ "remark-preset-lint-recommended", + "remark-gfm", ["remark-lint-list-item-indent", false], ["remark-lint-no-literal-urls", false], ["remark-lint-no-shortcut-reference-link", false], diff --git a/CHANGELOG.md b/CHANGELOG.md index 41af8e190dd..f3a80703238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,139 @@ document. ## Unreleased / In Rust Nightly -[7c7683c...master](https://github.com/rust-lang/rust-clippy/compare/7c7683c...master) +[3ae8faf...master](https://github.com/rust-lang/rust-clippy/compare/3ae8faf...master) + +## Rust 1.54 + +Current beta, release 2021-07-29 + +[7c7683c...3ae8faf](https://github.com/rust-lang/rust-clippy/compare/7c7683c...3ae8faf) + +### New Lints + +- [`ref_binding_to_reference`] + [#7105](https://github.com/rust-lang/rust-clippy/pull/7105) +- [`needless_bitwise_bool`] + [#7133](https://github.com/rust-lang/rust-clippy/pull/7133) +- [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225) +- [`manual_str_repeat`] + [#7265](https://github.com/rust-lang/rust-clippy/pull/7265) +- [`suspicious_splitn`] + [#7292](https://github.com/rust-lang/rust-clippy/pull/7292) + +### Moves and Deprecations + +- Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of + the new `avoid_breaking_exported_api` config option (see + [Enhancements](#1-54-enhancements)) + [#7187](https://github.com/rust-lang/rust-clippy/pull/7187) +- Move [`inconsistent_struct_constructor`] to `pedantic` + [#7193](https://github.com/rust-lang/rust-clippy/pull/7193) +- Move [`needless_borrow`] to `style` (now warn-by-default) + [#7254](https://github.com/rust-lang/rust-clippy/pull/7254) +- Move [`suspicious_operation_groupings`] to `nursery` + [#7266](https://github.com/rust-lang/rust-clippy/pull/7266) +- Move [`semicolon_if_nothing_returned`] to `pedantic` + [#7268](https://github.com/rust-lang/rust-clippy/pull/7268) + +### Enhancements + +- [`while_let_on_iterator`]: Now also lints in nested loops + [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) +- [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix` + [#7156](https://github.com/rust-lang/rust-clippy/pull/7156) +- [`needless_collect`]: Now also lints on assignments with type annotations + [#7163](https://github.com/rust-lang/rust-clippy/pull/7163) +- [`if_then_some_else_none`]: Now works with the MSRV config + [#7177](https://github.com/rust-lang/rust-clippy/pull/7177) +- Add `avoid_breaking_exported_api` config option for the lints + [`enum_variant_names`], [`large_types_passed_by_value`], + [`trivially_copy_pass_by_ref`], [`unnecessary_wraps`], + [`upper_case_acronyms`], and [`wrong_self_convention`]. We recommend to set + this configuration option to `false` before a major release (1.0/2.0/...) to + clean up the API [#7187](https://github.com/rust-lang/rust-clippy/pull/7187) +- [`needless_collect`]: Now lints on even more data structures + [#7188](https://github.com/rust-lang/rust-clippy/pull/7188) +- [`missing_docs_in_private_items`]: No longer sees `#[ = ""]` like + attributes as sufficient documentation + [#7281](https://github.com/rust-lang/rust-clippy/pull/7281) +- [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]: + Now work as expected when used with `allow` + [#7282](https://github.com/rust-lang/rust-clippy/pull/7282) + +### False Positive Fixes + +- [`implicit_return`]: Now takes all diverging functions in account to avoid + false positives [#6951](https://github.com/rust-lang/rust-clippy/pull/6951) +- [`while_let_on_iterator`]: No longer lints when the iterator is a struct field + and the struct is used in the loop + [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) +- [`multiple_inherent_impl`]: No longer lints with generic arguments + [#7089](https://github.com/rust-lang/rust-clippy/pull/7089) +- [`comparison_chain`]: No longer lints in a `const` context + [#7118](https://github.com/rust-lang/rust-clippy/pull/7118) +- [`while_immutable_condition`]: Fix false positive where mutation in the loop + variable wasn't picked up + [#7144](https://github.com/rust-lang/rust-clippy/pull/7144) +- [`default_trait_access`]: No longer lints in macros + [#7150](https://github.com/rust-lang/rust-clippy/pull/7150) +- [`needless_question_mark`]: No longer lints when the inner value is implicitly + dereferenced [#7165](https://github.com/rust-lang/rust-clippy/pull/7165) +- [`unused_unit`]: No longer lints when multiple macro contexts are involved + [#7167](https://github.com/rust-lang/rust-clippy/pull/7167) +- [`eval_order_dependence`]: Fix false positive in async context + [#7174](https://github.com/rust-lang/rust-clippy/pull/7174) +- [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the + type [#7175](https://github.com/rust-lang/rust-clippy/pull/7175) +- [`wrong_self_convention`]: No longer lints in trait implementations of + non-`Copy` types [#7182](https://github.com/rust-lang/rust-clippy/pull/7182) +- [`suboptimal_flops`]: No longer lints on `powi(2)` + [#7201](https://github.com/rust-lang/rust-clippy/pull/7201) +- [`wrong_self_convention`]: No longer lints if there is no implicit `self` + [#7215](https://github.com/rust-lang/rust-clippy/pull/7215) +- [`option_if_let_else`]: No longer lints on `else if let` pattern + [#7216](https://github.com/rust-lang/rust-clippy/pull/7216) +- [`use_self`], [`useless_conversion`]: Fix false positives when generic + arguments are involved + [#7223](https://github.com/rust-lang/rust-clippy/pull/7223) +- [`manual_unwrap_or`]: Fix false positive with deref coercion + [#7233](https://github.com/rust-lang/rust-clippy/pull/7233) +- [`similar_names`]: No longer lints on `wparam`/`lparam` + [#7255](https://github.com/rust-lang/rust-clippy/pull/7255) +- [`redundant_closure`]: No longer lints on using the `vec![]` macro in a + closure [#7263](https://github.com/rust-lang/rust-clippy/pull/7263) + +### Suggestion Fixes/Improvements + +- [`implicit_return`] + [#6951](https://github.com/rust-lang/rust-clippy/pull/6951) + - Fix suggestion for async functions + - Improve suggestion with macros + - Suggest to change `break` to `return` when appropriate +- [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary + [#6966](https://github.com/rust-lang/rust-clippy/pull/6966) +- [`match_single_binding`]: Improve suggestion when match scrutinee has side + effects [#7095](https://github.com/rust-lang/rust-clippy/pull/7095) +- [`needless_borrow`]: Now suggests to also change usage sites as needed + [#7105](https://github.com/rust-lang/rust-clippy/pull/7105) +- [`write_with_newline`]: Improve suggestion when only `\n` is written to the + buffer [#7183](https://github.com/rust-lang/rust-clippy/pull/7183) +- [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also + when a `<_ as Trait>::_` is involved + [#7264](https://github.com/rust-lang/rust-clippy/pull/7264) +- [`not_unsafe_ptr_arg_deref`]: Improved error message + [#7294](https://github.com/rust-lang/rust-clippy/pull/7294) + +### ICE Fixes + +- Fix ICE when running Clippy on `libstd` + [#7140](https://github.com/rust-lang/rust-clippy/pull/7140) +- [`implicit_return`] + [#7242](https://github.com/rust-lang/rust-clippy/pull/7242) ## Rust 1.53 -Current beta, release 2021-06-17 +Current stable, released 2021-06-17 [6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c) @@ -194,7 +322,7 @@ Current beta, release 2021-06-17 ## Rust 1.52 -Current stable, released 2021-05-06 +Released 2021-05-06 [3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e) @@ -2295,6 +2423,7 @@ Released 2018-09-13 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped +[`append_instead_of_extend`]: https://rust-lang.github.io/rust-clippy/master/index.html#append_instead_of_extend [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions [`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants @@ -2358,6 +2487,8 @@ Released 2018-09-13 [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord [`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method +[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents +[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons @@ -2527,6 +2658,7 @@ Released 2018-09-13 [`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op [`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn [`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items +[`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames [`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc [`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items [`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc @@ -2574,6 +2706,7 @@ Released 2018-09-13 [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options +[`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d7b7c81173..4273fda4e64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,7 +115,7 @@ To work around this, you need to have a copy of the [rustc-repo][rustc_repo] ava `git clone https://github.com/rust-lang/rust/`. Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies which `IntelliJ Rust` will be able to understand. -Run `cargo dev ide_setup --repo-path ` where `` is a path to the rustc repo +Run `cargo dev setup intellij --repo-path ` where `` is a path to the rustc repo you just cloned. The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to Clippys `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses. diff --git a/Cargo.toml b/Cargo.toml index b003b15a11d..9b5d9b2adf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.54" +version = "0.1.55" authors = ["The Rust Clippy Developers"] description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" diff --git a/README.md b/README.md index bd322cc8070..e1c968273cd 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,17 @@ A collection of lints to catch common mistakes and improve your [Rust](https://g Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. -| Category | Description | Default level | -| --------------------- | ----------------------------------------------------------------------- | ------------- | -| `clippy::all` | all lints that are on by default (correctness, style, complexity, perf) | **warn/deny** | -| `clippy::correctness` | code that is outright wrong or very useless | **deny** | -| `clippy::style` | code that should be written in a more idiomatic way | **warn** | -| `clippy::complexity` | code that does something simple but in a complex way | **warn** | -| `clippy::perf` | code that can be written to run faster | **warn** | -| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | -| `clippy::nursery` | new lints that are still under development | allow | -| `clippy::cargo` | lints for the cargo manifest | allow | +| Category | Description | Default level | +| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | +| `clippy::correctness` | code that is outright wrong or useless | **deny** | +| `clippy::suspicious` | code that is most likely wrong or useless | **warn** | +| `clippy::style` | code that should be written in a more idiomatic way | **warn** | +| `clippy::complexity` | code that does something simple but in a complex way | **warn** | +| `clippy::perf` | code that can be written to run faster | **warn** | +| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | +| `clippy::nursery` | new lints that are still under development | allow | +| `clippy::cargo` | lints for the cargo manifest | allow | More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! @@ -75,11 +76,10 @@ cargo clippy #### Automatically applying Clippy suggestions -Clippy can automatically apply some lint suggestions. -Note that this is still experimental and only supported on the nightly channel: +Clippy can automatically apply some lint suggestions, just like the compiler. ```terminal -cargo clippy --fix -Z unstable-options +cargo clippy --fix ``` #### Workspaces diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index b1844e29b32..5c6c106e0e6 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" bytecount = "0.6" clap = "2.33" itertools = "0.9" -opener = "0.4" +opener = "0.5" regex = "1" shell-escape = "0.1" walkdir = "2" diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 1517cdc9419..c81eb40d52f 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -60,11 +60,7 @@ fn try_run(context: &FmtContext) -> Result { let entry = entry?; let path = entry.path(); - if path.extension() != Some("rs".as_ref()) - || entry.file_name() == "ice-3891.rs" - // Avoid rustfmt bug rust-lang/rustfmt#1873 - || cfg!(windows) && entry.file_name() == "implicit_hasher.rs" - { + if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { continue; } @@ -90,7 +86,7 @@ fn output_err(err: CliError) { }, CliError::RaSetupActive => { eprintln!( - "error: a local rustc repo is enabled as path dependency via `cargo dev ide_setup`. + "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`. Not formatting because that would format the local repo as well! Please revert the changes to Cargo.tomls first." ); diff --git a/clippy_dev/src/ide_setup.rs b/clippy_dev/src/ide_setup.rs deleted file mode 100644 index defb1133e44..00000000000 --- a/clippy_dev/src/ide_setup.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::fs; -use std::fs::File; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; - -// This module takes an absolute path to a rustc repo and alters the dependencies to point towards -// the respective rustc subcrates instead of using extern crate xyz. -// This allows rust analyzer to analyze rustc internals and show proper information inside clippy -// code. See https://github.com/rust-analyzer/rust-analyzer/issues/3517 and https://github.com/rust-lang/rust-clippy/issues/5514 for details - -/// # Panics -/// -/// Panics if `rustc_path` does not lead to a rustc repo or the files could not be read -pub fn run(rustc_path: Option<&str>) { - // we can unwrap here because the arg is required by clap - let rustc_path = PathBuf::from(rustc_path.unwrap()) - .canonicalize() - .expect("failed to get the absolute repo path"); - assert!(rustc_path.is_dir(), "path is not a directory"); - let rustc_source_basedir = rustc_path.join("compiler"); - assert!( - rustc_source_basedir.is_dir(), - "are you sure the path leads to a rustc repo?" - ); - - let clippy_root_manifest = fs::read_to_string("Cargo.toml").expect("failed to read ./Cargo.toml"); - let clippy_root_lib_rs = fs::read_to_string("src/driver.rs").expect("failed to read ./src/driver.rs"); - inject_deps_into_manifest( - &rustc_source_basedir, - "Cargo.toml", - &clippy_root_manifest, - &clippy_root_lib_rs, - ) - .expect("Failed to inject deps into ./Cargo.toml"); - - let clippy_lints_manifest = - fs::read_to_string("clippy_lints/Cargo.toml").expect("failed to read ./clippy_lints/Cargo.toml"); - let clippy_lints_lib_rs = - fs::read_to_string("clippy_lints/src/lib.rs").expect("failed to read ./clippy_lints/src/lib.rs"); - inject_deps_into_manifest( - &rustc_source_basedir, - "clippy_lints/Cargo.toml", - &clippy_lints_manifest, - &clippy_lints_lib_rs, - ) - .expect("Failed to inject deps into ./clippy_lints/Cargo.toml"); -} - -fn inject_deps_into_manifest( - rustc_source_dir: &Path, - manifest_path: &str, - cargo_toml: &str, - lib_rs: &str, -) -> std::io::Result<()> { - // do not inject deps if we have aleady done so - if cargo_toml.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") { - eprintln!( - "cargo dev ide_setup: warning: deps already found inside {}, doing nothing.", - manifest_path - ); - return Ok(()); - } - - let extern_crates = lib_rs - .lines() - // get the deps - .filter(|line| line.starts_with("extern crate")) - // we have something like "extern crate foo;", we only care about the "foo" - // ↓ ↓ - // extern crate rustc_middle; - .map(|s| &s[13..(s.len() - 1)]); - - let new_deps = extern_crates.map(|dep| { - // format the dependencies that are going to be put inside the Cargo.toml - format!( - "{dep} = {{ path = \"{source_path}/{dep}\" }}\n", - dep = dep, - source_path = rustc_source_dir.display() - ) - }); - - // format a new [dependencies]-block with the new deps we need to inject - let mut all_deps = String::from("[target.'cfg(NOT_A_PLATFORM)'.dependencies]\n"); - new_deps.for_each(|dep_line| { - all_deps.push_str(&dep_line); - }); - all_deps.push_str("\n[dependencies]\n"); - - // replace "[dependencies]" with - // [dependencies] - // dep1 = { path = ... } - // dep2 = { path = ... } - // etc - let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1); - - // println!("{}", new_manifest); - let mut file = File::create(manifest_path)?; - file.write_all(new_manifest.as_bytes())?; - - println!("Dependency paths injected: {}", manifest_path); - - Ok(()) -} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 69f42aca8b6..72bdaf8d592 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -14,9 +14,9 @@ pub mod bless; pub mod fmt; -pub mod ide_setup; pub mod new_lint; pub mod serve; +pub mod setup; pub mod stderr_length_check; pub mod update_lints; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 7040c257c83..ff324ff6ee6 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -2,8 +2,8 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use clap::{App, Arg, ArgMatches, SubCommand}; -use clippy_dev::{bless, fmt, ide_setup, new_lint, serve, stderr_length_check, update_lints}; +use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use clippy_dev::{bless, fmt, new_lint, serve, setup, stderr_length_check, update_lints}; fn main() { let matches = get_clap_config(); @@ -36,7 +36,22 @@ fn main() { ("limit_stderr_length", _) => { stderr_length_check::check(); }, - ("ide_setup", Some(matches)) => ide_setup::run(matches.value_of("rustc-repo-path")), + ("setup", Some(sub_command)) => match sub_command.subcommand() { + ("intellij", Some(matches)) => setup::intellij::setup_rustc_src( + matches + .value_of("rustc-repo-path") + .expect("this field is mandatory and therefore always valid"), + ), + ("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")), + ("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")), + _ => {}, + }, + ("remove", Some(sub_command)) => match sub_command.subcommand() { + ("git-hook", Some(_)) => setup::git_hook::remove_hook(), + ("intellij", Some(_)) => setup::intellij::remove_rustc_src(), + ("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(), + _ => {}, + }, ("serve", Some(matches)) => { let port = matches.value_of("port").unwrap().parse().unwrap(); let lint = matches.value_of("lint"); @@ -48,6 +63,7 @@ fn main() { fn get_clap_config<'a>() -> ArgMatches<'a> { App::new("Clippy developer tooling") + .setting(AppSettings::ArgRequiredElseHelp) .subcommand( SubCommand::with_name("bless") .about("bless the test output changes") @@ -123,6 +139,7 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { .possible_values(&[ "style", "correctness", + "suspicious", "complexity", "perf", "pedantic", @@ -140,16 +157,54 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { .about("Ensures that stderr files do not grow longer than a certain amount of lines."), ) .subcommand( - SubCommand::with_name("ide_setup") - .about("Alter dependencies so Intellij Rust can find rustc internals") - .arg( - Arg::with_name("rustc-repo-path") - .long("repo-path") - .short("r") - .help("The path to a rustc repo that will be used for setting the dependencies") - .takes_value(true) - .value_name("path") - .required(true), + SubCommand::with_name("setup") + .about("Support for setting up your personal development environment") + .setting(AppSettings::ArgRequiredElseHelp) + .subcommand( + SubCommand::with_name("intellij") + .about("Alter dependencies so Intellij Rust can find rustc internals") + .arg( + Arg::with_name("rustc-repo-path") + .long("repo-path") + .short("r") + .help("The path to a rustc repo that will be used for setting the dependencies") + .takes_value(true) + .value_name("path") + .required(true), + ), + ) + .subcommand( + SubCommand::with_name("git-hook") + .about("Add a pre-commit git hook that formats your code to make it look pretty") + .arg( + Arg::with_name("force-override") + .long("force-override") + .short("f") + .help("Forces the override of an existing git pre-commit hook") + .required(false), + ), + ) + .subcommand( + SubCommand::with_name("vscode-tasks") + .about("Add several tasks to vscode for formatting, validation and testing") + .arg( + Arg::with_name("force-override") + .long("force-override") + .short("f") + .help("Forces the override of existing vscode tasks") + .required(false), + ), + ), + ) + .subcommand( + SubCommand::with_name("remove") + .about("Support for undoing changes done by the setup command") + .setting(AppSettings::ArgRequiredElseHelp) + .subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook")) + .subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks")) + .subcommand( + SubCommand::with_name("intellij") + .about("Removes rustc source paths added via `cargo dev setup intellij`"), ), ) .subcommand( diff --git a/clippy_dev/src/setup/git_hook.rs b/clippy_dev/src/setup/git_hook.rs new file mode 100644 index 00000000000..3fbb77d5923 --- /dev/null +++ b/clippy_dev/src/setup/git_hook.rs @@ -0,0 +1,85 @@ +use std::fs; +use std::path::Path; + +use super::verify_inside_clippy_dir; + +/// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo. +/// I've decided against this for the sake of simplicity and to make sure that it doesn't install +/// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool +/// for formatting and should therefor only be used in a normal clone of clippy +const REPO_GIT_DIR: &str = ".git"; +const HOOK_SOURCE_FILE: &str = "util/etc/pre-commit.sh"; +const HOOK_TARGET_FILE: &str = ".git/hooks/pre-commit"; + +pub fn install_hook(force_override: bool) { + if !check_precondition(force_override) { + return; + } + + // So a little bit of a funny story. Git on unix requires the pre-commit file + // to have the `execute` permission to be set. The Rust functions for modifying + // these flags doesn't seem to work when executed with normal user permissions. + // + // However, there is a little hack that is also being used by Rust itself in their + // setup script. Git saves the `execute` flag when syncing files. This means + // that we can check in a file with execution permissions and the sync it to create + // a file with the flag set. We then copy this file here. The copy function will also + // include the `execute` permission. + match fs::copy(HOOK_SOURCE_FILE, HOOK_TARGET_FILE) { + Ok(_) => { + println!("info: the hook can be removed with `cargo dev remove git-hook`"); + println!("git hook successfully installed"); + }, + Err(err) => eprintln!( + "error: unable to copy `{}` to `{}` ({})", + HOOK_SOURCE_FILE, HOOK_TARGET_FILE, err + ), + } +} + +fn check_precondition(force_override: bool) -> bool { + if !verify_inside_clippy_dir() { + return false; + } + + // Make sure that we can find the git repository + let git_path = Path::new(REPO_GIT_DIR); + if !git_path.exists() || !git_path.is_dir() { + eprintln!("error: clippy_dev was unable to find the `.git` directory"); + return false; + } + + // Make sure that we don't override an existing hook by accident + let path = Path::new(HOOK_TARGET_FILE); + if path.exists() { + if force_override { + return delete_git_hook_file(path); + } + + eprintln!("error: there is already a pre-commit hook installed"); + println!("info: use the `--force-override` flag to override the existing hook"); + return false; + } + + true +} + +pub fn remove_hook() { + let path = Path::new(HOOK_TARGET_FILE); + if path.exists() { + if delete_git_hook_file(path) { + println!("git hook successfully removed"); + } + } else { + println!("no pre-commit hook was found"); + } +} + +fn delete_git_hook_file(path: &Path) -> bool { + if let Err(err) = fs::remove_file(path) { + eprintln!("error: unable to delete existing pre-commit git hook ({})", err); + false + } else { + true + } +} diff --git a/clippy_dev/src/setup/intellij.rs b/clippy_dev/src/setup/intellij.rs new file mode 100644 index 00000000000..bf741e6d121 --- /dev/null +++ b/clippy_dev/src/setup/intellij.rs @@ -0,0 +1,223 @@ +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +// This module takes an absolute path to a rustc repo and alters the dependencies to point towards +// the respective rustc subcrates instead of using extern crate xyz. +// This allows IntelliJ to analyze rustc internals and show proper information inside Clippy +// code. See https://github.com/rust-lang/rust-clippy/issues/5514 for details + +const RUSTC_PATH_SECTION: &str = "[target.'cfg(NOT_A_PLATFORM)'.dependencies]"; +const DEPENDENCIES_SECTION: &str = "[dependencies]"; + +const CLIPPY_PROJECTS: &[ClippyProjectInfo] = &[ + ClippyProjectInfo::new("root", "Cargo.toml", "src/driver.rs"), + ClippyProjectInfo::new("clippy_lints", "clippy_lints/Cargo.toml", "clippy_lints/src/lib.rs"), + ClippyProjectInfo::new("clippy_utils", "clippy_utils/Cargo.toml", "clippy_utils/src/lib.rs"), +]; + +/// Used to store clippy project information to later inject the dependency into. +struct ClippyProjectInfo { + /// Only used to display information to the user + name: &'static str, + cargo_file: &'static str, + lib_rs_file: &'static str, +} + +impl ClippyProjectInfo { + const fn new(name: &'static str, cargo_file: &'static str, lib_rs_file: &'static str) -> Self { + Self { + name, + cargo_file, + lib_rs_file, + } + } +} + +pub fn setup_rustc_src(rustc_path: &str) { + let rustc_source_dir = match check_and_get_rustc_dir(rustc_path) { + Ok(path) => path, + Err(_) => return, + }; + + for project in CLIPPY_PROJECTS { + if inject_deps_into_project(&rustc_source_dir, project).is_err() { + return; + } + } + + println!("info: the source paths can be removed again with `cargo dev remove intellij`"); +} + +fn check_and_get_rustc_dir(rustc_path: &str) -> Result { + let mut path = PathBuf::from(rustc_path); + + if path.is_relative() { + match path.canonicalize() { + Ok(absolute_path) => { + println!("info: the rustc path was resolved to: `{}`", absolute_path.display()); + path = absolute_path; + }, + Err(err) => { + eprintln!("error: unable to get the absolute path of rustc ({})", err); + return Err(()); + }, + }; + } + + let path = path.join("compiler"); + println!("info: looking for compiler sources at: {}", path.display()); + + if !path.exists() { + eprintln!("error: the given path does not exist"); + return Err(()); + } + + if !path.is_dir() { + eprintln!("error: the given path is not a directory"); + return Err(()); + } + + Ok(path) +} + +fn inject_deps_into_project(rustc_source_dir: &Path, project: &ClippyProjectInfo) -> Result<(), ()> { + let cargo_content = read_project_file(project.cargo_file)?; + let lib_content = read_project_file(project.lib_rs_file)?; + + if inject_deps_into_manifest(rustc_source_dir, project.cargo_file, &cargo_content, &lib_content).is_err() { + eprintln!( + "error: unable to inject dependencies into {} with the Cargo file {}", + project.name, project.cargo_file + ); + Err(()) + } else { + Ok(()) + } +} + +/// `clippy_dev` expects to be executed in the root directory of Clippy. This function +/// loads the given file or returns an error. Having it in this extra function ensures +/// that the error message looks nice. +fn read_project_file(file_path: &str) -> Result { + let path = Path::new(file_path); + if !path.exists() { + eprintln!("error: unable to find the file `{}`", file_path); + return Err(()); + } + + match fs::read_to_string(path) { + Ok(content) => Ok(content), + Err(err) => { + eprintln!("error: the file `{}` could not be read ({})", file_path, err); + Err(()) + }, + } +} + +fn inject_deps_into_manifest( + rustc_source_dir: &Path, + manifest_path: &str, + cargo_toml: &str, + lib_rs: &str, +) -> std::io::Result<()> { + // do not inject deps if we have already done so + if cargo_toml.contains(RUSTC_PATH_SECTION) { + eprintln!( + "warn: dependencies are already setup inside {}, skipping file", + manifest_path + ); + return Ok(()); + } + + let extern_crates = lib_rs + .lines() + // only take dependencies starting with `rustc_` + .filter(|line| line.starts_with("extern crate rustc_")) + // we have something like "extern crate foo;", we only care about the "foo" + // extern crate rustc_middle; + // ^^^^^^^^^^^^ + .map(|s| &s[13..(s.len() - 1)]); + + let new_deps = extern_crates.map(|dep| { + // format the dependencies that are going to be put inside the Cargo.toml + format!( + "{dep} = {{ path = \"{source_path}/{dep}\" }}\n", + dep = dep, + source_path = rustc_source_dir.display() + ) + }); + + // format a new [dependencies]-block with the new deps we need to inject + let mut all_deps = String::from("[target.'cfg(NOT_A_PLATFORM)'.dependencies]\n"); + new_deps.for_each(|dep_line| { + all_deps.push_str(&dep_line); + }); + all_deps.push_str("\n[dependencies]\n"); + + // replace "[dependencies]" with + // [dependencies] + // dep1 = { path = ... } + // dep2 = { path = ... } + // etc + let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1); + + // println!("{}", new_manifest); + let mut file = File::create(manifest_path)?; + file.write_all(new_manifest.as_bytes())?; + + println!("info: successfully setup dependencies inside {}", manifest_path); + + Ok(()) +} + +pub fn remove_rustc_src() { + for project in CLIPPY_PROJECTS { + remove_rustc_src_from_project(project); + } +} + +fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool { + let mut cargo_content = if let Ok(content) = read_project_file(project.cargo_file) { + content + } else { + return false; + }; + let section_start = if let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) { + section_start + } else { + println!( + "info: dependencies could not be found in `{}` for {}, skipping file", + project.cargo_file, project.name + ); + return true; + }; + + let end_point = if let Some(end_point) = cargo_content.find(DEPENDENCIES_SECTION) { + end_point + } else { + eprintln!( + "error: the end of the rustc dependencies section could not be found in `{}`", + project.cargo_file + ); + return false; + }; + + cargo_content.replace_range(section_start..end_point, ""); + + match File::create(project.cargo_file) { + Ok(mut file) => { + file.write_all(cargo_content.as_bytes()).unwrap(); + println!("info: successfully removed dependencies inside {}", project.cargo_file); + true + }, + Err(err) => { + eprintln!( + "error: unable to open file `{}` to remove rustc dependencies for {} ({})", + project.cargo_file, project.name, err + ); + false + }, + } +} diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs new file mode 100644 index 00000000000..a1e4dd103b8 --- /dev/null +++ b/clippy_dev/src/setup/mod.rs @@ -0,0 +1,23 @@ +pub mod git_hook; +pub mod intellij; +pub mod vscode; + +use std::path::Path; + +const CLIPPY_DEV_DIR: &str = "clippy_dev"; + +/// This function verifies that the tool is being executed in the clippy directory. +/// This is useful to ensure that setups only modify Clippys resources. The verification +/// is done by checking that `clippy_dev` is a sub directory of the current directory. +/// +/// It will print an error message and return `false` if the directory could not be +/// verified. +fn verify_inside_clippy_dir() -> bool { + let path = Path::new(CLIPPY_DEV_DIR); + if path.exists() && path.is_dir() { + true + } else { + eprintln!("error: unable to verify that the working directory is clippys directory"); + false + } +} diff --git a/clippy_dev/src/setup/vscode.rs b/clippy_dev/src/setup/vscode.rs new file mode 100644 index 00000000000..d59001b2c66 --- /dev/null +++ b/clippy_dev/src/setup/vscode.rs @@ -0,0 +1,104 @@ +use std::fs; +use std::path::Path; + +use super::verify_inside_clippy_dir; + +const VSCODE_DIR: &str = ".vscode"; +const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json"; +const TASK_TARGET_FILE: &str = ".vscode/tasks.json"; + +pub fn install_tasks(force_override: bool) { + if !check_install_precondition(force_override) { + return; + } + + match fs::copy(TASK_SOURCE_FILE, TASK_TARGET_FILE) { + Ok(_) => { + println!("info: the task file can be removed with `cargo dev remove vscode-tasks`"); + println!("vscode tasks successfully installed"); + }, + Err(err) => eprintln!( + "error: unable to copy `{}` to `{}` ({})", + TASK_SOURCE_FILE, TASK_TARGET_FILE, err + ), + } +} + +fn check_install_precondition(force_override: bool) -> bool { + if !verify_inside_clippy_dir() { + return false; + } + + let vs_dir_path = Path::new(VSCODE_DIR); + if vs_dir_path.exists() { + // verify the target will be valid + if !vs_dir_path.is_dir() { + eprintln!("error: the `.vscode` path exists but seems to be a file"); + return false; + } + + // make sure that we don't override any existing tasks by accident + let path = Path::new(TASK_TARGET_FILE); + if path.exists() { + if force_override { + return delete_vs_task_file(path); + } + + eprintln!( + "error: there is already a `task.json` file inside the `{}` directory", + VSCODE_DIR + ); + println!("info: use the `--force-override` flag to override the existing `task.json` file"); + return false; + } + } else { + match fs::create_dir(vs_dir_path) { + Ok(_) => { + println!("info: created `{}` directory for clippy", VSCODE_DIR); + }, + Err(err) => { + eprintln!( + "error: the task target directory `{}` could not be created ({})", + VSCODE_DIR, err + ); + }, + } + } + + true +} + +pub fn remove_tasks() { + let path = Path::new(TASK_TARGET_FILE); + if path.exists() { + if delete_vs_task_file(path) { + try_delete_vs_directory_if_empty(); + println!("vscode tasks successfully removed"); + } + } else { + println!("no vscode tasks were found"); + } +} + +fn delete_vs_task_file(path: &Path) -> bool { + if let Err(err) = fs::remove_file(path) { + eprintln!("error: unable to delete the existing `tasks.json` file ({})", err); + return false; + } + + true +} + +/// This function will try to delete the `.vscode` directory if it's empty. +/// It may fail silently. +fn try_delete_vs_directory_if_empty() { + let path = Path::new(VSCODE_DIR); + if path.read_dir().map_or(false, |mut iter| iter.next().is_none()) { + // The directory is empty. We just try to delete it but allow a silence + // fail as an empty `.vscode` directory is still valid + let _silence_result = fs::remove_dir(path); + } else { + // The directory is not empty or could not be read. Either way don't take + // any further actions + } +} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index edf6c5f57a4..db467c26f15 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -92,7 +92,10 @@ pub fn run(update_mode: UpdateMode) { || { // clippy::all should only include the following lint groups: let all_group_lints = usable_lints.iter().filter(|l| { - l.group == "correctness" || l.group == "style" || l.group == "complexity" || l.group == "perf" + matches!( + &*l.group, + "correctness" | "suspicious" | "style" | "complexity" | "perf" + ) }); gen_lint_group_list(all_group_lints) diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 48f2972ec58..42cf7547f51 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clippy_lints" # begin automatic update -version = "0.1.54" +version = "0.1.55" # end automatic update authors = ["The Rust Clippy Developers"] description = "A bunch of helpful lints to avoid common pitfalls in Rust" @@ -23,6 +23,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", optional = true } toml = "0.5.3" unicode-normalization = "0.1" +unicode-script = { version = "0.5.3", default-features = false } semver = "0.11" rustc-semver = "1.1.0" # NOTE: cargo requires serde feat in its url dep diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs index bc6eec0051a..a8c527fe2e3 100644 --- a/clippy_lints/src/assign_ops.rs +++ b/clippy_lints/src/assign_ops.rs @@ -55,7 +55,7 @@ /// a += a + b; /// ``` pub MISREFACTORED_ASSIGN_OP, - complexity, + suspicious, "having a variable on both sides of an assign op" } diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 932cd58bf62..f272ed010a1 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -173,7 +173,7 @@ /// #![deny(clippy::as_conversions)] /// ``` pub BLANKET_CLIPPY_RESTRICTION_LINTS, - style, + suspicious, "enabling the complete restriction group" } diff --git a/clippy_lints/src/blacklisted_name.rs b/clippy_lints/src/blacklisted_name.rs index b26ef33e056..8eb94f3c28e 100644 --- a/clippy_lints/src/blacklisted_name.rs +++ b/clippy_lints/src/blacklisted_name.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::{diagnostics::span_lint, is_test_module_or_function}; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::{Pat, PatKind}; +use rustc_hir::{Item, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -25,18 +25,37 @@ #[derive(Clone, Debug)] pub struct BlacklistedName { blacklist: FxHashSet, + test_modules_deep: u32, } impl BlacklistedName { pub fn new(blacklist: FxHashSet) -> Self { - Self { blacklist } + Self { + blacklist, + test_modules_deep: 0, + } + } + + fn in_test_module(&self) -> bool { + self.test_modules_deep != 0 } } impl_lint_pass!(BlacklistedName => [BLACKLISTED_NAME]); impl<'tcx> LateLintPass<'tcx> for BlacklistedName { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if is_test_module_or_function(cx.tcx, item) { + self.test_modules_deep = self.test_modules_deep.saturating_add(1); + } + } + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + // Check whether we are under the `test` attribute. + if self.in_test_module() { + return; + } + if let PatKind::Binding(.., ident, _) = pat.kind { if self.blacklist.contains(&ident.name.to_string()) { span_lint( @@ -48,4 +67,10 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { } } } + + fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if is_test_module_or_function(cx.tcx, item) { + self.test_modules_deep = self.test_modules_deep.saturating_sub(1); + } + } } diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index 877ae002d36..4f7ffdcdfb4 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -1,15 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::match_type; -use clippy_utils::{contains_name, get_pat_name, paths, single_segment_path}; +use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, UnOp}; +use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, UintTy}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; -use rustc_span::Symbol; declare_clippy_lint! { /// **What it does:** Checks for naive byte counts @@ -38,42 +38,43 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { - if let ExprKind::MethodCall(count, _, count_args, _) = expr.kind; + if let ExprKind::MethodCall(count, _, [count_recv], _) = expr.kind; if count.ident.name == sym!(count); - if count_args.len() == 1; - if let ExprKind::MethodCall(filter, _, filter_args, _) = count_args[0].kind; + if let ExprKind::MethodCall(filter, _, [filter_recv, filter_arg], _) = count_recv.kind; if filter.ident.name == sym!(filter); - if filter_args.len() == 2; - if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind; + if let ExprKind::Closure(_, _, body_id, _, _) = filter_arg.kind; let body = cx.tcx.hir().body(body_id); - if body.params.len() == 1; - if let Some(argname) = get_pat_name(body.params[0].pat); + if let [param] = body.params; + if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; if let ExprKind::Binary(ref op, l, r) = body.value.kind; if op.node == BinOpKind::Eq; if match_type(cx, - cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(), + cx.typeck_results().expr_ty(filter_recv).peel_refs(), &paths::SLICE_ITER); + let operand_is_arg = |expr| { + let expr = peel_ref_operators(cx, remove_blocks(expr)); + path_to_local_id(expr, arg_id) + }; + let needle = if operand_is_arg(l) { + r + } else if operand_is_arg(r) { + l + } else { + return; + }; + if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); + if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle); then { - let needle = match get_path_name(l) { - Some(name) if check_arg(name, argname, r) => r, - _ => match get_path_name(r) { - Some(name) if check_arg(name, argname, l) => l, - _ => { return; } - } - }; - if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() { - return; - } let haystack = if let ExprKind::MethodCall(path, _, args, _) = - filter_args[0].kind { + filter_recv.kind { let p = path.ident.name; if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { &args[0] } else { - &filter_args[0] + &filter_recv } } else { - &filter_args[0] + &filter_recv }; let mut applicability = Applicability::MaybeIncorrect; span_lint_and_sugg( @@ -91,24 +92,3 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { }; } } - -fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool { - name == arg && !contains_name(name, needle) -} - -fn get_path_name(expr: &Expr<'_>) -> Option { - match expr.kind { - ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) | ExprKind::Unary(UnOp::Deref, e) => { - get_path_name(e) - }, - ExprKind::Block(b, _) => { - if b.stmts.is_empty() { - b.expr.as_ref().and_then(|p| get_path_name(p)) - } else { - None - } - }, - ExprKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), - _ => None, - } -} diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index ab22578abd6..a6c3a5b0e83 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -1,11 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::LocalUsedVisitor; -use clippy_utils::{is_lang_ctor, path_to_local, SpanlessEq}; +use clippy_utils::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq}; use if_chain::if_chain; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind, UnOp}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TypeckResults; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{MultiSpan, Span}; @@ -73,7 +72,7 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext if arms_inner.iter().all(|arm| arm.guard.is_none()); // match expression must be a local binding // match { .. } - if let Some(binding_id) = path_to_local(strip_ref_operators(expr_in, cx.typeck_results())); + if let Some(binding_id) = path_to_local(peel_ref_operators(cx, expr_in)); // one of the branches must be "wild-like" if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(cx, arm_inner)); let (wild_inner_arm, non_wild_inner_arm) = @@ -163,16 +162,3 @@ fn pat_contains_or(pat: &Pat<'_>) -> bool { }); result } - -/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is -/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. -fn strip_ref_operators<'hir>(mut expr: &'hir Expr<'hir>, typeck_results: &TypeckResults<'_>) -> &'hir Expr<'hir> { - loop { - match expr.kind { - ExprKind::AddrOf(_, _, e) => expr = e, - ExprKind::Unary(UnOp::Deref, e) if typeck_results.expr_ty(e).is_ref() => expr = e, - _ => break, - } - } - expr -} diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 7a53d390bb4..947479db8f5 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -7,7 +7,6 @@ use rustc_hir::def::Res; use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::{Ident, Symbol}; @@ -122,7 +121,7 @@ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { if let StmtKind::Local(local) = stmt.kind; if let Some(expr) = local.init; if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); - if !in_external_macro(cx.tcx.sess, expr.span); + if !in_macro(expr.span); // only take bindings to identifiers if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind; // only when assigning `... = Default::default()` diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 759f7d4062d..a125376bffa 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -7,9 +7,10 @@ intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor}, Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind, }; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::{ hir::map::Map, + lint::in_external_macro, ty::{self, FloatTy, IntTy, PolyFnSig, Ty}, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -73,6 +74,7 @@ fn new(cx: &'a LateContext<'tcx>) -> Self { /// Check whether a passed literal has potential to cause fallback or not. fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { if_chain! { + if !in_external_macro(self.cx.sess(), lit.span); if let Some(ty_bound) = self.ty_bounds.last(); if matches!(lit.node, LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 04f3d77464f..2933fbc9341 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -149,7 +149,7 @@ macro_rules! declare_deprecated_lint { /// enables the `enum_variant_names` lint for public items. /// ``` pub PUB_ENUM_VARIANT_NAMES, - "set the `avoid_breaking_exported_api` config option to `false` to enable the `enum_variant_names` lint for public items" + "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items" } declare_deprecated_lint! { @@ -158,5 +158,5 @@ macro_rules! declare_deprecated_lint { /// **Deprecation reason:** The `avoid_breaking_exported_api` config option was added, which /// enables the `wrong_self_conversion` lint for public items. pub WRONG_PUB_SELF_CONVENTION, - "set the `avoid_breaking_exported_api` config option to `false` to enable the `wrong_self_convention` lint for public items" + "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items" } diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 729941f345a..3ac20fd9849 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -410,11 +410,8 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { } if let ExprKind::Block(block, _) = expr.kind { - match block.rules { - BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => { - self.has_unsafe = true; - }, - _ => {}, + if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules { + self.has_unsafe = true; } } diff --git a/clippy_lints/src/disallowed_method.rs b/clippy_lints/src/disallowed_method.rs index ded0a0fff54..aa1a609afed 100644 --- a/clippy_lints/src/disallowed_method.rs +++ b/clippy_lints/src/disallowed_method.rs @@ -2,7 +2,7 @@ use clippy_utils::fn_def_id; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::Expr; +use rustc_hir::{def::Res, def_id::DefId, Crate, Expr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::Symbol; @@ -13,21 +13,14 @@ /// **Why is this bad?** Some methods are undesirable in certain contexts, /// and it's beneficial to lint for them as needed. /// - /// **Known problems:** Currently, you must write each function as a - /// fully-qualified path. This lint doesn't support aliases or reexported - /// names; be aware that many types in `std` are actually reexports. - /// - /// For example, if you want to disallow `Duration::as_secs`, your clippy.toml - /// configuration would look like - /// `disallowed-methods = ["core::time::Duration::as_secs"]` and not - /// `disallowed-methods = ["std::time::Duration::as_secs"]` as you might expect. + /// **Known problems:** None. /// /// **Example:** /// /// An example clippy.toml configuration: /// ```toml /// # clippy.toml - /// disallowed-methods = ["alloc::vec::Vec::leak", "std::time::Instant::now"] + /// disallowed-methods = ["std::vec::Vec::leak", "std::time::Instant::now"] /// ``` /// /// ```rust,ignore @@ -52,6 +45,7 @@ #[derive(Clone, Debug)] pub struct DisallowedMethod { disallowed: FxHashSet>, + def_ids: FxHashSet<(DefId, Vec)>, } impl DisallowedMethod { @@ -61,6 +55,7 @@ pub fn new(disallowed: &FxHashSet) -> Self { .iter() .map(|s| s.split("::").map(|seg| Symbol::intern(seg)).collect::>()) .collect(), + def_ids: FxHashSet::default(), } } } @@ -68,10 +63,20 @@ pub fn new(disallowed: &FxHashSet) -> Self { impl_lint_pass!(DisallowedMethod => [DISALLOWED_METHOD]); impl<'tcx> LateLintPass<'tcx> for DisallowedMethod { + fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) { + for path in &self.disallowed { + let segs = path.iter().map(ToString::to_string).collect::>(); + if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &segs.iter().map(String::as_str).collect::>()) + { + self.def_ids.insert((id, path.clone())); + } + } + } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(def_id) = fn_def_id(cx, expr) { - let func_path = cx.get_def_path(def_id); - if self.disallowed.contains(&func_path) { + if self.def_ids.iter().any(|(id, _)| def_id == *id) { + let func_path = cx.get_def_path(def_id); let func_path_string = func_path .into_iter() .map(Symbol::to_ident_string) diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs new file mode 100644 index 00000000000..12c525634c5 --- /dev/null +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -0,0 +1,112 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast; +use rustc_data_structures::fx::FxHashSet; +use rustc_lint::{EarlyContext, EarlyLintPass, Level}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use unicode_script::{Script, UnicodeScript}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of unicode scripts other than those explicitly allowed + /// by the lint config. + /// + /// This lint doesn't take into account non-text scripts such as `Unknown` and `Linear_A`. + /// It also ignores the `Common` script type. + /// While configuring, be sure to use official script name [aliases] from + /// [the list of supported scripts][supported_scripts]. + /// + /// See also: [`non_ascii_idents`]. + /// + /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases + /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html + /// + /// **Why is this bad?** It may be not desired to have many different scripts for + /// identifiers in the codebase. + /// + /// Note that if you only want to allow plain English, you might want to use + /// built-in [`non_ascii_idents`] lint instead. + /// + /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Assuming that `clippy.toml` contains the following line: + /// // allowed-locales = ["Latin", "Cyrillic"] + /// let counter = 10; // OK, latin is allowed. + /// let счётчик = 10; // OK, cyrillic is allowed. + /// let zähler = 10; // OK, it's still latin. + /// let カウンタ = 10; // Will spawn the lint. + /// ``` + pub DISALLOWED_SCRIPT_IDENTS, + restriction, + "usage of non-allowed Unicode scripts" +} + +#[derive(Clone, Debug)] +pub struct DisallowedScriptIdents { + whitelist: FxHashSet