| // Take a look at the license at the top of the repository in the LICENSE file. |
| |
| use super::utils::{show_error, TestResult}; |
| use std::ffi::OsStr; |
| use std::path::Path; |
| |
| fn to_correct_name(s: &str) -> String { |
| let mut out = String::with_capacity(s.len()); |
| |
| for c in s.chars() { |
| if c.is_uppercase() { |
| if !out.is_empty() { |
| out.push('_'); |
| } |
| out.push_str(c.to_lowercase().to_string().as_str()); |
| } else { |
| out.push(c); |
| } |
| } |
| out |
| } |
| |
| fn check_md_doc_path(p: &Path, md_line: &str, ty_line: &str) -> bool { |
| let parts = md_line.split('/').collect::<Vec<_>>(); |
| if let Some(md_name) = parts.last().and_then(|n| n.split(".md").next()) { |
| if let Some(name) = ty_line.split_whitespace().filter(|s| !s.is_empty()).nth(2) { |
| if let Some(name) = name |
| .split('<') |
| .next() |
| .and_then(|n| n.split('{').next()) |
| .and_then(|n| n.split('(').next()) |
| .and_then(|n| n.split(';').next()) |
| { |
| let correct = to_correct_name(name); |
| if correct.as_str() == md_name { |
| return true; |
| } |
| show_error( |
| p, |
| &format!( |
| "Invalid markdown file name `{md_name}`, should have been `{correct}`", |
| ), |
| ); |
| return false; |
| } |
| } |
| show_error(p, &format!("Cannot extract type name from `{ty_line}`")); |
| } else { |
| show_error(p, &format!("Cannot extract md name from `{md_line}`")); |
| } |
| false |
| } |
| |
| fn check_doc_comments_before(p: &Path, lines: &[&str], start: usize) -> bool { |
| let mut found_docs = false; |
| |
| for pos in (0..start).rev() { |
| let trimmed = lines[pos].trim(); |
| if trimmed.starts_with("///") { |
| if !lines[start].trim().starts_with("pub enum ThreadStatus {") { |
| show_error( |
| p, |
| &format!( |
| "Types should use common documentation by using `#[doc = include_str!(` \ |
| and by putting the markdown file in the `md_doc` folder instead of `{}`", |
| &lines[pos], |
| ), |
| ); |
| return false; |
| } |
| return true; |
| } else if trimmed.starts_with("#[doc = include_str!(") { |
| found_docs = true; |
| if !check_md_doc_path(p, trimmed, lines[start]) { |
| return false; |
| } |
| } else if !trimmed.starts_with("#[") && !trimmed.starts_with("//") { |
| break; |
| } |
| } |
| if !found_docs { |
| show_error( |
| p, |
| &format!( |
| "Missing documentation for public item: `{}` (if it's not supposed to be a public \ |
| item, use `pub(crate)` instead)", |
| lines[start], |
| ), |
| ); |
| return false; |
| } |
| true |
| } |
| |
| pub fn check_docs(content: &str, p: &Path) -> TestResult { |
| let mut res = TestResult { |
| nb_tests: 0, |
| nb_errors: 0, |
| }; |
| |
| // No need to check if we are in the `src` folder or if we are in a `ffi.rs` file. |
| if p.parent().unwrap().file_name().unwrap() == OsStr::new("src") |
| || p.file_name().unwrap() == OsStr::new("ffi.rs") |
| { |
| return res; |
| } |
| let lines = content.lines().collect::<Vec<_>>(); |
| |
| for pos in 1..lines.len() { |
| let line = lines[pos]; |
| let trimmed = line.trim(); |
| if trimmed.starts_with("//!") { |
| show_error(p, "There shouln't be inner doc comments (`//!`)"); |
| res.nb_tests += 1; |
| res.nb_errors += 1; |
| continue; |
| } else if !line.starts_with("pub fn ") |
| && !trimmed.starts_with("pub struct ") |
| && !trimmed.starts_with("pub enum ") |
| { |
| continue; |
| } |
| res.nb_tests += 1; |
| if !check_doc_comments_before(p, &lines, pos) { |
| res.nb_errors += 1; |
| } |
| } |
| res |
| } |