/ test / lint / test_runner / src / lint_py.rs
lint_py.rs
 1  // Copyright (c) The Bitcoin Core developers
 2  // Distributed under the MIT software license, see the accompanying
 3  // file COPYING or https://opensource.org/license/mit/.
 4  
 5  use std::io::ErrorKind;
 6  use std::process::Command;
 7  
 8  use crate::util::{check_output, get_pathspecs_default_excludes, git, LintResult};
 9  
10  pub fn lint_py_lint() -> LintResult {
11      let bin_name = "ruff";
12      let checks = format!(
13          "--select={}",
14          [
15              "B006", // mutable-argument-default
16              "B008", // function-call-in-default-argument
17              "E101", // indentation contains mixed spaces and tabs
18              "E401", // multiple imports on one line
19              "E402", // module level import not at top of file
20              "E701", // multiple statements on one line (colon)
21              "E702", // multiple statements on one line (semicolon)
22              "E703", // statement ends with a semicolon
23              "E711", // comparison to None should be 'if cond is None:'
24              "E713", // test for membership should be "not in"
25              "E714", // test for object identity should be "is not"
26              "E721", // do not compare types, use "isinstance()"
27              "E722", // do not use bare 'except'
28              "E742", // do not define classes named "l", "O", or "I"
29              "E743", // do not define functions named "l", "O", or "I"
30              "F401", // module imported but unused
31              "F402", // import module from line N shadowed by loop variable
32              "F403", // 'from foo_module import *' used; unable to detect undefined names
33              "F404", // future import(s) name after other statements
34              "F405", // foo_function may be undefined, or defined from star imports: bar_module
35              "F406", // "from module import *" only allowed at module level
36              "F407", // an undefined __future__ feature name was imported
37              "F541", // f-string without any placeholders
38              "F601", // dictionary key name repeated with different values
39              "F602", // dictionary key variable name repeated with different values
40              "F621", // too many expressions in an assignment with star-unpacking
41              "F631", // assertion test is a tuple, which are always True
42              "F632", // use ==/!= to compare str, bytes, and int literals
43              "F811", // redefinition of unused name from line N
44              "F821", // undefined name 'Foo'
45              "F822", // undefined name name in __all__
46              "F823", // local variable name … referenced before assignment
47              "F841", // local variable 'foo' is assigned to but never used
48              "PLE",  // Pylint errors
49              "W191", // indentation contains tabs
50              "W291", // trailing whitespace
51              "W292", // no newline at end of file
52              "W293", // blank line contains whitespace
53              "W605", // invalid escape sequence "x"
54          ]
55          .join(",")
56      );
57      let files = check_output(
58          git()
59              .args(["ls-files", "--", "*.py"])
60              .args(get_pathspecs_default_excludes()),
61      )?;
62  
63      let mut cmd = Command::new(bin_name);
64      cmd.args(["check", &checks]).args(files.lines());
65  
66      match cmd.status() {
67          Ok(status) if status.success() => Ok(()),
68          Ok(_) => Err(format!("`{bin_name}` found errors!")),
69          Err(e) if e.kind() == ErrorKind::NotFound => {
70              println!("`{bin_name}` was not found in $PATH, skipping those checks.");
71              Ok(())
72          }
73          Err(e) => Err(format!("Error running `{bin_name}`: {e}")),
74      }
75  }