Skip to content

Commit

Permalink
Some minor fixes: (#10)
Browse files Browse the repository at this point in the history
- plug  md003 to the linter
- more detailed messages for rules violations
- documentation for md003
  • Loading branch information
ekropotin authored Jun 17, 2024
1 parent d05d0dd commit 7521b33
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ TBD
## Rules

- **[MD001](docs/rules/md001.md)** *heading-increment* - Heading levels should only increment by one level at a time

- **[MD003](docs/rules/md003.md)** *heading-style* - Heading style
37 changes: 37 additions & 0 deletions docs/rules/md003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# `MD003` - Heading style

Tags: `headings`

Aliases: `heading-style`

Parameters:

- `style`: Heading style (`string`, default `consistent`, values `atx` / `consistent` / `setext`)

This rule is triggered when different heading styles are used in the same
document:

```markdown
# ATX style H1

Setext style H1
===============
```

To fix the issue, use consistent heading styles throughout the document:

```markdown
# ATX style H1

## ATX style H2
```

Note: The placement of a horizontal rule directly below a line of text can
trigger this rule by turning that text into a level 2 setext-style heading:

```markdown
A line of text followed by a horizontal rule becomes a heading
---
```

Rationale: Consistent formatting makes it easier to understand a document.
3 changes: 2 additions & 1 deletion src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ pub struct RuleViolation {
impl RuleViolation {
pub fn new(
rule: &'static Rule,
message: String,
severity: RuleViolationSeverity,
file_path: PathBuf,
pos: &Sourcepos,
) -> Self {
Self {
rule,
message: rule.description.to_string(),
message,
severity,
location: Location {
file_path,
Expand Down
9 changes: 6 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ fn main() -> anyhow::Result<()> {
let file_content = fs::read_to_string(&file_path)
.context(format!("Can't read file {}", &file_path.to_string_lossy()))?;

let rules = vec![quickmark::rules::md001::MD001];
let rules = vec![
quickmark::rules::md001::MD001,
quickmark::rules::md003::MD003,
];

let context = quickmark::linter::Context {
file_path: file_path.clone(),
Expand All @@ -33,14 +36,14 @@ fn main() -> anyhow::Result<()> {
.lint(&file_content)
.iter()
.fold((0, 0), |(warns, errs), v| {
eprint!("{}", v);
eprintln!("{}", v);
match &v.severity {
Error => (warns, errs + 1),
_ => (warns + 1, errs),
}
});

println!("\n\nErrors: {}", errs);
println!("\nErrors: {}", errs);
println!("Warnings: {}", warns);

let exit_code = min(errs, 1);
Expand Down
6 changes: 6 additions & 0 deletions src/rules/md001.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ impl RuleLinter for MD001Linter {
{
return Option::Some(RuleViolation::new(
&MD001,
format!(
"{} [Expected: h{}; Actual: h{}]",
MD001.description,
self.current_heading_level + 1,
level
),
RuleViolationSeverity::Error,
self.context.file_path.clone(),
&(node.sourcepos),
Expand Down
108 changes: 105 additions & 3 deletions src/rules/md003.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::fmt;

use comrak::nodes::{Ast, NodeHeading, NodeValue};

use crate::linter::{Context, HeadingStyle, RuleLinter, RuleViolation, RuleViolationSeverity};
Expand All @@ -10,6 +12,15 @@ enum Style {
Atx,
}

impl fmt::Display for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Style::Setext => write!(f, "setext"),
Style::Atx => write!(f, "atx"),
}
}
}

pub(crate) struct MD003Linter {
context: Context,
enforced_style: Option<Style>,
Expand Down Expand Up @@ -44,6 +55,10 @@ impl RuleLinter for MD003Linter {
if style != *enforced_style {
return Option::Some(RuleViolation::new(
&MD003,
format!(
"{} [Expected: {}; Actual: {}]",
MD003.description, enforced_style, style
),
RuleViolationSeverity::Error,
self.context.file_path.clone(),
&node.sourcepos,
Expand Down Expand Up @@ -84,7 +99,8 @@ mod test {
};
let mut linter = (MD003.new_linter)(context);

let input = "Setext level 1
let input = "
Setext level 1
--------------
Setext level 2
==============
Expand All @@ -105,7 +121,8 @@ Setext level 2
};
let mut linter = (MD003.new_linter)(context);

let input = "Setext level 1
let input = "
Setext level 1
--------------
Setext level 2
==============
Expand All @@ -124,9 +141,94 @@ Setext level 2
};
let mut linter = (MD003.new_linter)(context);

let input = "# Atx heading 1
let input = "
# Atx heading 1
## Atx heading 2
### Atx heading 3
";
let violations = lint_content(input, &mut linter);
assert_eq!(violations.len(), 0);
}

#[test]
fn test_heading_style_atx_positive() {
let context = Context {
file_path: PathBuf::from("test.md"),
settings: Settings {
heading_style: HeadingStyle::Atx,
},
};
let mut linter = (MD003.new_linter)(context);

let input = "
Setext heading 1
----------------
Setext heading 2
================
### Atx heading 3
";
let violations = lint_content(input, &mut linter);
assert_eq!(violations.len(), 2);
}

#[test]
fn test_heading_style_atx_negative() {
let context = Context {
file_path: PathBuf::from("test.md"),
settings: Settings {
heading_style: HeadingStyle::Atx,
},
};
let mut linter = (MD003.new_linter)(context);

let input = "
# Atx heading 1
## Atx heading 2
### Atx heading 3
";
let violations = lint_content(input, &mut linter);
assert_eq!(violations.len(), 0);
}

#[test]
fn test_heading_style_setext_positive() {
let context = Context {
file_path: PathBuf::from("test.md"),
settings: Settings {
heading_style: HeadingStyle::Setext,
},
};
let mut linter = (MD003.new_linter)(context);

let input = "
# Atx heading 1
Setext heading 1
----------------
Setext heading 2
================
### Atx heading 3
";
let violations = lint_content(input, &mut linter);
assert_eq!(violations.len(), 2);
}

#[test]
fn test_heading_style_setext_negative() {
let context = Context {
file_path: PathBuf::from("test.md"),
settings: Settings {
heading_style: HeadingStyle::Setext,
},
};
let mut linter = (MD003.new_linter)(context);

let input = "
Setext heading 1
----------------
Setext heading 2
================
Setext heading 2
================
";
let violations = lint_content(input, &mut linter);
assert_eq!(violations.len(), 0);
Expand Down

0 comments on commit 7521b33

Please sign in to comment.