diff options
Diffstat (limited to 'src/doc/reference/style-check')
-rw-r--r-- | src/doc/reference/style-check/Cargo.lock | 62 | ||||
-rw-r--r-- | src/doc/reference/style-check/Cargo.toml | 8 | ||||
-rw-r--r-- | src/doc/reference/style-check/src/main.rs | 131 |
3 files changed, 201 insertions, 0 deletions
diff --git a/src/doc/reference/style-check/Cargo.lock b/src/doc/reference/style-check/Cargo.lock new file mode 100644 index 000000000..1b6229001 --- /dev/null +++ b/src/doc/reference/style-check/Cargo.lock @@ -0,0 +1,62 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + +[[package]] +name = "style-check" +version = "0.1.0" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" diff --git a/src/doc/reference/style-check/Cargo.toml b/src/doc/reference/style-check/Cargo.toml new file mode 100644 index 000000000..d592f65d0 --- /dev/null +++ b/src/doc/reference/style-check/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "style-check" +version = "0.1.0" +authors = ["steveklabnik <steve@steveklabnik.com>"] +edition = "2021" + +[dependencies] +pulldown-cmark = "0.8" diff --git a/src/doc/reference/style-check/src/main.rs b/src/doc/reference/style-check/src/main.rs new file mode 100644 index 000000000..2589cd620 --- /dev/null +++ b/src/doc/reference/style-check/src/main.rs @@ -0,0 +1,131 @@ +use std::env; +use std::error::Error; +use std::fs; +use std::path::Path; + +macro_rules! style_error { + ($bad:expr, $path:expr, $($arg:tt)*) => { + *$bad = true; + eprint!("error in {}: ", $path.display()); + eprintln!("{}", format_args!($($arg)*)); + }; +} + +fn main() { + let arg = env::args().nth(1).unwrap_or_else(|| { + eprintln!("Please pass a src directory as the first argument"); + std::process::exit(1); + }); + + let mut bad = false; + if let Err(e) = check_directory(&Path::new(&arg), &mut bad) { + eprintln!("error: {}", e); + std::process::exit(1); + } + if bad { + eprintln!("some style checks failed"); + std::process::exit(1); + } + eprintln!("passed!"); +} + +fn check_directory(dir: &Path, bad: &mut bool) -> Result<(), Box<dyn Error>> { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + check_directory(&path, bad)?; + continue; + } + + if !matches!( + path.extension().and_then(|p| p.to_str()), + Some("md") | Some("html") + ) { + // This may be extended in the future if other file types are needed. + style_error!(bad, path, "expected only md or html in src"); + } + + let contents = fs::read_to_string(&path)?; + if contents.contains("#![feature") { + style_error!(bad, path, "#![feature] attributes are not allowed"); + } + if contents.contains('\r') { + style_error!( + bad, + path, + "CR characters not allowed, must use LF line endings" + ); + } + if contents.contains('\t') { + style_error!(bad, path, "tab characters not allowed, use spaces"); + } + if !contents.ends_with('\n') { + style_error!(bad, path, "file must end with a newline"); + } + for line in contents.lines() { + if line.ends_with(' ') { + style_error!(bad, path, "lines must not end with spaces"); + } + } + cmark_check(&path, bad, &contents)?; + } + Ok(()) +} + +fn cmark_check(path: &Path, bad: &mut bool, contents: &str) -> Result<(), Box<dyn Error>> { + use pulldown_cmark::{BrokenLink, CodeBlockKind, Event, Options, Parser, Tag}; + + macro_rules! cmark_error { + ($bad:expr, $path:expr, $range:expr, $($arg:tt)*) => { + *$bad = true; + let lineno = contents[..$range.start].chars().filter(|&ch| ch == '\n').count() + 1; + eprint!("error in {} (line {}): ", $path.display(), lineno); + eprintln!("{}", format_args!($($arg)*)); + } + } + + let options = Options::all(); + // Can't use `bad` because it would get captured in closure. + let mut link_err = false; + let mut cb = |link: BrokenLink<'_>| { + cmark_error!( + &mut link_err, + path, + link.span, + "broken {:?} link (reference `{}`)", + link.link_type, + link.reference + ); + None + }; + let parser = Parser::new_with_broken_link_callback(contents, options, Some(&mut cb)); + + for (event, range) in parser.into_offset_iter() { + match event { + Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) => { + cmark_error!( + bad, + path, + range, + "indented code blocks should use triple backtick-style \ + with a language identifier" + ); + } + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(languages))) => { + if languages.is_empty() { + cmark_error!( + bad, + path, + range, + "code block should include an explicit language", + ); + } + } + _ => {} + } + } + *bad |= link_err; + Ok(()) +} |