xtask/tasks/fmt/house_rules/
copyright.rs1use anyhow::anyhow;
5use fs_err::File;
6use std::io::BufRead;
7use std::io::BufReader;
8use std::io::Read;
9use std::io::Write;
10use std::path::Path;
11
12fn commit(source: File, target: &Path) -> std::io::Result<()> {
13 source.set_permissions(target.metadata()?.permissions())?;
14 let (file, path) = source.into_parts();
15 drop(file); fs_err::rename(path, target)
17}
18
19pub fn check_copyright(path: &Path, fix: bool) -> anyhow::Result<()> {
20 const HEADER_MIT_FIRST: &str = "Copyright (c) Microsoft Corporation.";
21 const HEADER_MIT_SECOND: &str = "Licensed under the MIT License.";
22
23 let ext = path
24 .extension()
25 .and_then(|e| e.to_str())
26 .unwrap_or_default();
27
28 if !matches!(
29 ext,
30 "rs" | "c" | "proto" | "toml" | "ts" | "tsx" | "js" | "css" | "html" | "py" | "ps1"
31 ) {
32 return Ok(());
33 }
34
35 let f = BufReader::new(File::open(path)?);
36 let mut lines = f.lines();
37 let (
38 allowed_non_copyright_first_line,
39 blank_after_allowed_non_copyright_first_line,
40 first_content_line,
41 ) = {
42 let line = lines.next().unwrap_or(Ok(String::new()))?;
43 if (line.starts_with("#!") && ext != "rs")
53 || (line.starts_with("<!DOCTYPE html>") && ext == "html")
54 {
55 let allowed_non_copyright_first_line = line;
56 let after_allowed_non_copyright_first_line =
57 lines.next().unwrap_or(Ok(String::new()))?;
58 (
59 Some(allowed_non_copyright_first_line),
60 Some(after_allowed_non_copyright_first_line.is_empty()),
61 lines.next().unwrap_or(Ok(String::new()))?,
62 )
63 } else {
64 (None, None, line)
65 }
66 };
67 let second_content_line = lines.next().unwrap_or(Ok(String::new()))?;
68 let third_content_line = lines.next().unwrap_or(Ok(String::new()))?;
69
70 if first_content_line.contains("Copyright") && !first_content_line.contains("Microsoft") {
72 return Ok(());
73 }
74
75 let mut missing_banner = !first_content_line.contains(HEADER_MIT_FIRST)
76 || !second_content_line.contains(HEADER_MIT_SECOND);
77 let mut missing_blank_line = !third_content_line.is_empty();
78 let mut header_lines = 2;
79
80 let is_msft_internal = std::env::var("XTASK_FMT_COPYRIGHT_ALLOW_MISSING_MIT").is_ok();
86 if is_msft_internal && missing_banner {
87 missing_banner =
89 !(first_content_line.contains("Copyright") && first_content_line.contains("Microsoft"));
90 missing_blank_line = !second_content_line.is_empty();
91 header_lines = 1;
92 }
93
94 if fix {
95 drop(lines);
98
99 if missing_banner || missing_blank_line {
100 let path_fix = &{
101 let mut p = path.to_path_buf();
102 let ok = p.set_extension(format!("{}.fix", ext));
103 assert!(ok);
104 p
105 };
106
107 let mut f = BufReader::new(File::open(path)?);
108 let mut f_fixed = File::create(path_fix)?;
109
110 if let Some(allowed_non_copyright_first_line) = &allowed_non_copyright_first_line {
111 writeln!(f_fixed, "{allowed_non_copyright_first_line}")?;
112 f.read_line(&mut String::new())?;
113 }
114 if let Some(blank_after_allowed_non_copyright_first_line) =
115 blank_after_allowed_non_copyright_first_line
116 {
117 if !blank_after_allowed_non_copyright_first_line {
118 writeln!(f_fixed)?;
119 }
120 }
121
122 if missing_banner {
123 let prefix = match ext {
124 "rs" | "c" | "proto" | "ts" | "tsx" | "js" => "//",
125 "toml" | "py" | "ps1" | "config" => "#",
126 "css" => "/*",
127 "html" => "<!--",
128 _ => unreachable!(),
129 };
130
131 let suffix = match ext {
134 "rs" | "c" | "proto" | "ts" | "tsx" | "js" | "toml" | "py" | "ps1"
135 | "config" => "",
136 "css" => " */",
137 "html" => " -->",
138 _ => unreachable!(),
139 };
140
141 if allowed_non_copyright_first_line.is_none()
143 && first_content_line.starts_with('\u{feff}')
144 {
145 write!(f_fixed, "\u{feff}")?;
146 f.read_exact(&mut [0; 3])?;
148 }
149
150 writeln!(f_fixed, "{} {}{}", prefix, HEADER_MIT_FIRST, suffix)?;
151 if !is_msft_internal {
152 writeln!(f_fixed, "{} {}{}", prefix, HEADER_MIT_SECOND, suffix)?;
153 }
154
155 writeln!(f_fixed)?; } else if missing_blank_line {
157 for _ in 0..header_lines {
159 let mut s = String::new();
160 f.read_line(&mut s)?;
161 write!(f_fixed, "{}", s)?;
162 }
163
164 writeln!(f_fixed)?;
166 }
167
168 std::io::copy(&mut f, &mut f_fixed)?;
170
171 drop(f);
174 commit(f_fixed, path)?;
175 }
176 }
177
178 let mut missing = vec![];
181 if missing_banner {
182 missing.push("the copyright & license header");
183 }
184 if missing_blank_line {
185 missing.push("a blank line after the copyright & license header");
186 }
187 if let Some(blank_after_allowed_non_copyright_first_line) =
188 blank_after_allowed_non_copyright_first_line
189 {
190 if !blank_after_allowed_non_copyright_first_line {
191 missing.push("a blank line after the script interpreter line");
192 }
193 }
194
195 if missing.is_empty() {
196 return Ok(());
197 }
198
199 if fix {
200 log::info!(
201 "applied fixes for missing {:?} in {}",
202 missing,
203 path.display()
204 );
205 Ok(())
206 } else {
207 Err(anyhow!("missing {:?} in {}", missing, path.display()))
208 }
209}