Skip to content

Commit fea6322

Browse files
feat(md077): support fixed continuation indent config (#786)
Adds an optional `indent` setting to MD077 so documents that follow a different indentation convention (e.g. 4 spaces for MkDocs, or a .editorconfig-driven style) can pin the required continuation indent instead of the content-column-derived default. The requirement becomes marker column + configured indent, per item. Closes #784
1 parent 313d72a commit fea6322

3 files changed

Lines changed: 104 additions & 2 deletions

File tree

docs/md077.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,41 @@ to the content column.
234234
# column (mdformat parity). Tight under-indented lazy
235235
# continuation is additionally flagged and snapped up.
236236
style = "any"
237+
238+
# Fixed continuation indent relative to each item's marker, e.g. 4 for
239+
# MkDocs-style documents. When set, this overrides the content-column-derived
240+
# requirement (including the MkDocs flavor's 4-space minimum). Unset (None)
241+
# keeps the default content-column behavior.
242+
# indent = 4
243+
```
244+
245+
### `indent`
246+
247+
By default MD077 derives the required continuation indent from each item's
248+
content column (the W+N rule; under the MkDocs flavor a 4-space minimum is
249+
enforced). When a fixed `indent` is configured, the requirement becomes the
250+
item's *marker column plus the configured indent*, which lets documents that
251+
follow a `.editorconfig` style (e.g. `indent = 2`, or MkDocs's 4-space
252+
convention) stay consistent with tools like `editorconfig-checker`:
253+
254+
```toml
255+
[MD077]
256+
indent = 4
237257
```
238258

259+
With `indent = 4`, a top-level item (marker at column 0) requires continuation
260+
content at 4 spaces, and a nested item (marker at column 2) requires it at 6:
261+
262+
```markdown
263+
- a long line
264+
that continues 4 spaces past the marker
265+
- nested item
266+
that continues 4 spaces past the nested marker
267+
```
268+
269+
Continuation content below the fixed requirement is flagged and fixed up to it
270+
(the same rules for blank-line escapes and over-indentation still apply).
271+
239272
### `style = "aligned"`
240273

241274
By default (`style = "any"`), a wrapped list item may continue at zero indent

src/rules/md077_list_continuation_indent.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl MD077ListContinuationIndent {
5353
/// (`ContinuationStyle::Any`) preserves the historical behavior.
5454
pub fn new(style: ContinuationStyle) -> Self {
5555
Self {
56-
config: MD077Config { style },
56+
config: MD077Config { style, indent: None },
5757
}
5858
}
5959

@@ -651,7 +651,11 @@ impl Rule for MD077ListContinuationIndent {
651651
.iter()
652652
.enumerate()
653653
.map(|(item_idx, &(item_line, marker_col, content_col, task_col))| {
654-
let required = if strict_indent { content_col.max(4) } else { content_col };
654+
let required = match self.config.indent {
655+
Some(indent) => marker_col + indent,
656+
None if strict_indent => content_col.max(4),
657+
None => content_col,
658+
};
655659
(
656660
item_line,
657661
marker_col,
@@ -2886,6 +2890,48 @@ mod tests {
28862890
assert!(rule.check(&ctx).unwrap().is_empty());
28872891
}
28882892

2893+
#[test]
2894+
fn from_config_indent_sets_fixed_requirement() {
2895+
// End-to-end: `[MD077] indent = 4` wires through from_config and
2896+
// requires continuation content to sit 4 spaces past the marker.
2897+
let mut config = crate::config::Config::default();
2898+
let mut rule_config = crate::config::RuleConfig::default();
2899+
rule_config.values.insert("indent".to_string(), toml::Value::Integer(4));
2900+
config.rules.insert("MD077".to_string(), rule_config);
2901+
2902+
let rule = MD077ListContinuationIndent::from_config(&config);
2903+
2904+
// "- item\n wrap\n" -> continuation at 4 spaces: accepted.
2905+
let ok_ctx = LintContext::new("- item\n wrap\n", MarkdownFlavor::Standard, None);
2906+
assert!(rule.check(&ok_ctx).unwrap().is_empty());
2907+
assert_eq!(rule.fix(&ok_ctx).unwrap(), "- item\n wrap\n");
2908+
2909+
// "- item\n\n wrap\n" -> continuation at 2 spaces after a blank line:
2910+
// flagged as under-indented (would escape the list item).
2911+
let bad_ctx = LintContext::new("- item\n\n wrap\n", MarkdownFlavor::Standard, None);
2912+
let warnings = rule.check(&bad_ctx).unwrap();
2913+
assert_eq!(warnings.len(), 1);
2914+
assert!(warnings[0].message.contains("needs 4 spaces"));
2915+
}
2916+
2917+
#[test]
2918+
fn from_config_indent_applies_per_nested_marker() {
2919+
// With a fixed indent, each item's requirement is its own marker
2920+
// column plus the configured indent (nested item marker at 2 -> 6).
2921+
let mut config = crate::config::Config::default();
2922+
let mut rule_config = crate::config::RuleConfig::default();
2923+
rule_config.values.insert("indent".to_string(), toml::Value::Integer(4));
2924+
config.rules.insert("MD077".to_string(), rule_config);
2925+
2926+
let rule = MD077ListContinuationIndent::from_config(&config);
2927+
let ctx = LintContext::new("- a\n - b\n wrap\n", MarkdownFlavor::Standard, None);
2928+
let warnings = rule.check(&ctx).unwrap();
2929+
assert!(
2930+
warnings.is_empty(),
2931+
"continuation at 6 spaces should pass: {warnings:?}"
2932+
);
2933+
}
2934+
28892935
#[test]
28902936
fn aligned_tight_underindented_fence_inside_item_left_alone() {
28912937
// A fenced block is a structural construct; aligned mode does not

src/rules/md077_list_continuation_indent/md077_config.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ pub struct MD077Config {
99
/// How strictly continuation-line indentation is enforced.
1010
#[serde(default)]
1111
pub style: ContinuationStyle,
12+
/// Fixed continuation indent relative to the list marker, e.g. `4` for
13+
/// MkDocs-style documents. When set, overrides the content-column-derived
14+
/// requirement (`content_col`, or `max(content_col, 4)` under the MkDocs
15+
/// flavor). `None` keeps the default content-column behavior.
16+
#[serde(default)]
17+
pub indent: Option<usize>,
1218
}
1319

1420
impl RuleConfig for MD077Config {
@@ -22,19 +28,36 @@ mod tests {
2228
#[test]
2329
fn defaults_to_any() {
2430
assert_eq!(MD077Config::default().style, ContinuationStyle::Any);
31+
assert_eq!(MD077Config::default().indent, None);
2532
let parsed: MD077Config = toml::from_str("").unwrap();
2633
assert_eq!(parsed.style, ContinuationStyle::Any);
34+
assert_eq!(parsed.indent, None);
2735
}
2836

2937
#[test]
3038
fn parses_aligned() {
3139
let parsed: MD077Config = toml::from_str(r#"style = "aligned""#).unwrap();
3240
assert_eq!(parsed.style, ContinuationStyle::Aligned);
41+
assert_eq!(parsed.indent, None);
3342
}
3443

3544
#[test]
3645
fn parses_any() {
3746
let parsed: MD077Config = toml::from_str(r#"style = "any""#).unwrap();
3847
assert_eq!(parsed.style, ContinuationStyle::Any);
3948
}
49+
50+
#[test]
51+
fn parses_fixed_indent() {
52+
let parsed: MD077Config = toml::from_str("indent = 4").unwrap();
53+
assert_eq!(parsed.indent, Some(4));
54+
assert_eq!(parsed.style, ContinuationStyle::Any);
55+
}
56+
57+
#[test]
58+
fn parses_style_and_indent_together() {
59+
let parsed: MD077Config = toml::from_str("style = \"aligned\"\nindent = 4").unwrap();
60+
assert_eq!(parsed.style, ContinuationStyle::Aligned);
61+
assert_eq!(parsed.indent, Some(4));
62+
}
4063
}

0 commit comments

Comments
 (0)