[GH-ISSUE #977] New rule: Prevent lazy continuation lines #2425

Open
opened 2026-03-07 20:07:39 +03:00 by kerem · 13 comments
Owner

Originally created by @skwde on GitHub (Sep 15, 2023).
Original GitHub issue: https://github.com/DavidAnson/markdownlint/issues/977

This is somewhat related to MD032 and emerged from https://github.com/DavidAnson/markdownlint/issues/972

Lazy line conditions make the markdown file less readable. That is because

1. abc
2. def
ghi

behave the same like standard lists

1. abc
2. def
   ghi

Even more confusing/weird is the behavior when using multiple levels like

- abc
  + def
ghi

where ghi goes to the 2nd indent level.

In addition, as mentioned in https://github.com/DavidAnson/markdownlint/blob/main/doc/md032.md some parsers do not parse lists if they are not surrounded by blank lines. MD032 is however only concerned with the starting line because it cannot know when it should be a lazy line continuation or when the list is done.

Originally created by @skwde on GitHub (Sep 15, 2023). Original GitHub issue: https://github.com/DavidAnson/markdownlint/issues/977 This is somewhat related to `MD032` and emerged from <https://github.com/DavidAnson/markdownlint/issues/972> Lazy line conditions make the markdown file less readable. That is because ```markdown 1. abc 2. def ghi ``` behave the same like standard lists ```markdown 1. abc 2. def ghi ``` Even more confusing/weird is the behavior when using multiple levels like ```markdown - abc + def ghi ``` where `ghi` goes to the 2nd indent level. In addition, as mentioned in <https://github.com/DavidAnson/markdownlint/blob/main/doc/md032.md> some parsers do not parse lists if they are not surrounded by blank lines. `MD032` is however only concerned with the starting line because it cannot know when it should be a lazy line continuation or when the list is done.
Author
Owner

@BlaineEXE commented on GitHub (Apr 29, 2024):

Ditto. This would be a great add :)

<!-- gh-comment-id:2083794693 --> @BlaineEXE commented on GitHub (Apr 29, 2024): Ditto. This would be a great add :)
Author
Owner

@amyq commented on GitHub (Jun 3, 2024):

Chiming in here. My coworker Marcel (@ravlen) and I have been talking about this problem over in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155088 for the GitLab docs. He's trying to construct a rule in Vale to error out on (what I now know are) lazy continuation lines.

While lazy continuation lines are valid CommonMark, I agree with Marcel - when managing Markdown-formatted docs at scale, our technical writing team would find it helpful to prevent them from creeping into our docs. It feels like a good rule to leave off by default, but make available to users who need every scrap of scannability improvements they can get.

<!-- gh-comment-id:2146140495 --> @amyq commented on GitHub (Jun 3, 2024): Chiming in here. My coworker Marcel (@ravlen) and I have been talking about this problem over in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155088 for the GitLab docs. He's trying to construct a rule in Vale to error out on (what I now know are) lazy continuation lines. While lazy continuation lines are valid CommonMark, I agree with Marcel - when managing Markdown-formatted docs at scale, our technical writing team would find it helpful to prevent them from creeping into our docs. It feels like a good rule to leave off by default, but make available to users who need every scrap of scannability improvements they can get.
Author
Owner

@Ravlen commented on GitHub (Jun 4, 2024):

Yeah, 👍 from me, though I think this is a repeat of the request in https://github.com/DavidAnson/markdownlint/issues/344, where I've got a non-breaking change suggestion that could work in https://github.com/DavidAnson/markdownlint/issues/344#issuecomment-1891542874

<!-- gh-comment-id:2146428273 --> @Ravlen commented on GitHub (Jun 4, 2024): Yeah, 👍 from me, though I think this is a repeat of the request in https://github.com/DavidAnson/markdownlint/issues/344, where I've got a non-breaking change suggestion that could work in https://github.com/DavidAnson/markdownlint/issues/344#issuecomment-1891542874
Author
Owner

@wwarriner commented on GitHub (Jun 6, 2025):

In preparation for a fix, we'd need to formalize what it means to be a lazy continuation line to constrain the problem space. Here is my first attempt at a formalization.

  1. Occur only as part of a list item.
  2. Have a parent list item, defined to be the list item with the largest line number among preceding lines.
  3. Have no blank lines between self and parent list item.
  4. Are content lines with no leading list marker.
  5. Start at a smaller column number than the starting column number of the content of the parent list item.

Presumably the parser already knows what content is part of list item and that information is available for rules to examine in markdownlint. This would enable checking items (1)-(4). Item (5) is not yet accounted for, and what we need to consider.

I wrote the last item like I did because it allows us to account for block quotes implicitly. Nothing in the spec appears to allow other contexts starting on the same line as a list item, so we need only consider block quotes. If another context starts on the next line, then other parts of the spec apply, that line isn't part of the list item (unless indented appropriately), and this rule doesn't apply. If another context starts after one or more blank lines, then this rule doesn't apply by item (3).

As for fixes, consider (5). If we know the starting column of the parent list item, and the starting column of the lazy continuation line, we can increase the indent of the lazy continuation line by the difference, to match the parent list item. Repeat this for all lazy continuation lines of the current list item.

Have I missed anything?

Edit: Some parsers may treat any amount of indentation on subsequent lines to be part of the list item. So we may also need to consider any case where indentation doesn't match parent list item content.

<!-- gh-comment-id:2950506411 --> @wwarriner commented on GitHub (Jun 6, 2025): In preparation for a fix, we'd need to formalize what it means to be a lazy continuation line to constrain the problem space. Here is my first attempt at a formalization. 1. Occur only as part of a list item. 2. Have a parent list item, defined to be the list item with the largest line number among preceding lines. 3. Have no blank lines between self and parent list item. 4. Are content lines with no leading list marker. 5. Start at a smaller column number than the starting column number of the content of the parent list item. Presumably the parser already knows what content is part of list item and that information is available for rules to examine in markdownlint. This would enable checking items (1)-(4). Item (5) is not yet accounted for, and what we need to consider. I wrote the last item like I did because it allows us to account for block quotes implicitly. Nothing in the spec appears to allow other contexts starting on the same line as a list item, so we need only consider block quotes. If another context starts on the next line, then other parts of the spec apply, that line isn't part of the list item (unless indented appropriately), and this rule doesn't apply. If another context starts after one or more blank lines, then this rule doesn't apply by item (3). As for fixes, consider (5). If we know the starting column of the parent list item, and the starting column of the lazy continuation line, we can increase the indent of the lazy continuation line by the difference, to match the parent list item. Repeat this for all lazy continuation lines of the current list item. Have I missed anything? Edit: Some parsers may treat _any_ amount of indentation on subsequent lines to be part of the list item. So we may also need to consider any case where indentation doesn't match parent list item content.
Author
Owner

@wwarriner commented on GitHub (Jun 26, 2025):

Here is a first attempt at a custom rule. I've got working tests and everything, but I haven't tried it on a documentation repository I manage yet.

https://github.com/wwarriner/markdownlint-rule-lazy-continuation-lines

I welcome feedback on the code, I'm a complete novice at JS.

<!-- gh-comment-id:3009709523 --> @wwarriner commented on GitHub (Jun 26, 2025): Here is a first attempt at a custom rule. I've got working tests and everything, but I haven't tried it on a documentation repository I manage yet. https://github.com/wwarriner/markdownlint-rule-lazy-continuation-lines I welcome feedback on the code, I'm a complete novice at JS.
Author
Owner

@DavidAnson commented on GitHub (Jun 27, 2025):

@wwarriner, what I saw briefly looked was pretty much as I'd expect. You may want to consider importing helpers/micromark for some token utility functions that could possibly replace some of what you have. Thanks for sharing!

<!-- gh-comment-id:3011088400 --> @DavidAnson commented on GitHub (Jun 27, 2025): @wwarriner, what I saw briefly looked was pretty much as I'd expect. You may want to consider importing `helpers/micromark` for some token utility functions that could possibly replace some of what you have. Thanks for sharing!
Author
Owner

@wwarriner commented on GitHub (Jun 27, 2025):

@DavidAnson Thank you, good to know. I'll look into the helpers, that's great!

JS (especially module management) is all very new to me, so I'd like to pick your brain about some things to facilitate development of future rules.

  1. I've posted an issue related to use with vscode-markdownlint, as I'm seeing an error. https://github.com/DavidAnson/vscode-markdownlint/issues/386
  2. How do I go about getting this custom rule added to npm as a package? What sort of maintenance cost (time and effort) should I expect?
  3. Are you open to adding this rule into the core markdownlint rules, and what would I need to do to make that happen?
  4. What are useful resources to learn more about how the various module import/require type functions (and exporting modules)? I think I have a decent handle on writing JS, but providing reusable interfaces to what I write is still magic to me.
<!-- gh-comment-id:3013443842 --> @wwarriner commented on GitHub (Jun 27, 2025): @DavidAnson Thank you, good to know. I'll look into the helpers, that's great! JS (especially module management) is all very new to me, so I'd like to pick your brain about some things to facilitate development of future rules. 1. I've posted an issue related to use with `vscode-markdownlint`, as I'm seeing an error. <https://github.com/DavidAnson/vscode-markdownlint/issues/386> 2. How do I go about getting this custom rule added to `npm` as a package? What sort of maintenance cost (time and effort) should I expect? 3. Are you open to adding this rule into the core `markdownlint` rules, and what would I need to do to make that happen? 4. What are useful resources to learn more about how the various module import/require type functions (and exporting modules)? I think I have a decent handle on writing JS, but providing reusable interfaces to what I write is still magic to me.
Author
Owner

@DavidAnson commented on GitHub (Jun 27, 2025):

The Node and npm documentation sites are both good resources to get answers to your questions. There are probably some blogs that take a friendlier approach to this, but I don't know of any to recommend offhand. Other than syntax, I don't think the philosophy of exports in Node is much different than other environments.

Regarding the specific question about publishing your custom rule, I would recommend having a look at this custom rule I previously wrote and published because it's about as simple as possible: https://github.com/DavidAnson/markdownlint-rule-extended-ascii

Before adding this rule to the core library, I would want to see it used in the real world for a while to work out any kinks. That's also an opportunity to convert to the helper functions I reference above because I wouldn't want duplication in the core library. Finally, I'd want to review the code for style and behavior. But the first step is definitely to demonstrate utility and versatility in the real world. :)

<!-- gh-comment-id:3014399570 --> @DavidAnson commented on GitHub (Jun 27, 2025): The Node and npm documentation sites are both good resources to get answers to your questions. There are probably some blogs that take a friendlier approach to this, but I don't know of any to recommend offhand. Other than syntax, I don't think the philosophy of exports in Node is much different than other environments. Regarding the specific question about publishing your custom rule, I would recommend having a look at this custom rule I previously wrote and published because it's about as simple as possible: https://github.com/DavidAnson/markdownlint-rule-extended-ascii Before adding this rule to the core library, I would want to see it used in the real world for a while to work out any kinks. That's also an opportunity to convert to the helper functions I reference above because I wouldn't want duplication in the core library. Finally, I'd want to review the code for style and behavior. But the first step is definitely to demonstrate utility and versatility in the real world. :)
Author
Owner

@Ravlen commented on GitHub (Nov 26, 2025):

@DavidAnson Coming back to this after some more cleanup work in our docs and I wonder if there would be interest in a more targeted rule for one case of lazy continuation lines in lists. I'm realizing that lazy continuation for the last list item is more problematic than the other list items. There is more ambiguity here, which I think is a good reason to add it to the main markdownlint rules.

If the last line is a lazy continuation, it could be that the author is trying to do one of two things, and parsers can't determine which one the author is intending:

  • Lazy continuation of the previous list item.
  • New line intended to be outside the list.

For example:

- List item 1 details
- List item 2 details
These are important details.

It's ambiguous what the author is intending. In all cases, lazy continuation should be avoided at the end of the list, in preference of either:

  • A newline
  • Indentation that matches the list item above.

Then the accepted options would be either:

  1. - List item 1
    - List item 2
    
    These are important details.
    
  2. - List item 1
    - List item 2
      This is an important detail.
    
<!-- gh-comment-id:3579217853 --> @Ravlen commented on GitHub (Nov 26, 2025): @DavidAnson Coming back to this after some more cleanup work in our docs and I wonder if there would be interest in a more targeted rule for one case of lazy continuation lines in lists. I'm realizing that lazy continuation for the _last_ list item is more problematic than the other list items. There is more ambiguity here, which I think is a good reason to add it to the main markdownlint rules. If the last line is a lazy continuation, it could be that the author is trying to do one of two things, and parsers can't determine which one the author is intending: - Lazy continuation of the previous list item. - New line intended to be outside the list. For example: ```markdown - List item 1 details - List item 2 details These are important details. ``` It's ambiguous what the author is intending. In all cases, lazy continuation should be avoided at the end of the list, in preference of either: - A newline - Indentation that matches the list item above. Then the accepted options would be either: 1. ```markdown - List item 1 - List item 2 These are important details. ``` 1. ```markdown - List item 1 - List item 2 This is an important detail. ```
Author
Owner

@DavidAnson commented on GitHub (Nov 26, 2025):

@Ravlen, have you tried the implementation above?

<!-- gh-comment-id:3579262268 --> @DavidAnson commented on GitHub (Nov 26, 2025): @Ravlen, have you tried the implementation above?
Author
Owner

@Ravlen commented on GitHub (Nov 28, 2025):

@DavidAnson Unfortunately, I couldn't get it to work, there were dependency issues for me (could very possibly on my end).

For a POC, I used AI to generate something workable (I'm not an engineer and can't write code). I'm not sure I would submit the AI-generated code for review at GitLab, because I can't guarantee the quality of it, but it at least demonstrated the value it would have for me: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/214400

<!-- gh-comment-id:3587707397 --> @Ravlen commented on GitHub (Nov 28, 2025): @DavidAnson Unfortunately, I couldn't get it to work, there were dependency issues for me (could very possibly on my end). For a POC, I used AI to generate something workable (I'm not an engineer and can't write code). I'm not sure I would submit the AI-generated code for review at GitLab, because I can't guarantee the quality of it, but it at least demonstrated the value it would have for me: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/214400
Author
Owner

@DavidAnson commented on GitHub (Nov 28, 2025):

@Ravlen, AI submissions are against the policies of this project.

<!-- gh-comment-id:3587740755 --> @DavidAnson commented on GitHub (Nov 28, 2025): @Ravlen, AI submissions are against the policies of this project.
Author
Owner

@Ravlen commented on GitHub (Nov 28, 2025):

@DavidAnson Yup! Which is why it's just a POC (to see how much of an impact it would have on our large docs project) and not submitted for review here (or anywhere) 🙇

<!-- gh-comment-id:3587830027 --> @Ravlen commented on GitHub (Nov 28, 2025): @DavidAnson Yup! Which is why it's just a POC (to see how much of an impact it would have on our large docs project) and not submitted for review here (or anywhere) 🙇
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/markdownlint#2425
No description provided.