Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent text before the first heading #117

Merged
merged 4 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
42 changes: 42 additions & 0 deletions docs/markdown-heading-first/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>markdown-heading-first</title>
<link rel="stylesheet" href="../main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<article>
<h1><code>markdown-heading-first</code></h1>
<p>
No content appears between preamble and first heading.
</p>

<section>
<h2>Examples</h2>

<pre>
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...
|</pre>
</section>
<section>
<h2>Explanation</h2>

<p>
<code>markdown-heading-first</code> ensures that no content
appears before the first heading.
</p>

<p>
It is improper form to put text/markdown outside of a section.
Such text cannot be referred to in a URL (eg. <code>#Section-Title</code>),
nor does it appear in the table of contents.
</p>
</section>
</article>
</body>
</html>
4 changes: 4 additions & 0 deletions eipw-lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ pub fn default_lints_enum() -> impl Iterator<Item = (&'static str, DefaultLint<&
(
"markdown-headings-space",
MarkdownHeadingsSpace(markdown::HeadingsSpace{}),
),
(
"markdown-heading-first",
MarkdownHeadingFirst(markdown::HeadingFirst),
)
]
.into_iter()
Expand Down
4 changes: 4 additions & 0 deletions eipw-lint/src/lints/known_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub enum DefaultLint<S> {
sections: markdown::SectionRequired<S>,
},
MarkdownHeadingsSpace(markdown::HeadingsSpace),
MarkdownHeadingFirst(markdown::HeadingFirst),
}

impl<S> DefaultLint<S>
Expand Down Expand Up @@ -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),
}
}
}
Expand Down Expand Up @@ -154,6 +156,7 @@ where
Self::MarkdownSectionOrder { sections } => sections,
Self::MarkdownSectionRequired { sections } => sections,
Self::MarkdownHeadingsSpace(l) => l,
Self::MarkdownHeadingFirst(l) => l,
}
}
}
Expand Down Expand Up @@ -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()),
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions eipw-lint/src/lints/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
49 changes: 49 additions & 0 deletions eipw-lint/src/lints/markdown/heading_first.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
69 changes: 69 additions & 0 deletions eipw-lint/tests/lint_markdown_heading_first.rs
Original file line number Diff line number Diff line change
@@ -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::<Text<String>>::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::<Text<String>>::default()
.clear_lints()
.deny("markdown-heading-first", HeadingFirst {})
.check_slice(None, src)
.run()
.await
.unwrap()
.into_inner();

assert_eq!(reports, "");
}
Loading