/ test / lint / test_runner / src / lint_text_format.rs
lint_text_format.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::fs::File;
  6  use std::io::{Read, Seek, SeekFrom};
  7  
  8  use crate::util::{check_output, commit_range, get_pathspecs_default_excludes, git, LintResult};
  9  
 10  /// Return the pathspecs for whitespace related excludes
 11  fn get_pathspecs_exclude_whitespace() -> Vec<String> {
 12      let mut list = get_pathspecs_default_excludes();
 13      list.extend(
 14          [
 15              // Permanent excludes
 16              "*.patch",
 17              "src/qt/locale",
 18              "contrib/windeploy/win-codesign.cert",
 19              "doc/README_windows.txt",
 20              // Temporary excludes, or existing violations
 21              "contrib/init/bitcoind.openrc",
 22              "contrib/macdeploy/macdeployqtplus",
 23              "src/crypto/sha256_sse4.cpp",
 24              "src/qt/res/src/*.svg",
 25              "test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv",
 26              "test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv",
 27              "contrib/qos/tc.sh",
 28              "contrib/verify-commits/gpg.sh",
 29              "src/univalue/include/univalue_escapes.h",
 30              "src/univalue/test/object.cpp",
 31              "test/lint/git-subtree-check.sh",
 32          ]
 33          .iter()
 34          .map(|s| format!(":(exclude){s}")),
 35      );
 36      list
 37  }
 38  
 39  pub fn lint_trailing_whitespace() -> LintResult {
 40      let trailing_space = git()
 41          .args(["grep", "-I", "--line-number", "\\s$", "--"])
 42          .args(get_pathspecs_exclude_whitespace())
 43          .status()
 44          .expect("command error")
 45          .success();
 46      if trailing_space {
 47          Err(r#"
 48  Trailing whitespace (including Windows line endings [CR LF]) is problematic, because git may warn
 49  about it, or editors may remove it by default, forcing developers in the future to either undo the
 50  changes manually or spend time on review.
 51  
 52  Thus, it is best to remove the trailing space now.
 53  
 54  Please add any false positives, such as subtrees, Windows-related files, patch files, or externally
 55  sourced files to the exclude list.
 56              "#
 57          .trim()
 58          .to_string())
 59      } else {
 60          Ok(())
 61      }
 62  }
 63  
 64  pub fn lint_trailing_newline() -> LintResult {
 65      let files = check_output(
 66          git()
 67              .args([
 68                  "ls-files", "--", "*.py", "*.cpp", "*.h", "*.md", "*.rs", "*.sh", "*.cmake",
 69              ])
 70              .args(get_pathspecs_default_excludes()),
 71      )?;
 72      let mut missing_newline = false;
 73      for path in files.lines() {
 74          let mut file = File::open(path).expect("must be able to open file");
 75          if file.seek(SeekFrom::End(-1)).is_err() {
 76              continue; // Allow fully empty files
 77          }
 78          let mut buffer = [0u8; 1];
 79          file.read_exact(&mut buffer)
 80              .expect("must be able to read the last byte");
 81          if buffer[0] != b'\n' {
 82              missing_newline = true;
 83              println!("{path}");
 84          }
 85      }
 86      if missing_newline {
 87          Err(r#"
 88  A trailing newline is required, because git may warn about it missing. Also, it can make diffs
 89  verbose and can break git blame after appending lines.
 90  
 91  Thus, it is best to add the trailing newline now.
 92  
 93  Please add any false positives to the exclude list.
 94              "#
 95          .trim()
 96          .to_string())
 97      } else {
 98          Ok(())
 99      }
100  }
101  
102  pub fn lint_tabs_whitespace() -> LintResult {
103      let tabs = git()
104          .args(["grep", "-I", "--line-number", "--perl-regexp", "^\\t", "--"])
105          .args(["*.cpp", "*.h", "*.md", "*.py", "*.sh"])
106          .args(get_pathspecs_exclude_whitespace())
107          .status()
108          .expect("command error")
109          .success();
110      if tabs {
111          Err(r#"
112  Use of tabs in this codebase is problematic, because existing code uses spaces and tabs will cause
113  display issues and conflict with editor settings.
114  
115  Please remove the tabs.
116  
117  Please add any false positives, such as subtrees, or externally sourced files to the exclude list.
118              "#
119          .trim()
120          .to_string())
121      } else {
122          Ok(())
123      }
124  }
125  
126  pub fn lint_commit_msg() -> LintResult {
127      let mut good = true;
128      let commit_hashes = check_output(git().args([
129          "-c",
130          "log.showSignature=false",
131          "log",
132          &commit_range(),
133          "--format=%H",
134      ]))?;
135      for hash in commit_hashes.lines() {
136          let commit_info = check_output(git().args([
137              "-c",
138              "log.showSignature=false",
139              "log",
140              "--format=%B",
141              "-n",
142              "1",
143              hash,
144          ]))?;
145          if let Some(line) = commit_info.lines().nth(1) {
146              if !line.is_empty() {
147                  println!(
148                          "The subject line of commit hash {hash} is followed by a non-empty line. Subject lines should always be followed by a blank line."
149                      );
150                  good = false;
151              }
152          }
153      }
154      if good {
155          Ok(())
156      } else {
157          Err("".to_string())
158      }
159  }