diff --git a/README.md b/README.md index 24823932..0e89eda6 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,8 @@ error[preamble-order]: preamble header `description` must come after `title` | `markdown-refs` | ERCs are referenced using ERC-X, while other proposals use EIP-X. | | `markdown-rel-links` | All URLs in the page are relative. | | `markdown-req-section` | Required sections are present in the body of the proposal. | -| `markdown-headings-space` | Headers have a space after the leading '#' characters | +| `markdown-heading-first` | No content appears between preamble and first heading. | +| `markdown-headings-space` | Headers have a space after the leading '#' characters. | | `preamble-author` | The author header is correctly formatted, and there is at least one GitHub user listed. | | `preamble-date-created` | The `created` header is a date. | | `preamble-date-last-call-deadline` | The `last-call-deadline` header is a date. | diff --git a/docs/markdown-heading-first/index.html b/docs/markdown-heading-first/index.html new file mode 100644 index 00000000..d0928567 --- /dev/null +++ b/docs/markdown-heading-first/index.html @@ -0,0 +1,42 @@ + + + + + markdown-heading-first + + + + +
+

markdown-heading-first

+

+ No content appears between preamble and first heading. +

+ +
+

Examples

+ +
+error[markdown-heading-first]: Nothing is permitted between the preamble and the first heading
+  --> input.md
+   |
+12 | This proposal describes the introduction in clients of a controlled gas limit increase strategy to determine the gas limit of a spec...
+   |
+
+
+

Explanation

+ +

+ markdown-heading-first ensures that no content + appears before the first heading. +

+ +

+ It is improper form to put text/markdown outside of a section. + Such text cannot be referred to in a URL (eg. #Section-Title), + nor does it appear in the table of contents. +

+
+
+ + diff --git a/eipw-lint/src/lib.rs b/eipw-lint/src/lib.rs index 7e9a2784..8f445fab 100644 --- a/eipw-lint/src/lib.rs +++ b/eipw-lint/src/lib.rs @@ -471,6 +471,10 @@ pub fn default_lints_enum() -> impl Iterator { sections: markdown::SectionRequired, }, MarkdownHeadingsSpace(markdown::HeadingsSpace), + MarkdownHeadingFirst(markdown::HeadingFirst), } impl DefaultLint @@ -114,6 +115,7 @@ where Self::MarkdownSectionOrder { sections } => Box::new(sections), Self::MarkdownSectionRequired { sections } => Box::new(sections), Self::MarkdownHeadingsSpace(l) => Box::new(l), + Self::MarkdownHeadingFirst(l) => Box::new(l), } } } @@ -154,6 +156,7 @@ where Self::MarkdownSectionOrder { sections } => sections, Self::MarkdownSectionRequired { sections } => sections, Self::MarkdownHeadingsSpace(l) => l, + Self::MarkdownHeadingFirst(l) => l, } } } @@ -290,6 +293,7 @@ where sections: markdown::SectionRequired(sections.0.iter().map(AsRef::as_ref).collect()), }, Self::MarkdownHeadingsSpace(l) => DefaultLint::MarkdownHeadingsSpace(l.clone()), + Self::MarkdownHeadingFirst(l) => DefaultLint::MarkdownHeadingFirst(l.clone()), } } } diff --git a/eipw-lint/src/lints/markdown.rs b/eipw-lint/src/lints/markdown.rs index 264329e1..41d07591 100644 --- a/eipw-lint/src/lints/markdown.rs +++ b/eipw-lint/src/lints/markdown.rs @@ -4,6 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +pub mod heading_first; pub mod headings_space; pub mod html_comments; pub mod json_schema; @@ -16,6 +17,7 @@ pub mod relative_links; pub mod section_order; pub mod section_required; +pub use self::heading_first::HeadingFirst; pub use self::headings_space::HeadingsSpace; pub use self::html_comments::HtmlComments; pub use self::json_schema::JsonSchema; diff --git a/eipw-lint/src/lints/markdown/heading_first.rs b/eipw-lint/src/lints/markdown/heading_first.rs new file mode 100644 index 00000000..52adcc94 --- /dev/null +++ b/eipw-lint/src/lints/markdown/heading_first.rs @@ -0,0 +1,49 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use comrak::nodes::{Ast, NodeValue}; + +use crate::lints::{Error, Lint}; +use crate::SnippetExt; + +use eipw_snippets::Snippet; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HeadingFirst; + +impl Lint for HeadingFirst { + fn lint<'a>(&self, slug: &'a str, ctx: &crate::lints::Context<'a, '_>) -> Result<(), Error> { + let second = match ctx.body().descendants().nth(1) { + Some(el) => el.data.borrow().to_owned(), + None => return Ok(()), + }; + + let ast = match second { + Ast { + value: NodeValue::Heading(_), + .. + } => return Ok(()), + other => other, + }; + + let source = ctx.line(ast.sourcepos.start.line); + ctx.report( + ctx.annotation_level() + .title("Nothing is permitted between the preamble and the first heading") + .id(slug) + .snippet( + Snippet::source(source) + .origin_opt(ctx.origin()) + .line_start(ast.sourcepos.start.line) + .fold(false), + ), + )?; + + Ok(()) + } +} diff --git a/eipw-lint/tests/lint_markdown_heading_first.rs b/eipw-lint/tests/lint_markdown_heading_first.rs new file mode 100644 index 00000000..b6d422f3 --- /dev/null +++ b/eipw-lint/tests/lint_markdown_heading_first.rs @@ -0,0 +1,69 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use eipw_lint::{lints::markdown::HeadingFirst, reporters::Text, Linter}; + +#[tokio::test] +async fn invalid_eip() { + let src = r#"--- +eip: 1234 +--- + +This is some text that appears before the first heading. Authors sometimes try +to write an introduction or preface to their proposal here. We don't want to allow +this. + +## Abstract + +After the "Abstract" heading is the first place we want to allow text."#; + + let reports = Linter::>::default() + .clear_lints() + .deny("markdown-heading-first", HeadingFirst {}) + .check_slice(None, src) + .run() + .await + .unwrap() + .into_inner(); + + assert_eq!( + reports, + r#"error[markdown-heading-first]: Nothing is permitted between the preamble and the first heading + | +5 | This is some text that appears before the first heading. Authors sometimes try + | +"# + ); +} + +#[tokio::test] +async fn valid_eip() { + let src = r#"--- +eip: 100 +title: Change difficulty adjustment to target mean block time including uncles +author: Vitalik Buterin (@vbuterin) +type: Standards Track +category: Core +status: Final +created: 2016-04-28 +--- + +### Specification + +Currently, the formula to compute the difficulty of a block includes the following logic: +"#; + + let reports = Linter::>::default() + .clear_lints() + .deny("markdown-heading-first", HeadingFirst {}) + .check_slice(None, src) + .run() + .await + .unwrap() + .into_inner(); + + assert_eq!(reports, ""); +}