/ maint / add_warning
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)