diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 086b907ae..fbebf4b74 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,8 +26,8 @@ jobs: mdbook --version - name: Run tests run: mdbook test - - name: Check for unstable features - run: (cd stable-check && cargo run -- ../src) + - name: Style checks + run: (cd style-check && cargo run -- ../src) - name: Check for broken links run: | curl -sSLo linkcheck.sh \ diff --git a/STYLE.md b/STYLE.md index b70eb8808..f265aab3b 100644 --- a/STYLE.md +++ b/STYLE.md @@ -3,6 +3,10 @@ Some conventions and content guidelines are specified in the [introduction]. This document serves as a guide for editors and reviewers. +There is a [`style-check`](style-check/) tool which is run in CI to check some +of these. To use it locally, run +`cargo run --manifest-path=style-check/Cargo.toml src`. + ## Markdown formatting * Use ATX-style heading with sentence case. diff --git a/src/attributes/codegen.md b/src/attributes/codegen.md index d23a0244b..a047120e6 100644 --- a/src/attributes/codegen.md +++ b/src/attributes/codegen.md @@ -157,9 +157,9 @@ otherwise undefined behavior results. ### Behavior -Applying the attribute to a function `f` allows code within `f` to get a hint of the [`Location`] of -the "topmost" tracked call that led to `f`'s invocation. At the point of observation, an -implementation behaves as if it walks up the stack from `f`'s frame to find the nearest frame of an +Applying the attribute to a function `f` allows code within `f` to get a hint of the [`Location`] of +the "topmost" tracked call that led to `f`'s invocation. At the point of observation, an +implementation behaves as if it walks up the stack from `f`'s frame to find the nearest frame of an *unattributed* function `outer`, and it returns the [`Location`] of the tracked call in `outer`. ```rust @@ -190,7 +190,7 @@ fn calls_f() { ``` When `f` is called by another attributed function `g` which is in turn called by `calls_g`, code in -both `f` and `g` observes `g`'s callsite within `calls_g`: +both `f` and `g` observes `g`'s callsite within `calls_g`: ```rust # #[track_caller] diff --git a/src/comments.md b/src/comments.md index 59442c67e..ff1595064 100644 --- a/src/comments.md +++ b/src/comments.md @@ -2,27 +2,27 @@ > **Lexer**\ > LINE_COMMENT :\ ->       `//` (~[`/` `!`] | `//`) ~`\n`\*\ +>       `//` (~\[`/` `!`] | `//`) ~`\n`\*\ >    | `//` > > BLOCK_COMMENT :\ ->       `/*` (~[`*` `!`] | `**` | _BlockCommentOrDoc_) +>       `/*` (~\[`*` `!`] | `**` | _BlockCommentOrDoc_) > (_BlockCommentOrDoc_ | ~`*/`)\* `*/`\ >    | `/**/`\ >    | `/***/` > > INNER_LINE_DOC :\ ->    `//!` ~[`\n` _IsolatedCR_]\* +>    `//!` ~\[`\n` _IsolatedCR_]\* > > INNER_BLOCK_DOC :\ ->    `/*!` ( _BlockCommentOrDoc_ | ~[`*/` _IsolatedCR_] )\* `*/` +>    `/*!` ( _BlockCommentOrDoc_ | ~\[`*/` _IsolatedCR_] )\* `*/` > > OUTER_LINE_DOC :\ ->    `///` (~`/` ~[`\n` _IsolatedCR_]\*)? +>    `///` (~`/` ~\[`\n` _IsolatedCR_]\*)? > > OUTER_BLOCK_DOC :\ >    `/**` (~`*` | _BlockCommentOrDoc_ ) -> (_BlockCommentOrDoc_ | ~[`*/` _IsolatedCR_])\* `*/` +> (_BlockCommentOrDoc_ | ~\[`*/` _IsolatedCR_])\* `*/` > > _BlockCommentOrDoc_ :\ >       BLOCK_COMMENT\ diff --git a/src/destructors.md b/src/destructors.md index c70c7010d..89f3dfd65 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -166,7 +166,7 @@ smallest scope that contains the expression and is for one of the following: * The second operand of a [lazy boolean expression]. > **Notes**: -> +> > Temporaries that are created in the final expression of a function > body are dropped *after* any named variables bound in the function body, as > there is no smaller enclosing temporary scope. diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index f49ae697e..217a6a19c 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -12,10 +12,10 @@ > _ClosureParam_ :\ >    [_OuterAttribute_]\* [_Pattern_] ( `:` [_Type_] )? -A _closure expression_, also know as a lambda expression or a lambda, defines a -closure and denotes it as a value, in a single expression. A closure expression -is a pipe-symbol-delimited (`|`) list of irrefutable [patterns] followed by an -expression. Type annotations may optionally be added for the type of the +A _closure expression_, also know as a lambda expression or a lambda, defines a +closure and denotes it as a value, in a single expression. A closure expression +is a pipe-symbol-delimited (`|`) list of irrefutable [patterns] followed by an +expression. Type annotations may optionally be added for the type of the parameters or for the return type. If there is a return type, the expression used for the body of the closure must be a normal [block]. A closure expression also may begin with the `move` keyword before the initial `|`. diff --git a/src/glossary.md b/src/glossary.md index 65b1978d0..fd204c29d 100644 --- a/src/glossary.md +++ b/src/glossary.md @@ -75,17 +75,17 @@ function* or a *free const*. Contrast to an [associated item]. ### Fundamental traits -A fundamental trait is one where adding an impl of it for an existing type is a breaking change. +A fundamental trait is one where adding an impl of it for an existing type is a breaking change. The `Fn` traits and `Sized` are fundamental. ### Fundamental type constructors -A fundamental type constructor is a type where implementing a [blanket implementation](#blanket-implementation) over it -is a breaking change. `&`, `&mut`, `Box`, and `Pin` are fundamental. +A fundamental type constructor is a type where implementing a [blanket implementation](#blanket-implementation) over it +is a breaking change. `&`, `&mut`, `Box`, and `Pin` are fundamental. -Any time a type `T` is considered [local](#local-type), `&T`, `&mut T`, `Box`, and `Pin` -are also considered local. Fundamental type constructors cannot [cover](#uncovered-type) other types. -Any time the term "covered type" is used, +Any time a type `T` is considered [local](#local-type), `&T`, `&mut T`, `Box`, and `Pin` +are also considered local. Fundamental type constructors cannot [cover](#uncovered-type) other types. +Any time the term "covered type" is used, the `T` in `&T`, `&mut T`, `Box`, and `Pin` is not considered covered. ### Inhabited @@ -120,7 +120,7 @@ or not independent of applied type arguments. Given `trait Foo`, A `struct`, `enum`, or `union` which was defined in the current crate. This is not affected by applied type arguments. `struct Foo` is considered local, but -`Vec` is not. `LocalType` is local. Type aliases do not +`Vec` is not. `LocalType` is local. Type aliases do not affect locality. ### Nominal types diff --git a/src/identifiers.md b/src/identifiers.md index 809ff3850..88d3f2071 100644 --- a/src/identifiers.md +++ b/src/identifiers.md @@ -2,8 +2,8 @@ > **Lexer:**\ > IDENTIFIER_OR_KEYWORD :\ ->       [`a`-`z` `A`-`Z`] [`a`-`z` `A`-`Z` `0`-`9` `_`]\*\ ->    | `_` [`a`-`z` `A`-`Z` `0`-`9` `_`]+ +>       \[`a`-`z` `A`-`Z`] \[`a`-`z` `A`-`Z` `0`-`9` `_`]\*\ +>    | `_` \[`a`-`z` `A`-`Z` `0`-`9` `_`]+ > > RAW_IDENTIFIER : `r#` IDENTIFIER_OR_KEYWORD *Except `crate`, `self`, `super`, `Self`* > diff --git a/src/items/functions.md b/src/items/functions.md index 15b8cc10b..f76991cf0 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -293,7 +293,7 @@ attributes][attributes] are allowed directly after the `{` inside its [block]. This example shows an inner attribute on a function. The function will only be available while running tests. -``` +```rust fn test_only() { #![test] } diff --git a/src/items/implementations.md b/src/items/implementations.md index c4e63b2fa..26ba45a8e 100644 --- a/src/items/implementations.md +++ b/src/items/implementations.md @@ -174,7 +174,7 @@ least one of the following is true: Only the appearance of *uncovered* type parameters is restricted. Note that for the purposes of coherence, [fundamental types] are -special. The `T` in `Box` is not considered covered, and `Box` +special. The `T` in `Box` is not considered covered, and `Box` is considered local. diff --git a/src/macro-ambiguity.md b/src/macro-ambiguity.md index 531fbef0b..52dc17d88 100644 --- a/src/macro-ambiguity.md +++ b/src/macro-ambiguity.md @@ -16,8 +16,8 @@ of this text is copied, and expanded upon in subsequent RFCs. "match"). - `repetition` : a fragment that follows a regular repeating pattern - `NT`: non-terminal, the various "meta-variables" or repetition matchers - that can appear in a matcher, specified in MBE syntax with a leading `$` - character. + that can appear in a matcher, specified in MBE syntax with a leading `$` + character. - `simple NT`: a "meta-variable" non-terminal (further discussion below). - `complex NT`: a repetition matching non-terminal, specified via repetition operators (`\*`, `+`, `?`). @@ -80,9 +80,9 @@ Greek letters "α" "β" "γ" "δ" stand for potentially empty token-tree sequen and does not stand for a token-tree sequence.) * This Greek letter convention is usually just employed when the presence of - a sequence is a technical detail; in particular, when we wish to *emphasize* - that we are operating on a sequence of token-trees, we will use the notation - "tt ..." for the sequence, not a Greek letter. + a sequence is a technical detail; in particular, when we wish to *emphasize* + that we are operating on a sequence of token-trees, we will use the notation + "tt ..." for the sequence, not a Greek letter. Note that a matcher is merely a token tree. A "simple NT", as mentioned above, is an meta-variable NT; thus it is a non-repetition. For example, `$foo:ty` is @@ -108,7 +108,7 @@ of FIRST and FOLLOW are described later. tt uu ...`) with `uu ...` nonempty, we must have FOLLOW(`... tt`) ∪ {ε} ⊇ FIRST(`uu ...`). 1. For any separated complex NT in a matcher, `M = ... $(tt ...) SEP OP ...`, - we must have `SEP` ∈ FOLLOW(`tt ...`). + we must have `SEP` ∈ FOLLOW(`tt ...`). 1. For an unseparated complex NT in a matcher, `M = ... $(tt ...) OP ...`, if OP = `\*` or `+`, we must have FOLLOW(`tt ...`) ⊇ FIRST(`tt ...`). diff --git a/src/notation.md b/src/notation.md index 2f9b42f01..cb3d8f606 100644 --- a/src/notation.md +++ b/src/notation.md @@ -15,9 +15,9 @@ The following notations are used by the *Lexer* and *Syntax* grammar snippets: | x+ | _MacroMatch_+ | 1 or more of x | | xa..b | HEX_DIGIT1..6 | a to b repetitions of x | | \| | `u8` \| `u16`, Block \| Item | Either one or another | -| [ ] | [`b` `B`] | Any of the characters listed | -| [ - ] | [`a`-`z`] | Any of the characters in the range | -| ~[ ] | ~[`b` `B`] | Any characters, except those listed | +| \[ ] | \[`b` `B`] | Any of the characters listed | +| \[ - ] | \[`a`-`z`] | Any of the characters in the range | +| ~\[ ] | ~\[`b` `B`] | Any characters, except those listed | | ~`string` | ~`\n`, ~`*/` | Any characters, except this sequence | | ( ) | (`,` _Parameter_)? | Groups items | diff --git a/src/tokens.md b/src/tokens.md index f01369e11..f329ce912 100644 --- a/src/tokens.md +++ b/src/tokens.md @@ -115,7 +115,7 @@ and numeric literal tokens are accepted only with suffixes from the list below. > **Lexer**\ > CHAR_LITERAL :\ ->    `'` ( ~[`'` `\` \\n \\r \\t] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE ) `'` +>    `'` ( ~\[`'` `\` \\n \\r \\t] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE ) `'` > > QUOTE_ESCAPE :\ >    `\'` | `\"` @@ -136,7 +136,7 @@ which must be _escaped_ by a preceding `U+005C` character (`\`). > **Lexer**\ > STRING_LITERAL :\ >    `"` (\ ->       ~[`"` `\` _IsolatedCR_]\ +>       ~\[`"` `\` _IsolatedCR_]\ >       | QUOTE_ESCAPE\ >       | ASCII_ESCAPE\ >       | UNICODE_ESCAPE\ @@ -338,13 +338,13 @@ literal_. The grammar for recognizing the two kinds of literals is mixed. > HEX_LITERAL :\ >    `0x` (HEX_DIGIT|`_`)\* HEX_DIGIT (HEX_DIGIT|`_`)\* > -> BIN_DIGIT : [`0`-`1`] +> BIN_DIGIT : \[`0`-`1`] > -> OCT_DIGIT : [`0`-`7`] +> OCT_DIGIT : \[`0`-`7`] > -> DEC_DIGIT : [`0`-`9`] +> DEC_DIGIT : \[`0`-`9`] > -> HEX_DIGIT : [`0`-`9` `a`-`f` `A`-`F`] +> HEX_DIGIT : \[`0`-`9` `a`-`f` `A`-`F`] > > INTEGER_SUFFIX :\ >       `u8` | `u16` | `u32` | `u64` | `u128` | `usize`\ diff --git a/src/type-coercions.md b/src/type-coercions.md index d94dd82bf..2b48a0645 100644 --- a/src/type-coercions.md +++ b/src/type-coercions.md @@ -1,7 +1,7 @@ # Type coercions **Type coercions** are implicit operations that change the type of a value. -They happen automatically at specific locations and are highly restricted in +They happen automatically at specific locations and are highly restricted in what types actually coerce. Any conversions allowed by coercion can also be explicitly performed by the @@ -55,7 +55,7 @@ sites are: Foo { x: &mut 42 }; } ``` - + * Function results—either the final line of a block if it is not semicolon-terminated or any expression in a `return` statement diff --git a/src/type-layout.md b/src/type-layout.md index c450195b4..96adc6518 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -177,7 +177,7 @@ for interfacing with the C programming language. This representation can be applied to structs, unions, and enums. The exception is [zero-variant enums] for which the `C` representation is an error. -#### \#[repr(C)] Structs +#### `#[repr(C)]` Structs The alignment of the struct is the alignment of the most-aligned field in it. @@ -244,7 +244,7 @@ the sake of clarity. To perform memory layout computations in actual code, use > they are fields that have the `[[no_unique_address]]` attribute, in which > case they do not increase the overall size of the struct. -#### \#[repr(C)] Unions +#### `#[repr(C)]` Unions A union declared with `#[repr(C)]` will have the same size and alignment as an equivalent C union declaration in the C language for the target platform. @@ -274,7 +274,7 @@ assert_eq!(std::mem::size_of::(), 8); // Size of 6 from b, assert_eq!(std::mem::align_of::(), 4); // From a ``` -#### \#[repr(C)] Field-less Enums +#### `#[repr(C)]` Field-less Enums For [field-less enums], the `C` representation has the size and alignment of the default `enum` size and alignment for the target platform's C ABI. @@ -295,7 +295,7 @@ using a field-less enum in FFI to model a C `enum` is often wrong. -#### \#[repr(C)] Enums With Fields +#### `#[repr(C)]` Enums With Fields The representation of a `repr(C)` enum with fields is a `repr(C)` struct with two fields, also called a "tagged union" in C: @@ -369,7 +369,7 @@ the primitive integer types. That is: `u8`, `u16`, `u32`, `u64`, `u128`, Primitive representations can only be applied to enumerations and have different behavior whether the enum has fields or no fields. It is an error -for [zero-variant enumerations] to have a primitive representation. Combining +for [zero-variant enums] to have a primitive representation. Combining two primitive representations together is an error. #### Primitive Representation of Field-less Enums @@ -433,7 +433,7 @@ struct MyVariantD(MyEnumDiscriminant); > Note: `union`s with non-`Copy` fields are unstable, see [55149]. -#### Combining primitive representations of enums with fields and \#[repr(C)] +#### Combining primitive representations of enums with fields and `#[repr(C)]` For enums with fields, it is also possible to combine `repr(C)` and a primitive representation (e.g., `repr(C, u8)`). This modifies the [`repr(C)`] by diff --git a/src/types/function-pointer.md b/src/types/function-pointer.md index 912ee932a..4a7a1ae47 100644 --- a/src/types/function-pointer.md +++ b/src/types/function-pointer.md @@ -60,4 +60,4 @@ restrictions as [regular function parameters]. [extern function]: ../items/functions.md#extern-function-qualifier [function items]: function-item.md [unsafe function]: ../unsafe-functions.md -[regular function parameters]: ../items/functions.md#attributes-on-function-parameters \ No newline at end of file +[regular function parameters]: ../items/functions.md#attributes-on-function-parameters diff --git a/stable-check/Cargo.lock b/stable-check/Cargo.lock deleted file mode 100644 index 982040de4..000000000 --- a/stable-check/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "stable-check" -version = "0.1.0" - diff --git a/stable-check/src/main.rs b/stable-check/src/main.rs deleted file mode 100644 index fc56ff7bb..000000000 --- a/stable-check/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::error::Error; -use std::env; -use std::fs; -use std::fs::File; -use std::io::prelude::*; -use std::path::Path; - -fn main() { - let arg = env::args().nth(1).unwrap_or_else(|| { - println!("Please pass a src directory as the first argument"); - std::process::exit(1); - }); - - match check_directory(&Path::new(&arg)) { - Ok(()) => println!("passed!"), - Err(e) => { - println!("Error: {}", e); - std::process::exit(1); - } - } -} - -fn check_directory(dir: &Path) -> Result<(), Box> { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - return check_directory(&path); - } - - let mut file = File::open(&path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - - if contents.contains("#![feature") { - return Err(From::from(format!("Feature flag found in {:?}", path))); - } - } - - Ok(()) -} diff --git a/stable-check/.gitignore b/style-check/.gitignore similarity index 100% rename from stable-check/.gitignore rename to style-check/.gitignore diff --git a/style-check/Cargo.lock b/style-check/Cargo.lock new file mode 100644 index 000000000..1b6229001 --- /dev/null +++ b/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/stable-check/Cargo.toml b/style-check/Cargo.toml similarity index 61% rename from stable-check/Cargo.toml rename to style-check/Cargo.toml index 1e3869b89..ec56dbaf0 100644 --- a/stable-check/Cargo.toml +++ b/style-check/Cargo.toml @@ -1,5 +1,8 @@ [package] -name = "stable-check" +name = "style-check" version = "0.1.0" authors = ["steveklabnik "] edition = "2018" + +[dependencies] +pulldown-cmark = "0.8" diff --git a/style-check/src/main.rs b/style-check/src/main.rs new file mode 100644 index 000000000..2589cd620 --- /dev/null +++ b/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> { + 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> { + 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(()) +}