|
5 | 5 | use std::env;
|
6 | 6 | use std::fs;
|
7 | 7 | use std::io::ErrorKind;
|
8 |
| -use std::path::{Path, PathBuf}; |
| 8 | +use std::path::PathBuf; |
9 | 9 | use std::process::{Command, ExitCode, Stdio};
|
10 | 10 |
|
| 11 | +/// A possible error returned by any of the linters. |
| 12 | +/// |
| 13 | +/// The error string should explain the failure type and list all violations. |
11 | 14 | type LintError = String;
|
12 | 15 | type LintResult = Result<(), LintError>;
|
13 | 16 | type LintFn = fn() -> LintResult;
|
@@ -45,6 +48,11 @@ fn get_linter_list() -> Vec<&'static Linter> {
|
45 | 48 | name: "std_filesystem",
|
46 | 49 | lint_fn: lint_std_filesystem
|
47 | 50 | },
|
| 51 | + &Linter { |
| 52 | + description: "Check that release note snippets are in the right folder", |
| 53 | + name: "doc_release_note_snippets", |
| 54 | + lint_fn: lint_doc_release_note_snippets |
| 55 | + }, |
48 | 56 | &Linter {
|
49 | 57 | description: "Check that subtrees are pure subtrees",
|
50 | 58 | name: "subtree",
|
@@ -125,20 +133,27 @@ fn parse_lint_args(args: &[String]) -> Vec<&'static Linter> {
|
125 | 133 | }
|
126 | 134 |
|
127 | 135 | /// Return the git command
|
| 136 | +/// |
| 137 | +/// Lint functions should use this command, so that only files tracked by git are considered and |
| 138 | +/// temporary and untracked files are ignored. For example, instead of 'grep', 'git grep' should be |
| 139 | +/// used. |
128 | 140 | fn git() -> Command {
|
129 | 141 | let mut git = Command::new("git");
|
130 | 142 | git.arg("--no-pager");
|
131 | 143 | git
|
132 | 144 | }
|
133 | 145 |
|
134 |
| -/// Return stdout |
| 146 | +/// Return stdout on success and a LintError on failure, when invalid UTF8 was detected or the |
| 147 | +/// command did not succeed. |
135 | 148 | fn check_output(cmd: &mut std::process::Command) -> Result<String, LintError> {
|
136 | 149 | let out = cmd.output().expect("command error");
|
137 | 150 | if !out.status.success() {
|
138 | 151 | return Err(String::from_utf8_lossy(&out.stderr).to_string());
|
139 | 152 | }
|
140 | 153 | Ok(String::from_utf8(out.stdout)
|
141 |
| - .map_err(|e| format!("{e}"))? |
| 154 | + .map_err(|e| { |
| 155 | + format!("All path names, source code, messages, and output must be valid UTF8!\n{e}") |
| 156 | + })? |
142 | 157 | .trim()
|
143 | 158 | .to_string())
|
144 | 159 | }
|
@@ -276,6 +291,30 @@ fs:: namespace, which has unsafe filesystem functions marked as deleted.
|
276 | 291 | }
|
277 | 292 | }
|
278 | 293 |
|
| 294 | +fn lint_doc_release_note_snippets() -> LintResult { |
| 295 | + let non_release_notes = check_output(git().args([ |
| 296 | + "ls-files", |
| 297 | + "--", |
| 298 | + "doc/release-notes/", |
| 299 | + ":(exclude)doc/release-notes/*.*.md", // Assume that at least one dot implies a proper release note |
| 300 | + ]))?; |
| 301 | + if non_release_notes.is_empty() { |
| 302 | + Ok(()) |
| 303 | + } else { |
| 304 | + Err(format!( |
| 305 | + r#" |
| 306 | +{} |
| 307 | +^^^ |
| 308 | +Release note snippets and other docs must be put into the doc/ folder directly. |
| 309 | +
|
| 310 | +The doc/release-notes/ folder is for archived release notes of previous releases only. Snippets are |
| 311 | +expected to follow the naming "/doc/release-notes-<PR number>.md". |
| 312 | + "#, |
| 313 | + non_release_notes |
| 314 | + )) |
| 315 | + } |
| 316 | +} |
| 317 | + |
279 | 318 | /// Return the pathspecs for whitespace related excludes
|
280 | 319 | fn get_pathspecs_exclude_whitespace() -> Vec<String> {
|
281 | 320 | let mut list = get_pathspecs_exclude_subtrees();
|
|
0 commit comments