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(())
+}