add_warning
1 #!/usr/bin/env python3 2 3 import argparse 4 import fnmatch 5 import sys 6 import os 7 import re 8 import shutil 9 import subprocess 10 11 # ---------- actual list of lints to apply (or disapply) ---------- 12 13 # NOTE: We should NEVER have a `deny` for a built-in rustc lint. 14 # It's okay to deny clippy lints, but if we deny rustc lints, 15 # a future version of the compiler might refuse to build our code 16 # entirely. 17 18 WANT_LINTS = """ 19 #![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable) 20 #![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly) 21 #![warn(missing_docs)] 22 #![warn(noop_method_call)] 23 #![warn(unreachable_pub)] 24 #![warn(clippy::all)] 25 #![deny(clippy::await_holding_lock)] 26 #![deny(clippy::cargo_common_metadata)] 27 #![deny(clippy::cast_lossless)] 28 #![deny(clippy::checked_conversions)] 29 #![warn(clippy::cognitive_complexity)] 30 #![deny(clippy::debug_assert_with_mut_call)] 31 #![deny(clippy::exhaustive_enums)] 32 #![deny(clippy::exhaustive_structs)] 33 #![deny(clippy::expl_impl_clone_on_copy)] 34 #![deny(clippy::fallible_impl_from)] 35 #![deny(clippy::implicit_clone)] 36 #![deny(clippy::large_stack_arrays)] 37 #![warn(clippy::manual_ok_or)] 38 #![deny(clippy::missing_docs_in_private_items)] 39 #![warn(clippy::needless_borrow)] 40 #![warn(clippy::needless_pass_by_value)] 41 #![warn(clippy::option_option)] 42 #![deny(clippy::print_stderr)] 43 #![deny(clippy::print_stdout)] 44 #![warn(clippy::rc_buffer)] 45 #![deny(clippy::ref_option_ref)] 46 #![warn(clippy::semicolon_if_nothing_returned)] 47 #![warn(clippy::trait_duplication_in_bounds)] 48 #![deny(clippy::unchecked_duration_subtraction)] 49 #![deny(clippy::unnecessary_wraps)] 50 #![warn(clippy::unseparated_literal_suffix)] 51 #![deny(clippy::unwrap_used)] 52 #![deny(clippy::mod_module_files)] 53 #![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness 54 #![allow(clippy::uninlined_format_args)] 55 #![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945 56 #![allow(clippy::result_large_err)] // temporary workaround for arti#587 57 #![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best 58 #![allow(clippy::needless_lifetimes)] // See arti#1765 59 """ 60 61 # ---------- list of lints to apply or disapply *in tests* ---------- 62 63 TEST_LINTS = """ 64 #![allow(clippy::bool_assert_comparison)] 65 #![allow(clippy::clone_on_copy)] 66 #![allow(clippy::dbg_macro)] 67 #![allow(clippy::mixed_attributes_style)] 68 #![allow(clippy::print_stderr)] 69 #![allow(clippy::print_stdout)] 70 #![allow(clippy::single_char_pattern)] 71 #![allow(clippy::unwrap_used)] 72 #![allow(clippy::unchecked_duration_subtraction)] 73 #![allow(clippy::useless_vec)] 74 #![allow(clippy::needless_pass_by_value)] 75 """ 76 77 # ---------- list of lints to apply or disapply *in examples* ---------- 78 79 EXAMPLE_LINTS = ( 80 """ 81 #![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly) 82 """ 83 + TEST_LINTS.strip() 84 ) 85 86 87 # ---------- some notes about lints we might use - NOT USED by any code here ---------- 88 89 SOON = """ 90 """ 91 92 WISH_WE_COULD = """ 93 #![warn(unused_crate_dependencies)] 94 """ 95 96 DECIDED_NOT = """ 97 #![deny(clippy::redundant_pub_crate)] 98 #![deny(clippy::future_not_send)] 99 #![deny(clippy::redundant_closure_for_method_calls)] 100 #![deny(clippy::panic)] 101 #![deny(clippy::if_then_some_else_none)] 102 #![deny(clippy::expect_used)] 103 #![deny(clippy::option_if_let_else)] 104 #![deny(missing_debug_implementations)] 105 #![deny(clippy::pub_enum_variant_names)] 106 """ 107 108 # ---------- code for autoprocessing Rust source files ---------- 109 110 PAT = re.compile(r"^ *#!\[(?:cfg_attr\(.*)?(allow|deny|warn)") 111 112 LINT_LISTS = { 113 "lint": WANT_LINTS, 114 "test lint": TEST_LINTS, 115 "example lint": EXAMPLE_LINTS, 116 } 117 # The start delimiter is a regular comment. 118 START_LINE = re.compile(r"^(\s*)// @@ begin (.*) list") 119 120 # End delimiter is Rustdoc containing an HTML comment, because rustfmt 121 # *really really* hates comments that come after things. 122 # Finishing the automaintained block with just a blank line is too much of a hazard. 123 # It does end up in the output HTML from Rustdoc, but it is harmless there. 124 END_LINE = re.compile(r"^\s*//! <!-- @@ end (.*) list") 125 126 opts = None 127 deferred_errors = [] 128 129 130 class ImproperFile(Exception): 131 def __init__(self, lno, message): 132 self.lno = lno 133 self.message = message 134 135 136 def strip_lints_containing(s): 137 """Remove every lint containing 's'.""" 138 139 def rmv_lints(inp, s): 140 return "\n".join(line for line in inp.split("\n") if s not in line) 141 142 global LINT_LISTS 143 LINT_LISTS = dict((k, rmv_lints(v, s)) for (k, v) in LINT_LISTS.items()) 144 145 146 def filter_file(lints, inp, outp, insist): 147 in_lint_list = None 148 found_lint_list = False 149 lno = 0 150 for line in inp.readlines(): 151 lno += 1 152 153 if start_match := START_LINE.match(line): 154 if in_lint_list: 155 raise ImproperFile( 156 lno, 'found "@@ begin lint list" but inside lint list' 157 ) 158 found_lint_list = True 159 indent = start_match.group(1) 160 in_lint_list = start_match.group(2) 161 elif end_match := END_LINE.match(line): 162 if not in_lint_list: 163 raise ImproperFile( 164 lno, 'found "@@ end lint list" but not inside lint list' 165 ) 166 if in_lint_list != end_match.group(1): 167 raise ImproperFile( 168 lno, 169 "found end tag " 170 + end_match.group(1) 171 + " but expected " 172 + in_lint_list, 173 ) 174 175 try: 176 lints = LINT_LISTS[in_lint_list] 177 except KeyError: 178 raise ImproperFile(lno, "No such lint list as " + in_lint_list) 179 for lint in lints.strip().split("\n"): 180 outp.write(indent + lint + "\n") 181 in_lint_list = None 182 elif in_lint_list: 183 if not PAT.match(line): 184 raise ImproperFile(lno, "entry in lint list does not look like a lint") 185 # do not send to output 186 continue 187 outp.write(line) 188 if in_lint_list: 189 raise ImproperFile( 190 lno, 'missing "@@ lint list" delimiter, still in lint list at EOF' 191 ) 192 if insist and not found_lint_list: 193 raise ImproperFile( 194 lno, "standard lint list block seems to be missing (wrong delimiters?)" 195 ) 196 197 198 def process(lints, fn, always_insist): 199 insist = ( 200 always_insist 201 or fnmatch.fnmatch(fn, "crates/*/src/lib.rs") 202 or fnmatch.fnmatch(fn, "crates/*/src/main.rs") 203 ) 204 205 tmp_name = fn + ".tmp~" 206 outp = open(tmp_name, "w") 207 inp = open(fn, "r") 208 try: 209 filter_file(lints, inp, outp, insist) 210 except ImproperFile as e: 211 print("%s:%d: %s" % (fn, e.lno, e.message), file=sys.stderr) 212 deferred_errors.append(fn) 213 os.remove(tmp_name) # this tmp file is probably partial 214 return 215 216 inp.close() 217 outp.close() 218 219 if opts.check: 220 if subprocess.run(["diff", "-u", "--", fn, tmp_name]).returncode != 0: 221 deferred_errors.append(fn) 222 else: 223 shutil.move(tmp_name, fn) 224 225 226 def main(lints, files): 227 if not os.path.exists("./crates/tor-proto/src/lib.rs"): 228 print("Run this from the top level of an arti repo.") 229 sys.exit(1) 230 231 always_insist = True 232 if not files: 233 files = subprocess.run( 234 ["find", ".", "-name", "*.rs"], stdout=subprocess.PIPE, check=True 235 ).stdout 236 files = files.decode("utf-8").rstrip("\n").split("\n") 237 always_insist = False 238 239 if opts.ci_nightly: 240 strip_lints_containing("@@REMOVE_WHEN(ci_arti_nightly)") 241 if opts.ci_stable: 242 strip_lints_containing("@@REMOVE_WHEN(ci_arti_stable)") 243 244 for fn in files: 245 process(lints, fn, always_insist) 246 247 if len(deferred_errors) > 0: 248 print( 249 "\n" 250 + sys.argv[0] 251 + ": standard lint block mismatch in the following files:\n " 252 + ", ".join(deferred_errors), 253 file=sys.stderr, 254 ) 255 print("Run " + sys.argv[0] + " (possibly after editing it) to fix.") 256 sys.exit(1) 257 258 259 if __name__ == "__main__": 260 parser = argparse.ArgumentParser("standardise Rust lint blocks") 261 parser.add_argument("--check", action="store_true") 262 parser.add_argument("--ci-nightly", action="store_true") 263 parser.add_argument("--ci-stable", action="store_true") 264 parser.add_argument("file", nargs="*") 265 opts = parser.parse_args() 266 267 main(WANT_LINTS, opts.file)