|
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