rumdl 0.0.66__tar.gz → 0.0.68__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rumdl might be problematic. Click here for more details.

Files changed (259) hide show
  1. {rumdl-0.0.66 → rumdl-0.0.68}/Cargo.lock +17 -1
  2. {rumdl-0.0.66 → rumdl-0.0.68}/Cargo.toml +5 -1
  3. {rumdl-0.0.66 → rumdl-0.0.68}/PKG-INFO +1 -1
  4. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md033.md +9 -1
  5. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md036.md +11 -0
  6. {rumdl-0.0.66 → rumdl-0.0.68}/src/lsp/types.rs +13 -4
  7. {rumdl-0.0.66 → rumdl-0.0.68}/src/main.rs +91 -1
  8. {rumdl-0.0.66 → rumdl-0.0.68}/src/rule.rs +4 -2
  9. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md001_heading_increment.rs +16 -3
  10. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md002_first_heading_h1.rs +18 -2
  11. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md003_heading_style.rs +97 -139
  12. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md004_unordered_list_style.rs +10 -6
  13. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md005_list_indent.rs +94 -55
  14. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md006_start_bullets.rs +28 -20
  15. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md007_ul_indent.rs +55 -75
  16. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md009_trailing_spaces.rs +40 -22
  17. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md010_no_hard_tabs.rs +67 -24
  18. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md011_no_reversed_links.rs +16 -11
  19. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md012_no_multiple_blanks.rs +29 -13
  20. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md013_line_length.rs +215 -44
  21. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md014_commands_show_output.rs +37 -18
  22. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md018_no_missing_space_atx.rs +18 -3
  23. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md019_no_multiple_space_atx.rs +19 -8
  24. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md020_no_missing_space_closed_atx.rs +50 -22
  25. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md021_no_multiple_space_closed_atx.rs +40 -10
  26. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md022_blanks_around_headings.rs +36 -18
  27. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md023_heading_start_left.rs +104 -46
  28. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md024_no_duplicate_heading.rs +34 -9
  29. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md025_single_title.rs +44 -6
  30. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md026_no_trailing_punctuation.rs +85 -34
  31. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md027_multiple_spaces_blockquote.rs +35 -9
  32. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md028_no_blanks_blockquote.rs +19 -10
  33. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md029_ordered_list_prefix.rs +5 -3
  34. rumdl-0.0.68/src/rules/md030_list_marker_space.rs +420 -0
  35. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md031_blanks_around_fences.rs +67 -35
  36. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md032_blanks_around_lists.rs +33 -17
  37. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md033_no_inline_html.rs +74 -33
  38. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md034_no_bare_urls.rs +138 -19
  39. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md035_hr_style.rs +12 -7
  40. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md036_no_emphasis_only_first.rs +34 -10
  41. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md037_spaces_around_emphasis.rs +8 -3
  42. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md038_no_space_in_code.rs +3 -1
  43. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md039_no_space_in_links.rs +11 -9
  44. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md040_fenced_code_language.rs +38 -29
  45. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md041_first_line_heading.rs +13 -4
  46. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md042_no_empty_links.rs +9 -3
  47. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md043_required_headings.rs +35 -20
  48. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md044_proper_names.rs +8 -5
  49. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md045_no_alt_text.rs +18 -11
  50. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md046_code_block_style.rs +53 -32
  51. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md047_single_trailing_newline.rs +31 -12
  52. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md048_code_fence_style.rs +39 -21
  53. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md049_emphasis_style.rs +93 -61
  54. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md050_strong_style.rs +16 -10
  55. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md051_link_fragments.rs +13 -5
  56. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md052_reference_links_images.rs +21 -8
  57. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md053_link_image_reference_definitions.rs +66 -38
  58. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md054_link_image_style.rs +69 -37
  59. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md055_table_pipe_style.rs +12 -6
  60. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md056_table_column_count.rs +12 -6
  61. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md057_existing_relative_links.rs +7 -4
  62. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/md058_blanks_around_tables.rs +23 -17
  63. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/range_utils.rs +120 -0
  64. rumdl-0.0.68/tests/character_ranges/additional_tests.rs +101 -0
  65. rumdl-0.0.68/tests/character_ranges/basic_tests.rs +129 -0
  66. rumdl-0.0.68/tests/character_ranges/comprehensive_tests.rs +104 -0
  67. rumdl-0.0.68/tests/character_ranges/extended_tests.rs +257 -0
  68. rumdl-0.0.68/tests/character_ranges/mod.rs +330 -0
  69. rumdl-0.0.68/tests/character_ranges/unicode_utils.rs +293 -0
  70. {rumdl-0.0.66 → rumdl-0.0.68}/tests/lib.rs +1 -0
  71. {rumdl-0.0.66 → rumdl-0.0.68}/tests/lsp_tests.rs +14 -2
  72. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md010_test.rs +6 -6
  73. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md014_test.rs +7 -5
  74. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md019_test.rs +1 -1
  75. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md020_test.rs +3 -3
  76. rumdl-0.0.68/tests/rules/md030_test.rs +468 -0
  77. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md033_test.rs +102 -8
  78. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md034_test.rs +1 -1
  79. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md036_test.rs +22 -2
  80. rumdl-0.0.66/src/rules/md030_list_marker_space.rs +0 -230
  81. rumdl-0.0.66/tests/rules/md030_test.rs +0 -194
  82. {rumdl-0.0.66 → rumdl-0.0.68}/.rumdl.toml +0 -0
  83. {rumdl-0.0.66 → rumdl-0.0.68}/MANIFEST.in +0 -0
  84. {rumdl-0.0.66 → rumdl-0.0.68}/Makefile +0 -0
  85. {rumdl-0.0.66 → rumdl-0.0.68}/README.md +0 -0
  86. {rumdl-0.0.66 → rumdl-0.0.68}/assets/logo.png +0 -0
  87. {rumdl-0.0.66 → rumdl-0.0.68}/benches/range_performance.rs +0 -0
  88. {rumdl-0.0.66 → rumdl-0.0.68}/benches/range_utils_benchmark.rs +0 -0
  89. {rumdl-0.0.66 → rumdl-0.0.68}/benches/rule_performance.rs +0 -0
  90. {rumdl-0.0.66 → rumdl-0.0.68}/docs/RULES.md +0 -0
  91. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md001.md +0 -0
  92. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md002.md +0 -0
  93. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md003.md +0 -0
  94. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md004.md +0 -0
  95. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md005.md +0 -0
  96. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md006.md +0 -0
  97. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md007.md +0 -0
  98. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md009.md +0 -0
  99. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md010.md +0 -0
  100. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md011.md +0 -0
  101. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md012.md +0 -0
  102. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md013.md +0 -0
  103. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md014.md +0 -0
  104. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md018.md +0 -0
  105. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md019.md +0 -0
  106. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md020.md +0 -0
  107. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md021.md +0 -0
  108. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md022.md +0 -0
  109. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md023.md +0 -0
  110. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md024.md +0 -0
  111. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md025.md +0 -0
  112. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md026.md +0 -0
  113. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md027.md +0 -0
  114. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md028.md +0 -0
  115. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md029.md +0 -0
  116. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md030.md +0 -0
  117. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md031.md +0 -0
  118. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md032.md +0 -0
  119. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md034.md +0 -0
  120. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md035.md +0 -0
  121. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md037.md +0 -0
  122. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md038.md +0 -0
  123. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md039.md +0 -0
  124. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md040.md +0 -0
  125. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md041.md +0 -0
  126. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md042.md +0 -0
  127. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md043.md +0 -0
  128. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md044.md +0 -0
  129. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md045.md +0 -0
  130. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md046.md +0 -0
  131. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md047.md +0 -0
  132. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md048.md +0 -0
  133. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md049.md +0 -0
  134. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md050.md +0 -0
  135. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md051.md +0 -0
  136. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md052.md +0 -0
  137. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md053.md +0 -0
  138. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md054.md +0 -0
  139. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md055.md +0 -0
  140. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md056.md +0 -0
  141. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md057.md +0 -0
  142. {rumdl-0.0.66 → rumdl-0.0.68}/docs/md058.md +0 -0
  143. {rumdl-0.0.66 → rumdl-0.0.68}/issues/plan-rule-parity-with-markdownlint.md +0 -0
  144. {rumdl-0.0.66 → rumdl-0.0.68}/parity_check.py +0 -0
  145. {rumdl-0.0.66 → rumdl-0.0.68}/pyproject.toml +0 -0
  146. {rumdl-0.0.66 → rumdl-0.0.68}/python/MANIFEST.in +0 -0
  147. {rumdl-0.0.66 → rumdl-0.0.68}/python/PYTHON-README.md +0 -0
  148. {rumdl-0.0.66 → rumdl-0.0.68}/python/rumdl/__init__.py +0 -0
  149. {rumdl-0.0.66 → rumdl-0.0.68}/python/rumdl/__main__.py +0 -0
  150. {rumdl-0.0.66 → rumdl-0.0.68}/python/rumdl/py.typed +0 -0
  151. {rumdl-0.0.66 → rumdl-0.0.68}/rumdl.toml.example +0 -0
  152. {rumdl-0.0.66 → rumdl-0.0.68}/src/config.rs +0 -0
  153. {rumdl-0.0.66 → rumdl-0.0.68}/src/init.rs +0 -0
  154. {rumdl-0.0.66 → rumdl-0.0.68}/src/lib.rs +0 -0
  155. {rumdl-0.0.66 → rumdl-0.0.68}/src/lint_context.rs +0 -0
  156. {rumdl-0.0.66 → rumdl-0.0.68}/src/lsp/mod.rs +0 -0
  157. {rumdl-0.0.66 → rumdl-0.0.68}/src/lsp/server.rs +0 -0
  158. {rumdl-0.0.66 → rumdl-0.0.68}/src/markdownlint_config.rs +0 -0
  159. {rumdl-0.0.66 → rumdl-0.0.68}/src/profiling.rs +0 -0
  160. {rumdl-0.0.66 → rumdl-0.0.68}/src/python.rs +0 -0
  161. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/blockquote_utils.rs +0 -0
  162. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/code_block_utils.rs +0 -0
  163. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/code_fence_utils.rs +0 -0
  164. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/emphasis_style.rs +0 -0
  165. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/front_matter_utils.rs +0 -0
  166. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/heading_utils.rs +0 -0
  167. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/list_utils.rs +0 -0
  168. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/mod.rs +0 -0
  169. {rumdl-0.0.66 → rumdl-0.0.68}/src/rules/strong_style.rs +0 -0
  170. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/ast_utils.rs +0 -0
  171. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/code_block_utils.rs +0 -0
  172. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/document_structure.rs +0 -0
  173. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/early_returns.rs +0 -0
  174. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/element_cache.rs +0 -0
  175. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/markdown_elements.rs +0 -0
  176. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/mod.rs +0 -0
  177. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/regex_cache.rs +0 -0
  178. {rumdl-0.0.66 → rumdl-0.0.68}/src/utils/string_interner.rs +0 -0
  179. {rumdl-0.0.66 → rumdl-0.0.68}/tests/advanced_integration_tests.rs +0 -0
  180. {rumdl-0.0.66 → rumdl-0.0.68}/tests/cli_duplication_test.rs +0 -0
  181. {rumdl-0.0.66 → rumdl-0.0.68}/tests/cli_integration_tests.rs +0 -0
  182. {rumdl-0.0.66 → rumdl-0.0.68}/tests/commonmark_compliance_tests.rs +0 -0
  183. {rumdl-0.0.66 → rumdl-0.0.68}/tests/comprehensive_integration_tests.rs +0 -0
  184. {rumdl-0.0.66 → rumdl-0.0.68}/tests/config_application_tests.rs +0 -0
  185. {rumdl-0.0.66 → rumdl-0.0.68}/tests/config_tests.rs +0 -0
  186. {rumdl-0.0.66 → rumdl-0.0.68}/tests/init_command_test.rs +0 -0
  187. {rumdl-0.0.66 → rumdl-0.0.68}/tests/init_tests.rs +0 -0
  188. {rumdl-0.0.66 → rumdl-0.0.68}/tests/integration_tests.rs +0 -0
  189. {rumdl-0.0.66 → rumdl-0.0.68}/tests/json_output_test.rs +0 -0
  190. {rumdl-0.0.66 → rumdl-0.0.68}/tests/lsp_integration_tests.rs +0 -0
  191. {rumdl-0.0.66 → rumdl-0.0.68}/tests/markdownlint_cli_integration.rs +0 -0
  192. {rumdl-0.0.66 → rumdl-0.0.68}/tests/markdownlint_config_test.rs +0 -0
  193. {rumdl-0.0.66 → rumdl-0.0.68}/tests/md030_edge_cases.md +0 -0
  194. {rumdl-0.0.66 → rumdl-0.0.68}/tests/output_format_tests.rs +0 -0
  195. {rumdl-0.0.66 → rumdl-0.0.68}/tests/perf_check.rs +0 -0
  196. {rumdl-0.0.66 → rumdl-0.0.68}/tests/pyproject_config_tests.rs +0 -0
  197. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md001_test.rs +0 -0
  198. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md001_unicode_test.rs +0 -0
  199. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md002_test.rs +0 -0
  200. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md003_test.rs +0 -0
  201. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md004_test.rs +0 -0
  202. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md005_test.rs +0 -0
  203. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md006_test.rs +0 -0
  204. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md006_unicode_test.rs +0 -0
  205. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md007_test.rs +0 -0
  206. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md009_test.rs +0 -0
  207. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md011_test.rs +0 -0
  208. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md012_test.rs +0 -0
  209. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md013_test.rs +0 -0
  210. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md018_test.rs +0 -0
  211. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md021_test.rs +0 -0
  212. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md022_test.rs +0 -0
  213. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md023_extended_test.rs +0 -0
  214. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md023_test.rs +0 -0
  215. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md024_test.rs +0 -0
  216. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md025_test.rs +0 -0
  217. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md026_test.rs +0 -0
  218. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md027_test.rs +0 -0
  219. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md028_test.rs +0 -0
  220. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md029_test.rs +0 -0
  221. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md031_test.rs +0 -0
  222. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md032_test.rs +0 -0
  223. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md033_extended_test.rs +0 -0
  224. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md035_test.rs +0 -0
  225. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md037_test.rs +0 -0
  226. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md038_test.rs +0 -0
  227. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md039_test.rs +0 -0
  228. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md040_test.rs +0 -0
  229. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md041_test.rs +0 -0
  230. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md042_test.rs +0 -0
  231. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md043_test.rs +0 -0
  232. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md044_test.rs +0 -0
  233. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md045_test.rs +0 -0
  234. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md046_test.rs +0 -0
  235. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md047_test.rs +0 -0
  236. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md048_test.rs +0 -0
  237. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md049_test.rs +0 -0
  238. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md050_test.rs +0 -0
  239. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md051_test.rs +0 -0
  240. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md052_test.rs +0 -0
  241. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md053_additional_test.rs +0 -0
  242. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md053_proptest.rs +0 -0
  243. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md053_test.rs +0 -0
  244. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md054_test.rs +0 -0
  245. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md054_unicode_test.rs +0 -0
  246. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md055_test.rs +0 -0
  247. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md056_test.rs +0 -0
  248. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md057_test.rs +0 -0
  249. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/md058_test.rs +0 -0
  250. {rumdl-0.0.66 → rumdl-0.0.68}/tests/rules/mod.rs +0 -0
  251. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/blockquote_utils_test.rs +0 -0
  252. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/code_block_utils_extended_test.rs +0 -0
  253. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/code_block_utils_test.rs +0 -0
  254. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/core_utils_test.rs +0 -0
  255. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/front_matter_utils_test.rs +0 -0
  256. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/line_index_test.rs +0 -0
  257. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils/mod.rs +0 -0
  258. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils_markdown_edge_cases.rs +0 -0
  259. {rumdl-0.0.66 → rumdl-0.0.68}/tests/utils_tests.rs +0 -0
@@ -1704,7 +1704,7 @@ dependencies = [
1704
1704
 
1705
1705
  [[package]]
1706
1706
  name = "rumdl"
1707
- version = "0.0.66"
1707
+ version = "0.0.68"
1708
1708
  dependencies = [
1709
1709
  "anyhow",
1710
1710
  "assert_cmd",
@@ -1748,7 +1748,11 @@ dependencies = [
1748
1748
  "tower 0.5.2",
1749
1749
  "tower-lsp",
1750
1750
  "tower-service",
1751
+ "unicode-bidi",
1752
+ "unicode-blocks",
1751
1753
  "unicode-normalization",
1754
+ "unicode-segmentation",
1755
+ "unicode-width",
1752
1756
  "url",
1753
1757
  "walkdir",
1754
1758
  ]
@@ -2295,6 +2299,18 @@ version = "0.1.4"
2295
2299
  source = "registry+https://github.com/rust-lang/crates.io-index"
2296
2300
  checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
2297
2301
 
2302
+ [[package]]
2303
+ name = "unicode-bidi"
2304
+ version = "0.3.18"
2305
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2306
+ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
2307
+
2308
+ [[package]]
2309
+ name = "unicode-blocks"
2310
+ version = "0.1.9"
2311
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2312
+ checksum = "6b12e05d9e06373163a9bb6bb8c263c261b396643a99445fe6b9811fd376581b"
2313
+
2298
2314
  [[package]]
2299
2315
  name = "unicode-id"
2300
2316
  version = "0.3.5"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rumdl"
3
- version = "0.0.66"
3
+ version = "0.0.68"
4
4
  edition = "2021"
5
5
  description = "A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)"
6
6
  authors = ["Ruben J. Jongejan <ruben.jongejan@gmail.com>"]
@@ -81,6 +81,10 @@ proptest = "1.6.0"
81
81
  criterion = { version = "0.5", features = ["html_reports"] }
82
82
  rand = "0.9.1"
83
83
  pretty_assertions = "1.4"
84
+ unicode-segmentation = "1.12"
85
+ unicode-width = "0.2"
86
+ unicode-blocks = "0.1"
87
+ unicode-bidi = "0.3"
84
88
 
85
89
  [[bench]]
86
90
  name = "rule_performance"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rumdl
3
- Version: 0.0.66
3
+ Version: 0.0.68
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Description
4
4
 
5
- This rule disallows the use of inline HTML in Markdown documents. Using inline HTML can reduce the
5
+ This rule disallows the use of inline HTML in Markdown documents. Using inline HTML can reduce the
6
6
  portability and compatibility of Markdown content, as some platforms may restrict or sanitize HTML.
7
7
 
8
8
  Pure Markdown is more portable and can be safely rendered in a variety of environments.
@@ -26,6 +26,12 @@ This is a paragraph with **bold** and *italic* text.
26
26
  [Link text](https://example.com)
27
27
 
28
28
  ![Image alt text](image.png)
29
+
30
+ Visit <https://example.com> for more information.
31
+
32
+ Contact us at <mailto:support@example.com>
33
+
34
+ Download from <ftp://files.example.com/archive.zip>
29
35
  ```
30
36
 
31
37
  ### Invalid
@@ -87,6 +93,8 @@ Example configuration:
87
93
  - This rule checks for any HTML tags in the Markdown content
88
94
  - It does not apply to code blocks or HTML comments
89
95
  - HTML comments (e.g., `<!-- rumdl-disable MD033 -->`, `<!-- rumdl-enable MD033 -->`, or any `<!-- comment -->`) are specifically excluded from this rule
96
+ - URLs in angle brackets (e.g., `<https://example.com>`, `<ftp://files.example.com>`, `<mailto:user@example.com>`) are excluded as they are valid Markdown autolinks, not HTML
97
+ - Email addresses in angle brackets (e.g., `<user@example.com>`) are excluded as they are valid Markdown autolinks
90
98
  - The rule can be configured to allow specific HTML tags when necessary
91
99
  - Some platforms may still sanitize allowed HTML tags
92
100
 
@@ -20,6 +20,14 @@ generation of tables of contents.
20
20
  ## This is another proper heading
21
21
 
22
22
  This is a paragraph with **bold** and *italic* text within it.
23
+
24
+ **Table of Contents**
25
+
26
+ *Contents*
27
+
28
+ __TOC__
29
+
30
+ _Index_
23
31
  ```
24
32
 
25
33
  ### Invalid
@@ -89,6 +97,9 @@ punctuation = ".!?" # Only remove periods, exclamation marks, and question mark
89
97
  - Both single emphasis (*italic*) and double emphasis (**bold**) are checked
90
98
  - Triple emphasis (***bold italic***) is also checked
91
99
  - Trailing punctuation (`.`, `:`, `;`, `!`, `?`) is automatically removed when converting to headings
100
+ - Common Table of Contents labels are excluded and will not be flagged:
101
+ - `**Table of Contents**`, `*Contents*`, `__TOC__`, `_Index_`
102
+ - These are considered legitimate uses of emphasis for document structure
92
103
 
93
104
  ## Related Rules
94
105
 
@@ -37,10 +37,10 @@ pub fn warning_to_diagnostic(warning: &crate::rule::LintWarning) -> Diagnostic {
37
37
  character: (warning.column.saturating_sub(1)) as u32,
38
38
  };
39
39
 
40
- // For now, assume single character range - we can improve this later
40
+ // Use proper range from warning
41
41
  let end_position = Position {
42
- line: start_position.line,
43
- character: start_position.character + 1,
42
+ line: (warning.end_line.saturating_sub(1)) as u32,
43
+ character: (warning.end_column.saturating_sub(1)) as u32,
44
44
  };
45
45
 
46
46
  let severity = match warning.severity {
@@ -48,6 +48,15 @@ pub fn warning_to_diagnostic(warning: &crate::rule::LintWarning) -> Diagnostic {
48
48
  crate::rule::Severity::Warning => DiagnosticSeverity::WARNING,
49
49
  };
50
50
 
51
+ // Create clickable link to rule documentation
52
+ let code_description = warning.rule_name.as_ref().and_then(|rule_name| {
53
+ // Create a link to the rule documentation
54
+ Url::parse(&format!(
55
+ "https://github.com/rvben/rumdl/blob/main/docs/{}.md",
56
+ rule_name.to_lowercase()
57
+ )).ok().map(|href| CodeDescription { href })
58
+ });
59
+
51
60
  Diagnostic {
52
61
  range: Range {
53
62
  start: start_position,
@@ -59,7 +68,7 @@ pub fn warning_to_diagnostic(warning: &crate::rule::LintWarning) -> Diagnostic {
59
68
  message: warning.message.clone(),
60
69
  related_information: None,
61
70
  tags: None,
62
- code_description: None,
71
+ code_description,
63
72
  data: None,
64
73
  }
65
74
  }
@@ -6,7 +6,7 @@ use rayon::prelude::*;
6
6
  use std::collections::HashSet;
7
7
  use std::error::Error;
8
8
  use std::fs;
9
- use std::io::{self, Write};
9
+ use std::io::{self, Write, Read};
10
10
  use std::path::Path;
11
11
  use std::process;
12
12
  use std::sync::Arc;
@@ -220,6 +220,10 @@ struct CheckArgs {
220
220
  /// Output format: text (default) or json
221
221
  #[arg(long, short = 'o', default_value = "text")]
222
222
  output: String,
223
+
224
+ /// Read from stdin instead of files
225
+ #[arg(long, help = "Read from stdin instead of files")]
226
+ stdin: bool,
223
227
  }
224
228
 
225
229
  // Get a complete set of enabled rules based on CLI options and config
@@ -1204,6 +1208,7 @@ build-backend = \"setuptools.build_meta\"
1204
1208
  profile: cli.profile,
1205
1209
  quiet: cli.quiet,
1206
1210
  output: "text".to_string(),
1211
+ stdin: false,
1207
1212
  };
1208
1213
  eprintln!("{}: Deprecation warning: Running 'rumdl .' or 'rumdl [PATHS...]' without a subcommand is deprecated and will be removed in a future release. Please use 'rumdl check .' instead.", "[rumdl]".yellow().bold());
1209
1214
  run_check(&args, cli.config.as_deref(), cli.no_config);
@@ -1225,6 +1230,85 @@ build-backend = \"setuptools.build_meta\"
1225
1230
  }
1226
1231
  }
1227
1232
 
1233
+ /// Process markdown content from stdin
1234
+ fn process_stdin(rules: &[Box<dyn Rule>], args: &CheckArgs) {
1235
+ // Read all content from stdin
1236
+ let mut content = String::new();
1237
+ if let Err(e) = io::stdin().read_to_string(&mut content) {
1238
+ if !args.quiet {
1239
+ eprintln!("Error reading from stdin: {}", e);
1240
+ }
1241
+ process::exit(1);
1242
+ }
1243
+
1244
+ // Create a lint context for the stdin content
1245
+ let ctx = LintContext::new(&content);
1246
+ let mut all_warnings = Vec::new();
1247
+
1248
+ // Run all enabled rules on the content
1249
+ for rule in rules {
1250
+ match rule.check(&ctx) {
1251
+ Ok(mut warnings) => {
1252
+ // Set file path to "<stdin>" for all warnings
1253
+ for warning in &mut warnings {
1254
+ // The warnings already have the correct character ranges
1255
+ }
1256
+ all_warnings.extend(warnings);
1257
+ }
1258
+ Err(e) => {
1259
+ if !args.quiet {
1260
+ eprintln!("Error running rule {}: {}", rule.name(), e);
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+
1266
+ // Output results
1267
+ if args.output == "json" {
1268
+ // For JSON output, modify warnings to show "<stdin>" as filename
1269
+ let mut json_warnings = Vec::new();
1270
+ for warning in all_warnings {
1271
+ let mut json_warning = serde_json::to_value(&warning).unwrap();
1272
+ if let Some(obj) = json_warning.as_object_mut() {
1273
+ obj.insert("file".to_string(), serde_json::Value::String("<stdin>".to_string()));
1274
+ }
1275
+ json_warnings.push(json_warning);
1276
+ }
1277
+ println!("{}", serde_json::to_string_pretty(&json_warnings).unwrap());
1278
+ } else {
1279
+ // Text output
1280
+ let has_issues = !all_warnings.is_empty();
1281
+ if has_issues {
1282
+ for warning in &all_warnings {
1283
+ println!(
1284
+ "<stdin>:{}:{}: {}: {} [{}]",
1285
+ warning.line,
1286
+ warning.column,
1287
+ match warning.severity {
1288
+ rumdl::rule::Severity::Error => "error".red(),
1289
+ rumdl::rule::Severity::Warning => "warning".yellow(),
1290
+ },
1291
+ warning.message,
1292
+ warning.rule_name.as_deref().unwrap_or("unknown")
1293
+ );
1294
+ }
1295
+ }
1296
+
1297
+ if !args.quiet {
1298
+ if has_issues {
1299
+ println!("\nFound {} issue(s) in stdin", all_warnings.len());
1300
+ } else {
1301
+ println!("No issues found in stdin");
1302
+ }
1303
+ }
1304
+
1305
+ // Exit with error code if issues found
1306
+ if has_issues {
1307
+ process::exit(1);
1308
+ }
1309
+ }
1310
+ }
1311
+
1228
1312
  fn run_check(args: &CheckArgs, global_config_path: Option<&str>, no_config: bool) {
1229
1313
  // 1. Load sourced config (for provenance and validation)
1230
1314
  let sourced = match rumdl_config::SourcedConfig::load_with_discovery(
@@ -1257,6 +1341,12 @@ fn run_check(args: &CheckArgs, global_config_path: Option<&str>, no_config: bool
1257
1341
  // Initialize rules with configuration
1258
1342
  let enabled_rules = get_enabled_rules_from_checkargs(args, &config);
1259
1343
 
1344
+ // Handle stdin input
1345
+ if args.stdin {
1346
+ process_stdin(&enabled_rules, args);
1347
+ return;
1348
+ }
1349
+
1260
1350
  // Find all markdown files to check
1261
1351
  let file_paths = match find_markdown_files(&args.paths, args, &config) {
1262
1352
  Ok(paths) => paths,
@@ -43,8 +43,10 @@ pub type LintResult = Result<Vec<LintWarning>, LintError>;
43
43
  #[derive(Debug, PartialEq, Clone, Serialize)]
44
44
  pub struct LintWarning {
45
45
  pub message: String,
46
- pub line: usize,
47
- pub column: usize,
46
+ pub line: usize, // 1-indexed start line
47
+ pub column: usize, // 1-indexed start column
48
+ pub end_line: usize, // 1-indexed end line
49
+ pub end_column: usize, // 1-indexed end column
48
50
  pub severity: Severity,
49
51
  pub fix: Option<Fix>,
50
52
  pub rule_name: Option<&'static str>,
@@ -1,7 +1,7 @@
1
1
  use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
2
2
  use crate::rules::heading_utils::HeadingUtils;
3
3
  use crate::utils::document_structure::DocumentStructure;
4
- use crate::utils::range_utils::LineIndex;
4
+ use crate::utils::range_utils::{LineIndex, calculate_heading_range};
5
5
  use crate::HeadingStyle;
6
6
 
7
7
  /// Rule MD001: Heading levels should only increment by one level at a time
@@ -147,10 +147,23 @@ impl Rule for MD001HeadingIncrement {
147
147
  let replacement =
148
148
  HeadingUtils::convert_heading_style(&heading_text, fixed_level as u32, style);
149
149
 
150
+ // Calculate precise range: highlight the entire heading
151
+ let line_content = if adjusted_line_num < lines.len() {
152
+ lines[adjusted_line_num]
153
+ } else {
154
+ ""
155
+ };
156
+ let (start_line, start_col, end_line, end_col) = calculate_heading_range(
157
+ line_num,
158
+ line_content
159
+ );
160
+
150
161
  warnings.push(LintWarning {
151
162
  rule_name: Some(self.name()),
152
- line: line_num,
153
- column: indentation + 1,
163
+ line: start_line,
164
+ column: start_col,
165
+ end_line: end_line,
166
+ end_column: end_col,
154
167
  message: format!("Heading level should be {} for this level", prev_level + 1),
155
168
  severity: Severity::Warning,
156
169
  fix: Some(Fix {
@@ -2,6 +2,7 @@ use crate::rule::Rule;
2
2
  use crate::rule::{Fix, LintError, LintResult, LintWarning, RuleCategory, Severity};
3
3
  use crate::rules::heading_utils::HeadingStyle;
4
4
  use crate::utils::document_structure::DocumentStructure;
5
+ use crate::utils::range_utils::calculate_heading_range;
5
6
  use lazy_static::lazy_static;
6
7
  use regex::Regex;
7
8
  use toml;
@@ -238,10 +239,25 @@ impl Rule for MD002FirstHeadingH1 {
238
239
  }
239
240
  },
240
241
  );
242
+
243
+ // Calculate precise range: highlight the entire first heading
244
+ let lines: Vec<&str> = content.lines().collect();
245
+ let line_content = if first_heading_line > 0 && first_heading_line <= lines.len() {
246
+ lines[first_heading_line - 1]
247
+ } else {
248
+ ""
249
+ };
250
+ let (start_line, start_col, end_line, end_col) = calculate_heading_range(
251
+ first_heading_line,
252
+ line_content
253
+ );
254
+
241
255
  result.push(LintWarning {
242
256
  message,
243
- line: first_heading_line,
244
- column: 1,
257
+ line: start_line,
258
+ column: start_col,
259
+ end_line: end_line,
260
+ end_column: end_col,
245
261
  severity: Severity::Warning,
246
262
  fix,
247
263
  rule_name: Some(self.name()),
@@ -4,9 +4,9 @@
4
4
  //! See [docs/md003.md](../../docs/md003.md) for full documentation, configuration, and examples.
5
5
 
6
6
  use crate::rule::{LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
7
- use crate::rules::heading_utils::{HeadingStyle, HeadingUtils};
7
+ use crate::rules::heading_utils::HeadingStyle;
8
8
  use crate::utils::document_structure::DocumentStructure;
9
- use crate::utils::markdown_elements::{ElementQuality, ElementType, MarkdownElements};
9
+ use crate::utils::range_utils::calculate_heading_range;
10
10
  use lazy_static::lazy_static;
11
11
  use regex::Regex;
12
12
  use std::str::FromStr;
@@ -166,143 +166,29 @@ impl Rule for MD003HeadingStyle {
166
166
  }
167
167
 
168
168
  fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
169
- let content = ctx.content;
170
- // Early return for empty content
171
- if content.is_empty() {
172
- return Ok(String::new());
173
- }
174
-
175
- // Quick check if there are any headings at all
176
- if !QUICK_HEADING_CHECK.is_match(content) {
177
- return Ok(content.to_string());
178
- }
179
-
180
- let mut fixed_content = String::new();
181
- let mut last_processed_line = 0;
182
- let lines: Vec<&str> = content.lines().collect();
183
-
184
- // Get the target style - use the fallback method since no structure is available
185
- let target_style = self.get_target_style(content, None);
186
-
187
- // Get all headings using the MarkdownElements utility
188
- let headings = MarkdownElements::detect_headings(content);
189
-
190
- for heading in headings {
191
- if heading.element_type != ElementType::Heading
192
- || heading.quality != ElementQuality::Valid
193
- {
194
- continue; // Skip non-headings or invalid headings
195
- }
196
-
197
- // Add any lines before this heading
198
- for i in last_processed_line..heading.start_line {
199
- if !fixed_content.is_empty() {
200
- fixed_content.push('\n');
201
- }
202
- fixed_content.push_str(lines.get(i).unwrap_or(&""));
203
- }
204
-
205
- // Get the heading level
206
- if let Some(level_str) = &heading.metadata {
207
- if let Ok(level) = level_str.parse::<u32>() {
208
- // Determine the current style of the heading
209
- let style = if heading.end_line > heading.start_line {
210
- // Setext heading (has an underline)
211
- if level == 1 {
212
- HeadingStyle::Setext1
213
- } else {
214
- HeadingStyle::Setext2
215
- }
216
- } else {
217
- // ATX heading
218
- let line = lines.get(heading.start_line).map_or("", |v| *v);
219
- if line.trim().ends_with('#') {
220
- HeadingStyle::AtxClosed
221
- } else {
222
- HeadingStyle::Atx
223
- }
224
- };
225
-
226
- // For markdownlint parity: when target style is Setext, all headings are expected to be Setext
227
- // For level 3+, we can't actually convert to Setext, so leave as ATX but flag as violation
228
- let expected_style = if target_style == HeadingStyle::Setext1
229
- || target_style == HeadingStyle::Setext2
230
- {
231
- if level > 2 {
232
- // Level 3+ can't be Setext, so keep as ATX but this will be flagged as violation
233
- HeadingStyle::Atx
234
- } else if level == 1 {
235
- HeadingStyle::Setext1
236
- } else {
237
- HeadingStyle::Setext2
238
- }
239
- } else {
240
- target_style
241
- };
242
-
243
- // If this heading's style doesn't match the target, convert it
244
- if style != expected_style {
245
- // Get the text content from the heading
246
- let text_content = if heading.end_line > heading.start_line {
247
- // Setext heading
248
- lines.get(heading.start_line).unwrap_or(&"").to_string()
249
- } else {
250
- // ATX heading
251
- let line = lines.get(heading.start_line).map_or("", |v| *v);
252
- HeadingUtils::get_heading_text(line).unwrap_or_default()
253
- };
254
-
255
- // Get indentation
256
- let indentation = if let Some(line) = lines.get(heading.start_line) {
257
- line.chars()
258
- .take_while(|c| c.is_whitespace())
259
- .collect::<String>()
260
- } else {
261
- String::new()
262
- };
263
-
264
- // Convert heading to target style
265
- let converted_heading = HeadingUtils::convert_heading_style(
266
- &format!("{}{}", indentation, text_content),
267
- level,
268
- expected_style,
269
- );
270
-
271
- // Add converted heading
272
- if !fixed_content.is_empty() {
273
- fixed_content.push('\n');
274
- }
275
- fixed_content.push_str(&converted_heading);
276
- } else {
277
- // Add original heading lines
278
- for i in heading.start_line..=heading.end_line {
279
- if !fixed_content.is_empty() {
280
- fixed_content.push('\n');
281
- }
282
- fixed_content.push_str(lines.get(i).unwrap_or(&""));
283
- }
284
- }
169
+ // Get all warnings with their fixes
170
+ let warnings = self.check(ctx)?;
285
171
 
286
- // Update last processed line
287
- last_processed_line = heading.end_line + 1;
288
- }
289
- }
172
+ // If no warnings, return original content
173
+ if warnings.is_empty() {
174
+ return Ok(ctx.content.to_string());
290
175
  }
291
176
 
292
- // Add any remaining lines
293
- for i in last_processed_line..lines.len() {
294
- if !fixed_content.is_empty() {
295
- fixed_content.push('\n');
177
+ // Collect all fixes and sort by range start (descending) to apply from end to beginning
178
+ let mut fixes: Vec<_> = warnings.iter()
179
+ .filter_map(|w| w.fix.as_ref().map(|f| (f.range.start, f.range.end, &f.replacement)))
180
+ .collect();
181
+ fixes.sort_by(|a, b| b.0.cmp(&a.0));
182
+
183
+ // Apply fixes from end to beginning to preserve byte offsets
184
+ let mut result = ctx.content.to_string();
185
+ for (start, end, replacement) in fixes {
186
+ if start < result.len() && end <= result.len() && start <= end {
187
+ result.replace_range(start..end, replacement);
296
188
  }
297
- fixed_content.push_str(lines.get(i).unwrap_or(&""));
298
- }
299
-
300
- // Preserve trailing newline
301
- if content.ends_with('\n') && !fixed_content.ends_with('\n') {
302
- fixed_content.push('\n');
303
189
  }
304
190
 
305
- Ok(fixed_content)
191
+ Ok(result)
306
192
  }
307
193
 
308
194
  fn check_with_structure(
@@ -383,16 +269,88 @@ impl Rule for MD003HeadingStyle {
383
269
  };
384
270
 
385
271
  if style != expected_style {
272
+ // Generate fix for this heading
273
+ let fix = {
274
+ use crate::rules::heading_utils::HeadingUtils;
275
+
276
+ // Get the text content from the heading
277
+ let text_content = if next_line_idx < lines.len() &&
278
+ (lines[next_line_idx].trim_start().starts_with('=') ||
279
+ lines[next_line_idx].trim_start().starts_with('-')) {
280
+ // Setext heading
281
+ current_line.to_string()
282
+ } else {
283
+ // ATX heading
284
+ HeadingUtils::get_heading_text(current_line).unwrap_or_default()
285
+ };
286
+
287
+ // Get indentation
288
+ let indentation = current_line.chars()
289
+ .take_while(|c| c.is_whitespace())
290
+ .collect::<String>();
291
+
292
+ // Convert heading to target style
293
+ let converted_heading = HeadingUtils::convert_heading_style(
294
+ &format!("{}{}", indentation, text_content.trim()),
295
+ level as u32,
296
+ expected_style,
297
+ );
298
+
299
+ // Calculate the correct range for the heading
300
+ let line_index = crate::utils::range_utils::LineIndex::new(ctx.content.to_string());
301
+ let range = if next_line_idx < lines.len() &&
302
+ (lines[next_line_idx].trim_start().starts_with('=') ||
303
+ lines[next_line_idx].trim_start().starts_with('-')) {
304
+ // Setext heading spans two lines
305
+ let start_byte = line_index.line_col_to_byte_range(line_num, 1).start;
306
+ let end_byte = if line_num + 1 < lines.len() {
307
+ line_index.line_col_to_byte_range(line_num + 2, 1).start - 1
308
+ } else {
309
+ ctx.content.len()
310
+ };
311
+ start_byte..end_byte
312
+ } else {
313
+ // ATX heading is single line
314
+ let start_byte = line_index.line_col_to_byte_range(line_num, 1).start;
315
+ let end_byte = if line_num < lines.len() {
316
+ line_index.line_col_to_byte_range(line_num + 1, 1).start - 1
317
+ } else {
318
+ ctx.content.len()
319
+ };
320
+ start_byte..end_byte
321
+ };
322
+
323
+ Some(crate::rule::Fix {
324
+ range,
325
+ replacement: converted_heading,
326
+ })
327
+ };
328
+
329
+ // Calculate precise character range for the heading marker
330
+ let (start_line, start_col, end_line, end_col) = if style == HeadingStyle::Setext1 || style == HeadingStyle::Setext2 {
331
+ // For Setext headings, highlight the underline
332
+ if next_line_idx < lines.len() {
333
+ calculate_heading_range(line_num + 1, lines[next_line_idx])
334
+ } else {
335
+ calculate_heading_range(line_num, current_line)
336
+ }
337
+ } else {
338
+ // For ATX headings, highlight the hash markers
339
+ calculate_heading_range(line_num, current_line)
340
+ };
341
+
386
342
  result.push(LintWarning {
387
- rule_name: Some(self.name()),
388
- line: line_num, // Already 1-indexed
389
- column: 1,
390
- message: format!(
391
- "Heading style should be {:?}, found {:?}",
343
+ rule_name: Some(self.name()),
344
+ line: start_line,
345
+ column: start_col,
346
+ end_line: end_line,
347
+ end_column: end_col,
348
+ message: format!(
349
+ "Heading style should be {:?}, found {:?}",
392
350
  expected_style, style
393
351
  ),
394
352
  severity: Severity::Warning,
395
- fix: None,
353
+ fix,
396
354
  });
397
355
  }
398
356
  }
@@ -168,9 +168,11 @@ impl Rule for MD004UnorderedListStyle {
168
168
  if marker != first {
169
169
  let (line, col) = ctx.offset_to_line_col(offset);
170
170
  warnings.push(LintWarning {
171
- line,
172
- column: col,
173
- message: format!("marker '{}' does not match expected style '{}'", marker, first),
171
+ line,
172
+ column: col,
173
+ end_line: line,
174
+ end_column: col + 1,
175
+ message: format!("marker '{}' does not match expected style '{}'", marker, first),
174
176
  severity: Severity::Warning,
175
177
  rule_name: Some(self.name()),
176
178
  fix: Some(Fix {
@@ -195,9 +197,11 @@ impl Rule for MD004UnorderedListStyle {
195
197
  if marker != target_marker {
196
198
  let (line, col) = ctx.offset_to_line_col(offset);
197
199
  warnings.push(LintWarning {
198
- line,
199
- column: col,
200
- message: format!("marker '{}' does not match expected style '{}'", marker, target_marker),
200
+ line,
201
+ column: col,
202
+ end_line: line,
203
+ end_column: col + 1,
204
+ message: format!("marker '{}' does not match expected style '{}'", marker, target_marker),
201
205
  severity: Severity::Warning,
202
206
  rule_name: Some(self.name()),
203
207
  fix: Some(Fix {