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..606f632b
--- /dev/null
+++ b/docs/markdown-heading-first/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+ markdown-heading-first
+
+
+
+
+
+
markdown-heading-first
+
+ First mention of an EIP must be a link.
+
+
+
+
Examples
+
+
error[markdown-heading-first]: the first match of the given pattern must be a link
+ --> input.md
+ |
+5 | EIP-3
+ |
+ = info: the pattern in question: `(?i)(?:eip|erc)-[0-9]+`
+
+
+
Explanation
+
+
+ markdown-heading-first ensures that the first time
+ a proposal is mentioned it is a hyperlink to that proposal.
+
+
+
+ The first reference to each proposal should be a hyperlink
+ so that it is easy for readers to navigate there.
+
+
+
+
+
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/heading_first.rs b/eipw-lint/src/lints/markdown/heading_first.rs
index 8e13d482..52adcc94 100644
--- a/eipw-lint/src/lints/markdown/heading_first.rs
+++ b/eipw-lint/src/lints/markdown/heading_first.rs
@@ -4,32 +4,46 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-use comrak::nodes::NodeValue;
+use comrak::nodes::{Ast, NodeValue};
use crate::lints::{Error, Lint};
+use crate::SnippetExt;
-#[derive(Debug)]
+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().value,
- None => {
- return ctx.report(
- ctx.annotation_level()
- .title("Cannot submit an empty proposal")
- .id(slug),
- )
- }
+ Some(el) => el.data.borrow().to_owned(),
+ None => return Ok(()),
+ };
+
+ let ast = match second {
+ Ast {
+ value: NodeValue::Heading(_),
+ ..
+ } => return Ok(()),
+ other => other,
};
- match second {
- NodeValue::Heading(_) => Ok(()),
- _ => ctx.report(
- ctx.annotation_level()
- .title("Nothing is permitted between the preamble and the first heading")
- .id(slug),
- ),
- }
+
+ 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
index 5a030829..b6d422f3 100644
--- a/eipw-lint/tests/lint_markdown_heading_first.rs
+++ b/eipw-lint/tests/lint_markdown_heading_first.rs
@@ -22,7 +22,7 @@ After the "Abstract" heading is the first place we want to allow text."#;
let reports = Linter::>::default()
.clear_lints()
- .deny("markdown-headings-only", HeadingFirst {})
+ .deny("markdown-heading-first", HeadingFirst {})
.check_slice(None, src)
.run()
.await
@@ -30,8 +30,12 @@ After the "Abstract" heading is the first place we want to allow text."#;
.into_inner();
assert_eq!(
- reports.trim(),
- "error[markdown-headings-only]: Nothing is permitted between the preamble and the first heading"
+ 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
+ |
+"#
);
}
@@ -50,36 +54,11 @@ created: 2016-04-28
### Specification
Currently, the formula to compute the difficulty of a block includes the following logic:
-
-``` python
-adj_factor = max(1 - ((timestamp - parent.timestamp) // 10), -99)
-child_diff = int(max(parent.difficulty + (parent.difficulty // BLOCK_DIFF_FACTOR) * adj_factor, min(parent.difficulty, MIN_DIFF)))
-...
-```
-
-If `block.number >= BYZANTIUM_FORK_BLKNUM`, we change the first line to the following:
-
-``` python
-adj_factor = max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)
-```
-### Rationale
-
-This new formula ensures that the difficulty adjustment algorithm targets a constant average rate of blocks produced including uncles, and so ensures a highly predictable issuance rate that cannot be manipulated upward by manipulating the uncle rate. A formula that accounts for the exact number of included uncles:
-``` python
-adj_factor = max(1 + len(parent.uncles) - ((timestamp - parent.timestamp) // 9), -99)
-```
-can be fairly easily seen to be (to within a tolerance of ~3/4194304) mathematically equivalent to assuming that a block with `k` uncles is equivalent to a sequence of `k+1` blocks that all appear with the exact same timestamp, and this is likely the simplest possible way to accomplish the desired effect. But since the exact formula depends on the full block and not just the header, we are instead using an approximate formula that accomplishes almost the same effect but has the benefit that it depends only on the block header (as you can check the uncle hash against the blank hash).
-
-Changing the denominator from 10 to 9 ensures that the block time remains roughly the same (in fact, it should decrease by ~3% given the current uncle rate of 7%).
-
-### References
-
-1. EIP 100 issue and discussion: https://github.com/ethereum/EIPs/issues/100
-2. https://bitslog.wordpress.com/2016/04/28/uncle-mining-an-ethereum-consensus-protocol-flaw/"#;
+"#;
let reports = Linter::>::default()
.clear_lints()
- .deny("markdown-headings-only", HeadingFirst {})
+ .deny("markdown-heading-first", HeadingFirst {})
.check_slice(None, src)
.run()
.await