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

Add new lint for space in Headers #87

Merged
merged 8 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ 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 |
| `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
4 changes: 4 additions & 0 deletions eipw-lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ pub fn default_lints_enum() -> impl Iterator<Item = (&'static str, DefaultLint<&
),
}),
),
(
"markdown-headings-space",
MarkdownHeadingsSpace(markdown::HeadingsSpace{}),
)
]
.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 @@ -68,6 +68,7 @@ pub enum DefaultLint<S> {
MarkdownSectionRequired {
sections: markdown::SectionRequired<S>,
},
MarkdownHeadingsSpace(markdown::HeadingsSpace),
}

impl<S> DefaultLint<S>
Expand Down Expand Up @@ -110,6 +111,7 @@ where
Self::MarkdownRelativeLinks(l) => Box::new(l),
Self::MarkdownSectionOrder { sections } => Box::new(sections),
Self::MarkdownSectionRequired { sections } => Box::new(sections),
Self::MarkdownHeadingsSpace(l) => Box::new(l),
}
}
}
Expand Down Expand Up @@ -148,6 +150,7 @@ where
Self::MarkdownRelativeLinks(l) => l,
Self::MarkdownSectionOrder { sections } => sections,
Self::MarkdownSectionRequired { sections } => sections,
Self::MarkdownHeadingsSpace(l) => l,
}
}
}
Expand Down Expand Up @@ -278,6 +281,7 @@ where
Self::MarkdownSectionRequired { sections } => DefaultLint::MarkdownSectionRequired {
sections: markdown::SectionRequired(sections.0.iter().map(AsRef::as_ref).collect()),
},
Self::MarkdownHeadingsSpace(l) => DefaultLint::MarkdownHeadingsSpace(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 headings_space;
pub mod html_comments;
pub mod json_schema;
pub mod link_first;
Expand All @@ -14,6 +15,7 @@ pub mod relative_links;
pub mod section_order;
pub mod section_required;

pub use self::headings_space::HeadingsSpace;
pub use self::html_comments::HtmlComments;
pub use self::json_schema::JsonSchema;
pub use self::link_first::LinkFirst;
Expand Down
82 changes: 82 additions & 0 deletions eipw-lint/src/lints/markdown/headings_space.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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 annotate_snippets::snippet::{Annotation, Slice, Snippet};

use annotate_snippets::snippet::SourceAnnotation;
use comrak::nodes::Ast;
use comrak::nodes::NodeValue;
use regex::Regex;

use crate::lints::{Context, Error, Lint};

use serde::{Deserialize, Serialize};

use std::fmt::Debug;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct HeadingsSpace;

impl Lint for HeadingsSpace {
fn lint<'a>(&self, slug: &'a str, ctx: &Context<'a, '_>) -> Result<(), Error> {
// Match for text nodes starting with leading '#' chars (upto 6)
// Markdown does not recognise these nodes as valid Headings without the space
let heading_pattern = Regex::new("^#{1,6}").unwrap();
let invalid_headings: Vec<_> = ctx
.body()
.descendants()
.filter_map(|node| match &*node.data.borrow() {
// Collect all matching Text nodes
Ast {
value: NodeValue::Text(text),
..
} => {
if let Some(matched_text) = heading_pattern.find(text) {
let heading_level = matched_text.end();
Some((
text.clone(),
node.data.borrow().sourcepos.start.line,
heading_level,
))
} else {
None
}
}
_ => None,
})
.collect();

let slices: Vec<_> = invalid_headings
.iter()
.map(|(text, line_start, heading_level)| Slice {
line_start: *line_start,
origin: ctx.origin(),
source: text,
fold: false,
annotations: vec![SourceAnnotation {
annotation_type: ctx.annotation_type(),
label: "space required here",
range: (*heading_level - 1, *heading_level),
}],
})
.collect();

if !slices.is_empty() {
ctx.report(Snippet {
title: Some(Annotation {
id: Some(slug),
annotation_type: ctx.annotation_type(),
label: Some("Space missing in header"),
}),
footer: vec![],
slices,
opt: Default::default(),
})?;
}

Ok(())
}
}
71 changes: 71 additions & 0 deletions eipw-lint/tests/lint_markdown_headings_space.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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::HeadingsSpace;
use eipw_lint::reporters::Text;
use eipw_lint::Linter;

#[tokio::test]
async fn normal_headings() {
let src = r#"---
header: value1
---

##Banana
####Mango
"#;

let reports = Linter::<Text<String>>::default()
.clear_lints()
.deny("markdown-headings-space", HeadingsSpace {})
.check_slice(None, src)
.run()
.await
.unwrap()
.into_inner();

assert_eq!(
reports,
r#"error[markdown-headings-space]: Space missing in header
|
5 | ##Banana
| ^ space required here
|
6 | ####Mango
| ^ space required here
|
"#
);
}

#[tokio::test]
async fn abnormal_heading() {
let src = r#"---
header: value1
---

##B#an#ana
"#;

let reports = Linter::<Text<String>>::default()
.clear_lints()
.deny("markdown-headings-space", HeadingsSpace {})
.check_slice(None, src)
.run()
.await
.unwrap()
.into_inner();

assert_eq!(
reports,
r#"error[markdown-headings-space]: Space missing in header
|
5 | ##B#an#ana
| ^ space required here
|
"#
);
}
0xRampey marked this conversation as resolved.
Show resolved Hide resolved