123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- #!/usr/bin/env python3
- # Copyright Materialize, Inc. and contributors. All rights reserved.
- #
- # Use of this software is governed by the Business Source License
- # included in the LICENSE file at the root of this repository.
- #
- # As of the Change Date specified in that file, in accordance with
- # the Business Source License, use of this software will be governed
- # by the Apache License, Version 2.0.
- """Regenerate lint annotations on all root Cargo workspace files and check that
- all crates inherit their lints from the workspace."""
- import argparse
- import json
- import os
- import subprocess
- import tempfile
- from pathlib import Path
- import toml
- ALLOW_RUST_LINTS = [
- # Allows us to allow/deny new lints and support older versions of rust/clippy.
- "unknown_lints",
- "non_local_definitions",
- ]
- ALLOW_RUST_DOC_LINTS = []
- ALLOW_CLIPPY_LINTS = [
- # The style and complexity lints frustrated too many engineers and caused
- # more bikeshedding than they saved. These lint categories are largely a
- # matter of opinion. A few of the worthwhile lints in these categories are
- # reenabled in `DENY_LINTS` below.
- ("style", -1),
- ("complexity", -1),
- # clippy::large_enum_variant complains when enum variants have divergent
- # sizes, as the size of an enum is determined by the size of its largest
- # element. Obeying this lint is nearly always a premature optimization,
- # and the suggested solution—boxing the large variant—might actually result
- # in slower code due to the allocation.
- ("large_enum_variant", 0),
- # A specialization of `large_enum_variant`; similar arguments apply.
- ("result_large_err", 0),
- # clippy::mutable_key_type disallows using internally mutable types as keys
- # in `HashMap`, because their order could change. This is a good lint in
- # principle, but its current implementation is too strict -- it disallows
- # anything containing an `Arc` or `Rc`, for example.
- ("mutable_key_type", 0),
- # Unstable sort is not strictly better than sort, notably on partially
- # sorted inputs.
- ("stable_sort_primitive", 0),
- # This lint has false positives where the pattern cannot be avoided without
- # cloning the key used in the map.
- ("map_entry", 0),
- # It is unclear if the performance gain from using `Box::default` instead of
- # `Box::new` is meaningful; and the lint can result in inconsistencies
- # when some types implement `Default` and others do not.
- # TODO(guswynn): benchmark the performance gain.
- ("box_default", 0),
- # This suggestion misses the point of `.drain(..).collect()` entirely:
- # to keep the capacity of the original collection the same.
- ("drain_collect", 0),
- ]
- WARN_CLIPPY_LINTS = [
- # Comparison of a bool with `true` or `false` is indeed clearer as `x` or
- # `!x`.
- "bool_comparison",
- # Rewrite `x.clone()` to `Arc::clone(&x)`.
- # This clarifies a significant amount of code by making it visually clear
- # when a clone is cheap.
- "clone_on_ref_ptr",
- # These can catch real bugs, because something that is expected (a cast, a
- # conversion, a statement) is not happening.
- "no_effect",
- "unnecessary_unwrap",
- # Prevent code using the `dbg!` macro from being merged to the main branch.
- #
- # To mark a debugging print as intentional (e.g., in a test), use
- # `println!("{:?}", obj)` instead.
- "dbg_macro",
- # Prevent code containing the `todo!` macro from being merged to the main
- # branch.
- #
- # To mark something as intentionally unimplemented, use the `unimplemented!`
- # macro instead.
- "todo",
- # Wildcard dependencies are, by definition, incorrect. It is impossible
- # to be compatible with all future breaking changes in a crate.
- #
- # TODO(parkmycar): Re-enable this lint when it's supported in Bazel.
- # "wildcard_dependencies",
- # Zero-prefixed literals may be incorrectly interpreted as octal literals.
- "zero_prefixed_literal",
- # Purely redundant tokens.
- "borrowed_box",
- "deref_addrof",
- "double_must_use",
- "double_parens",
- "extra_unused_lifetimes",
- "needless_borrow",
- "needless_question_mark",
- "needless_return",
- "redundant_pattern",
- "redundant_slicing",
- "redundant_static_lifetimes",
- "single_component_path_imports",
- "unnecessary_cast",
- "useless_asref",
- "useless_conversion",
- # Very likely to be confusing.
- "builtin_type_shadow",
- "duplicate_underscore_argument",
- # Purely redundant tokens; very likely to be confusing.
- "double_negations",
- # Purely redundant tokens; code is misleading.
- "unnecessary_mut_passed",
- # Purely redundant tokens; probably a mistake.
- "wildcard_in_or_patterns",
- # Transmuting between T and T* seems 99% likely to be buggy code.
- "crosspointer_transmute",
- # Confusing and likely buggy.
- "excessive_precision",
- "panicking_overflow_checks",
- # The `as` operator silently truncates data in many situations. It is very
- # difficult to assess whether a given usage of `as` is dangerous or not. So
- # ban it outright, to force usage of safer patterns, like `From` and
- # `TryFrom`.
- #
- # When absolutely essential (e.g., casting from a float to an integer), you
- # can attach `#[allow(clippy::as_conversion)]` to a single statement.
- "as_conversions",
- # Confusing.
- "match_overlapping_arm",
- # Confusing; possibly a mistake.
- "zero_divided_by_zero",
- # Probably a mistake.
- "must_use_unit",
- "suspicious_assignment_formatting",
- "suspicious_else_formatting",
- "suspicious_unary_op_formatting",
- # Legitimate performance impact.
- "mut_mutex_lock",
- "print_literal",
- "same_item_push",
- "useless_format",
- "write_literal",
- # Extra closures slow down compiles due to unnecessary code generation
- # that LLVM needs to optimize.
- "redundant_closure",
- "redundant_closure_call",
- "unnecessary_lazy_evaluations",
- # Provably either redundant or wrong.
- "partialeq_ne_impl",
- # This one is a debatable style nit, but it's so ingrained at this point
- # that we might as well keep it.
- "redundant_field_names",
- # Needless unsafe.
- "transmutes_expressible_as_ptr_casts",
- # Needless async.
- "unused_async",
- # Disallow the methods, macros, and types listed in clippy.toml;
- # see that file for rationale.
- "disallowed_methods",
- "disallowed_macros",
- "disallowed_types",
- # Implementing `From` gives you `Into` for free, but the reverse is not
- # true.
- "from_over_into",
- # We consistently don't use `mod.rs` files.
- "mod_module_files",
- "needless_pass_by_ref_mut",
- # Helps prevent bugs caused by wrong usage of `const` instead of `static`
- # to define global mutable values.
- "borrow_interior_mutable_const",
- "or_fun_call",
- ]
- MESSAGE_LINT_MISSING = (
- '{} does not contain a "lints" section. Please add: \n[lints]\nworkspace = true'
- )
- MESSAGE_LINT_INHERIT = "The lint section in {} does not inherit from the workspace, "
- EXCLUDE_CRATES = ["workspace-hack"]
- CHECK_CFGS = "bazel, stamped, coverage, nightly_doc_features, release, tokio_unstable"
- def main() -> None:
- parser = argparse.ArgumentParser()
- parser.add_argument(
- "--extra-dirs",
- action="append",
- default=[],
- )
- args = parser.parse_args()
- lint_config = [
- "\n" "# BEGIN LINT CONFIG\n",
- "# DO NOT EDIT. Automatically generated by bin/gen-lints.\n",
- "[workspace.lints.rust]\n",
- *(f'{lint} = "allow"\n' for lint in ALLOW_RUST_LINTS),
- f"unexpected_cfgs = {{ level = \"warn\", check-cfg = ['cfg({CHECK_CFGS})'] }}\n",
- "\n",
- "[workspace.lints.rustdoc]\n",
- *(f'{lint} = "allow"\n' for lint in ALLOW_RUST_DOC_LINTS),
- "\n",
- "[workspace.lints.clippy]\n",
- *(
- f'{lint} = {{ level = "allow", priority = {priority} }}\n'
- for (lint, priority) in ALLOW_CLIPPY_LINTS
- ),
- *(f'{lint} = "warn"\n' for lint in WARN_CLIPPY_LINTS),
- "# END LINT CONFIG\n",
- ]
- for workspace_root in [".", *args.extra_dirs]:
- workspace_cargo_toml = Path(f"{workspace_root}/Cargo.toml")
- # Make sure the workspace Cargo.toml files have the lint config.
- contents = workspace_cargo_toml.read_text().splitlines(keepends=True)
- try:
- # Overwrite existing lint configuration block.
- start = contents.index(lint_config[1]) - 2
- end = contents.index(lint_config[-1])
- new_contents = contents[:start] + lint_config + contents[end + 1 :]
- except ValueError:
- # No existing lint configuration block. Add a new one to the end
- # of the file.
- new_contents = contents + lint_config
- # Only write file if the content changed.
- if "".join(new_contents) != "".join(contents):
- tmp_file_path = None
- try:
- # Overwrite the file atomically so that there is never a half-written file.
- with tempfile.NamedTemporaryFile(
- "w", delete=False, dir=workspace_root, encoding="utf-8"
- ) as tmp_file:
- tmp_file.write("".join(new_contents))
- tmp_file_path = tmp_file.name
- os.replace(tmp_file_path, workspace_cargo_toml)
- except:
- if tmp_file_path:
- try:
- os.remove(tmp_file_path)
- except:
- pass
- raise
- # Make sure all of the crates in the workspace inherit their lints.
- metadata = json.loads(
- subprocess.check_output(
- [
- "cargo",
- "metadata",
- "--no-deps",
- "--format-version=1",
- f"--manifest-path={workspace_root}/Cargo.toml",
- ]
- )
- )
- cargo_toml_paths = (
- Path(package["manifest_path"])
- for package in metadata["packages"]
- if package["name"] not in EXCLUDE_CRATES
- )
- for cargo_file in cargo_toml_paths:
- cargo_toml = {}
- with cargo_file.open() as rcf:
- cargo_toml = toml.load(rcf)
- # Assert the Cargo.toml contains a "lints" section.
- assert "lints" in cargo_toml, MESSAGE_LINT_MISSING.format(cargo_file)
- # Assert the lints section contains a "workspace" key.
- assert cargo_toml["lints"].get("workspace"), MESSAGE_LINT_INHERIT.format(
- cargo_file
- )
- if __name__ == "__main__":
- main()
|