1use dev_prefix::*;
9use types::*;
10use cmd::types::*;
11use utils;
12
13pub fn get_subcommand<'a, 'b>() -> App<'a, 'b> {
14 SubCommand::with_name("check")
15 .about("Check for any errors in the project")
16 .settings(&SUBCMD_SETTINGS)
17}
18
19fn paint_it<W: Write>(w: &mut W, msg: &str, cmd: &Cmd) {
21 if cmd.color {
22 write!(w, "{}", Red.paint(msg)).unwrap();
23 } else {
24 write!(w, "{}", msg).unwrap();
25 }
26}
27fn paint_it_bold<W: Write>(w: &mut W, msg: &str, cmd: &Cmd) {
28 if cmd.color {
29 write!(w, "{}", Red.bold().paint(msg)).unwrap();
30 } else {
31 write!(w, "{}", msg).unwrap();
32 }
33}
34
35fn display_invalid_partof<W: Write>(w: &mut W, cwd: &Path, project: &Project, cmd: &Cmd) -> u64 {
36 let mut error: u64 = 0;
37
38 let mut invalid_partof = Names::new();
40
41 let mut displayed_header = false;
43 for (name, artifact) in &project.artifacts {
44 invalid_partof.clear();
45 for p in &artifact.partof {
46 if !project.artifacts.contains_key(p) {
47 invalid_partof.insert(p.clone());
48 }
49 }
50 if !invalid_partof.is_empty() {
51 error += 1;
52 let mut msg = String::new();
53 if !displayed_header {
54 displayed_header = true;
55 paint_it_bold(w, "\nFound partof names that do not exist:\n", cmd);
56 }
57 write!(
58 msg,
59 "- {} [{}]: {:?}\n",
60 name,
61 utils::relative_path(&artifact.def, cwd).display(),
62 invalid_partof
63 ).unwrap();
64 paint_it(w, &msg, cmd);
65 }
66 }
67
68 error
69}
70
71fn display_unresolvable<W: Write>(w: &mut W, project: &Project, cmd: &Cmd) -> u64 {
72 let mut error: u64 = 0;
73
74 let unresolved: Vec<(NameRc, &Artifact)> = Vec::from_iter(
76 project
77 .artifacts
78 .iter()
79 .filter(|a| a.1.completed < 0. || a.1.tested < 0.)
80 .map(|n| (n.0.clone(), n.1)),
81 );
82 let unknown_names: HashSet<NameRc> = HashSet::from_iter(unresolved.iter().map(|u| u.0.clone()));
83
84 if !unresolved.is_empty() {
85 error += 1;
86 let mut unresolved_partof: HashMap<NameRc, HashSet<NameRc>> = HashMap::new();
87 for &(ref name, artifact) in &unresolved {
88 let partof: HashSet<_> = artifact
89 .partof
90 .iter()
91 .filter(|n| {
92 !project.artifacts.contains_key(n.as_ref()) ||
93 unknown_names.contains(n.as_ref())
94 })
95 .cloned()
96 .collect();
97 unresolved_partof.insert(name.clone(), partof);
98 }
99
100 let mut resolved_names: HashSet<NameRc> = HashSet::new();
102 let mut remove_names = Vec::new();
103 let mut just_resolved = Vec::new();
104 loop {
105 just_resolved.clear();
106 let mut did_something = false;
107 for (name, partof) in &mut unresolved_partof {
108 remove_names.clear();
110 for p in partof.iter() {
111 if resolved_names.contains(p) {
112 remove_names.push(p.clone());
113 }
114 }
115 if !remove_names.is_empty() {
116 did_something = true;
117 }
118 for p in &remove_names {
119 partof.remove(p);
120 }
121
122 if partof.is_empty() {
124 resolved_names.insert(name.clone());
125 just_resolved.push(name.clone());
126 }
127 }
128 if !just_resolved.is_empty() {
129 did_something = true;
130 }
131 for r in &just_resolved {
132 unresolved_partof.remove(r);
133 }
134 if !did_something {
135 break;
136 }
137 }
138 paint_it_bold(
139 w,
140 "\nArtifacts partof contains at least one recursive reference:\n",
141 cmd,
142 );
143 let mut unresolved_partof: Vec<_> = unresolved_partof
144 .drain()
145 .map(|mut v| (v.0, v.1.drain().collect::<Vec<_>>()))
146 .collect();
147 unresolved_partof.sort_by(|a, b| a.0.cmp(&b.0));
148 for (name, partof) in unresolved_partof.drain(0..) {
149 let mut msg = String::new();
150 write!(msg, "- {:<30}: {:?}\n", name.to_string(), partof).unwrap();
151 write!(w, "{}", msg).unwrap();
152 }
153 }
154
155 error
156}
157
158fn display_invalid_locs<W: Write>(w: &mut W, cwd: &Path, project: &Project, cmd: &Cmd) -> u64 {
159 let mut error: u64 = 0;
160
161 if !project.dne_locs.is_empty() {
163 error += 1;
164 let mut invalid_locs: HashMap<PathBuf, Vec<(Name, Loc)>> = HashMap::new();
166 for (name, loc) in &project.dne_locs {
167 if !invalid_locs.contains_key(&loc.path) {
168 invalid_locs.insert(loc.path.clone(), Vec::new());
169 }
170 invalid_locs
171 .get_mut(&loc.path)
172 .unwrap()
173 .push((name.clone(), loc.clone()));
174 }
175 let header = "\nFound implementation links in the code that do not exist:\n";
176 paint_it_bold(w, header, cmd);
177 let mut invalid_locs: Vec<(PathBuf, Vec<(Name, Loc)>)> =
178 Vec::from_iter(invalid_locs.drain());
179 invalid_locs.sort_by(|a, b| a.0.cmp(&b.0));
180 for (path, mut locs) in invalid_locs.drain(0..) {
181 let mut pathstr = String::new();
183 write!(
184 pathstr,
185 "- {}:\n",
186 utils::relative_path(&path, cwd).display()
187 ).unwrap();
188 paint_it(w, &pathstr, cmd);
189 locs.sort_by(|a, b| a.1.line.cmp(&b.1.line));
190 for (name, loc) in locs {
191 let mut loc_str = String::new();
192 write!(loc_str, " - [{}]", loc.line).unwrap();
193 paint_it(w, &loc_str, cmd);
194 write!(w, " {}\n", name).unwrap();
195 }
196 }
197 }
198
199 error
200}
201
202
203fn display_hanging_artifacts<W: Write>(w: &mut W, cwd: &Path, project: &Project, cmd: &Cmd) -> u64 {
204 let mut error: u64 = 0;
205
206 let mut hanging: Vec<(NameRc, &Path)> = Vec::new();
208 for (name, artifact) in &project.artifacts {
209 if name.ty != Type::REQ && artifact.partof.is_empty() {
213 hanging.push((name.clone(), &artifact.def));
214 }
215 }
216 hanging.sort_by(|a, b| a.1.cmp(b.1));
217 if !hanging.is_empty() {
218 error += 1;
219 let msg = "\nHanging artifacts found (top-level but not partof a higher type):\n";
220 paint_it_bold(w, msg, cmd);
221 for (h, p) in hanging {
222 let mut msg = String::new();
223 write!(
224 msg,
225 "- {:<30}: {}\n",
226 utils::relative_path(p, cwd).display(),
227 h
228 ).unwrap();
229 write!(w, "{}", msg).unwrap();
230 }
231 }
232
233 error
234}
235
236fn display_hanging_references<W: Write>(
237 w: &mut W,
238 cwd: &Path,
239 project: &Project,
240 cmd: &Cmd,
241) -> u64 {
242 let mut error: u64 = 0;
243
244 let regexp = Regex::new(&format!(r"(?i)\[\[({})\]\]", NAME_VALID_STR)).expect("tested regexp");
245 let mut hanging: HashMap<NameRc, Vec<Name>> = HashMap::new();
246
247 for (name, artifact) in &project.artifacts {
248 let mut found = vec![];
249 for cap in regexp.captures_iter(&artifact.text) {
250 let raw = cap.get(1).expect("regexp definition");
251 let tname = Name::from_str(raw.as_str()).expect("regexp validatd");
252 if !project.artifacts.contains_key(&tname) {
253 error += 1;
254 found.push(tname);
255 }
256 }
257 if !found.is_empty() {
258 hanging.insert(name.clone(), found);
259 }
260 }
261
262 if !hanging.is_empty() {
263 paint_it_bold(
264 w,
265 "\nArtifacts text contains invalid [[ART-name]] references:\n",
266 cmd,
267 );
268 let mut hanging: Vec<_> = hanging.drain().collect();
269 hanging.sort();
270 for &(ref name, ref found) in &hanging {
271 let artifact = project.artifacts.get(name).expect("inserted from");
272 paint_it(
273 w,
274 &format!(
275 "- {} ({}):\n",
276 name,
277 utils::relative_path(&artifact.def, cwd).display()
278 ),
279 cmd,
280 );
281 for f in found {
282 write!(w, " - {}", f).unwrap();
283 }
284 }
285 }
286 error
287}
288
289pub struct Cmd {
290 pub color: bool,
291}
292
293
294pub fn display_check<W: Write>(w: &mut W, cwd: &Path, project: &Project, cmd: &Cmd) -> u64 {
295 let mut error: u64 = 0;
296 error += display_invalid_partof(w, cwd, project, cmd);
297 error += display_unresolvable(w, project, cmd);
298 error += display_invalid_locs(w, cwd, project, cmd);
299 error += display_hanging_artifacts(w, cwd, project, cmd);
300 error += display_hanging_references(w, cwd, project, cmd);
301 error
302}
303
304pub fn run_cmd<W: Write>(w: &mut W, cwd: &Path, project: &Project, cmd: &Cmd) -> Result<u8> {
306 let error = display_check(w, cwd, project, cmd);
307 if error == 0 {
308 let mut msg = String::new();
309 write!(msg, "art check: no errors found in {}\n", cwd.display()).unwrap();
310 if cmd.color {
311 write!(w, "{}", Green.paint(msg)).unwrap();
312 } else {
313 write!(w, "{}", msg).unwrap();
314 }
315 } else {
316 write!(w, "\n").unwrap();
317 }
318 if error != 0 {
319 Err(
320 ErrorKind::CmdError("errors found during ls, see logs".to_string()).into(),
321 )
322 } else {
323 Ok(0)
324 }
325}