rumdl 0.0.75__tar.gz → 0.0.77__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 (264) hide show
  1. {rumdl-0.0.75 → rumdl-0.0.77}/Cargo.lock +13 -19
  2. {rumdl-0.0.75 → rumdl-0.0.77}/Cargo.toml +21 -1
  3. {rumdl-0.0.75 → rumdl-0.0.77}/PKG-INFO +1 -1
  4. rumdl-0.0.77/src/rules/md005_list_indent.rs +451 -0
  5. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md006_start_bullets.rs +19 -14
  6. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md007_ul_indent.rs +34 -52
  7. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md011_no_reversed_links.rs +6 -0
  8. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md019_no_multiple_space_atx.rs +8 -5
  9. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md021_no_multiple_space_closed_atx.rs +12 -34
  10. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md023_heading_start_left.rs +24 -107
  11. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md026_no_trailing_punctuation.rs +113 -139
  12. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md027_multiple_spaces_blockquote.rs +6 -3
  13. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md028_no_blanks_blockquote.rs +6 -0
  14. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md030_list_marker_space.rs +13 -25
  15. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md031_blanks_around_fences.rs +6 -0
  16. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md037_spaces_around_emphasis.rs +146 -152
  17. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md039_no_space_in_links.rs +170 -150
  18. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md051_link_fragments.rs +122 -101
  19. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/additional_tests.rs +2 -2
  20. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/basic_tests.rs +1 -1
  21. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/extended_tests.rs +4 -4
  22. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md007_test.rs +3 -3
  23. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md019_test.rs +1 -1
  24. rumdl-0.0.77/tests/vscode_extension_fixes.rs +414 -0
  25. rumdl-0.0.75/src/rules/md005_list_indent.rs +0 -844
  26. {rumdl-0.0.75 → rumdl-0.0.77}/.rumdl.toml +0 -0
  27. {rumdl-0.0.75 → rumdl-0.0.77}/MANIFEST.in +0 -0
  28. {rumdl-0.0.75 → rumdl-0.0.77}/Makefile +0 -0
  29. {rumdl-0.0.75 → rumdl-0.0.77}/README.md +0 -0
  30. {rumdl-0.0.75 → rumdl-0.0.77}/assets/logo.png +0 -0
  31. {rumdl-0.0.75 → rumdl-0.0.77}/benches/fix_performance.rs +0 -0
  32. {rumdl-0.0.75 → rumdl-0.0.77}/benches/range_performance.rs +0 -0
  33. {rumdl-0.0.75 → rumdl-0.0.77}/benches/range_utils_benchmark.rs +0 -0
  34. {rumdl-0.0.75 → rumdl-0.0.77}/benches/rule_performance.rs +0 -0
  35. {rumdl-0.0.75 → rumdl-0.0.77}/benches/simple_fix_bench.rs +0 -0
  36. {rumdl-0.0.75 → rumdl-0.0.77}/docs/RULES.md +0 -0
  37. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md001.md +0 -0
  38. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md002.md +0 -0
  39. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md003.md +0 -0
  40. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md004.md +0 -0
  41. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md005.md +0 -0
  42. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md006.md +0 -0
  43. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md007.md +0 -0
  44. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md009.md +0 -0
  45. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md010.md +0 -0
  46. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md011.md +0 -0
  47. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md012.md +0 -0
  48. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md013.md +0 -0
  49. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md014.md +0 -0
  50. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md018.md +0 -0
  51. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md019.md +0 -0
  52. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md020.md +0 -0
  53. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md021.md +0 -0
  54. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md022.md +0 -0
  55. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md023.md +0 -0
  56. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md024.md +0 -0
  57. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md025.md +0 -0
  58. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md026.md +0 -0
  59. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md027.md +0 -0
  60. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md028.md +0 -0
  61. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md029.md +0 -0
  62. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md030.md +0 -0
  63. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md031.md +0 -0
  64. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md032.md +0 -0
  65. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md033.md +0 -0
  66. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md034.md +0 -0
  67. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md035.md +0 -0
  68. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md036.md +0 -0
  69. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md037.md +0 -0
  70. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md038.md +0 -0
  71. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md039.md +0 -0
  72. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md040.md +0 -0
  73. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md041.md +0 -0
  74. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md042.md +0 -0
  75. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md043.md +0 -0
  76. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md044.md +0 -0
  77. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md045.md +0 -0
  78. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md046.md +0 -0
  79. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md047.md +0 -0
  80. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md048.md +0 -0
  81. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md049.md +0 -0
  82. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md050.md +0 -0
  83. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md051.md +0 -0
  84. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md052.md +0 -0
  85. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md053.md +0 -0
  86. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md054.md +0 -0
  87. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md055.md +0 -0
  88. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md056.md +0 -0
  89. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md057.md +0 -0
  90. {rumdl-0.0.75 → rumdl-0.0.77}/docs/md058.md +0 -0
  91. {rumdl-0.0.75 → rumdl-0.0.77}/issues/plan-rule-parity-with-markdownlint.md +0 -0
  92. {rumdl-0.0.75 → rumdl-0.0.77}/parity_check.py +0 -0
  93. {rumdl-0.0.75 → rumdl-0.0.77}/pyproject.toml +0 -0
  94. {rumdl-0.0.75 → rumdl-0.0.77}/python/MANIFEST.in +0 -0
  95. {rumdl-0.0.75 → rumdl-0.0.77}/python/PYTHON-README.md +0 -0
  96. {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/__init__.py +0 -0
  97. {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/__main__.py +0 -0
  98. {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/py.typed +0 -0
  99. {rumdl-0.0.75 → rumdl-0.0.77}/rumdl.toml.example +0 -0
  100. {rumdl-0.0.75 → rumdl-0.0.77}/src/config.rs +0 -0
  101. {rumdl-0.0.75 → rumdl-0.0.77}/src/init.rs +0 -0
  102. {rumdl-0.0.75 → rumdl-0.0.77}/src/lib.rs +0 -0
  103. {rumdl-0.0.75 → rumdl-0.0.77}/src/lint_context.rs +0 -0
  104. {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/mod.rs +0 -0
  105. {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/server.rs +0 -0
  106. {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/types.rs +0 -0
  107. {rumdl-0.0.75 → rumdl-0.0.77}/src/main.rs +0 -0
  108. {rumdl-0.0.75 → rumdl-0.0.77}/src/markdownlint_config.rs +0 -0
  109. {rumdl-0.0.75 → rumdl-0.0.77}/src/parallel.rs +0 -0
  110. {rumdl-0.0.75 → rumdl-0.0.77}/src/performance.rs +0 -0
  111. {rumdl-0.0.75 → rumdl-0.0.77}/src/profiling.rs +0 -0
  112. {rumdl-0.0.75 → rumdl-0.0.77}/src/python.rs +0 -0
  113. {rumdl-0.0.75 → rumdl-0.0.77}/src/rule.rs +0 -0
  114. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/blockquote_utils.rs +0 -0
  115. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/code_block_utils.rs +0 -0
  116. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/code_fence_utils.rs +0 -0
  117. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/emphasis_style.rs +0 -0
  118. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/front_matter_utils.rs +0 -0
  119. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/heading_utils.rs +0 -0
  120. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/list_utils.rs +0 -0
  121. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md001_heading_increment.rs +0 -0
  122. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md002_first_heading_h1.rs +0 -0
  123. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md003_heading_style.rs +0 -0
  124. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md004_unordered_list_style.rs +0 -0
  125. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md009_trailing_spaces.rs +0 -0
  126. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md010_no_hard_tabs.rs +0 -0
  127. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md012_no_multiple_blanks.rs +0 -0
  128. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md013_line_length.rs +0 -0
  129. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md014_commands_show_output.rs +0 -0
  130. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md018_no_missing_space_atx.rs +0 -0
  131. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md020_no_missing_space_closed_atx.rs +0 -0
  132. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md022_blanks_around_headings.rs +0 -0
  133. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md024_no_duplicate_heading.rs +0 -0
  134. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md025_single_title.rs +0 -0
  135. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md029_ordered_list_prefix.rs +0 -0
  136. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md032_blanks_around_lists.rs +0 -0
  137. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md033_no_inline_html.rs +0 -0
  138. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md034_no_bare_urls.rs +0 -0
  139. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md035_hr_style.rs +0 -0
  140. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md036_no_emphasis_only_first.rs +0 -0
  141. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md038_no_space_in_code.rs +0 -0
  142. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md040_fenced_code_language.rs +0 -0
  143. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md041_first_line_heading.rs +0 -0
  144. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md042_no_empty_links.rs +0 -0
  145. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md043_required_headings.rs +0 -0
  146. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md044_proper_names.rs +0 -0
  147. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md045_no_alt_text.rs +0 -0
  148. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md046_code_block_style.rs +0 -0
  149. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md047_single_trailing_newline.rs +0 -0
  150. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md048_code_fence_style.rs +0 -0
  151. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md049_emphasis_style.rs +0 -0
  152. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md050_strong_style.rs +0 -0
  153. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md052_reference_links_images.rs +0 -0
  154. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md053_link_image_reference_definitions.rs +0 -0
  155. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md054_link_image_style.rs +0 -0
  156. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md055_table_pipe_style.rs +0 -0
  157. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md056_table_column_count.rs +0 -0
  158. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md057_existing_relative_links.rs +0 -0
  159. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md058_blanks_around_tables.rs +0 -0
  160. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/mod.rs +0 -0
  161. {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/strong_style.rs +0 -0
  162. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/ast_utils.rs +0 -0
  163. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/code_block_utils.rs +0 -0
  164. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/document_structure.rs +0 -0
  165. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/early_returns.rs +0 -0
  166. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/element_cache.rs +0 -0
  167. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/markdown_elements.rs +0 -0
  168. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/mod.rs +0 -0
  169. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/range_utils.rs +0 -0
  170. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/regex_cache.rs +0 -0
  171. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/string_interner.rs +0 -0
  172. {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/table_utils.rs +0 -0
  173. {rumdl-0.0.75 → rumdl-0.0.77}/tests/advanced_integration_tests.rs +0 -0
  174. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/comprehensive_tests.rs +0 -0
  175. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/mod.rs +0 -0
  176. {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/unicode_utils.rs +0 -0
  177. {rumdl-0.0.75 → rumdl-0.0.77}/tests/cli_duplication_test.rs +0 -0
  178. {rumdl-0.0.75 → rumdl-0.0.77}/tests/cli_integration_tests.rs +0 -0
  179. {rumdl-0.0.75 → rumdl-0.0.77}/tests/commonmark_compliance_tests.rs +0 -0
  180. {rumdl-0.0.75 → rumdl-0.0.77}/tests/comprehensive_integration_tests.rs +0 -0
  181. {rumdl-0.0.75 → rumdl-0.0.77}/tests/config_application_tests.rs +0 -0
  182. {rumdl-0.0.75 → rumdl-0.0.77}/tests/config_tests.rs +0 -0
  183. {rumdl-0.0.75 → rumdl-0.0.77}/tests/init_command_test.rs +0 -0
  184. {rumdl-0.0.75 → rumdl-0.0.77}/tests/init_tests.rs +0 -0
  185. {rumdl-0.0.75 → rumdl-0.0.77}/tests/integration_tests.rs +0 -0
  186. {rumdl-0.0.75 → rumdl-0.0.77}/tests/json_output_test.rs +0 -0
  187. {rumdl-0.0.75 → rumdl-0.0.77}/tests/lib.rs +0 -0
  188. {rumdl-0.0.75 → rumdl-0.0.77}/tests/lsp_integration_tests.rs +0 -0
  189. {rumdl-0.0.75 → rumdl-0.0.77}/tests/lsp_tests.rs +0 -0
  190. {rumdl-0.0.75 → rumdl-0.0.77}/tests/markdownlint_cli_integration.rs +0 -0
  191. {rumdl-0.0.75 → rumdl-0.0.77}/tests/markdownlint_config_test.rs +0 -0
  192. {rumdl-0.0.75 → rumdl-0.0.77}/tests/md030_edge_cases.md +0 -0
  193. {rumdl-0.0.75 → rumdl-0.0.77}/tests/output_format_tests.rs +0 -0
  194. {rumdl-0.0.75 → rumdl-0.0.77}/tests/perf_check.rs +0 -0
  195. {rumdl-0.0.75 → rumdl-0.0.77}/tests/pyproject_config_tests.rs +0 -0
  196. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md001_test.rs +0 -0
  197. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md001_unicode_test.rs +0 -0
  198. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md002_test.rs +0 -0
  199. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md003_test.rs +0 -0
  200. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md004_test.rs +0 -0
  201. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md005_test.rs +0 -0
  202. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md006_test.rs +0 -0
  203. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md006_unicode_test.rs +0 -0
  204. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md009_test.rs +0 -0
  205. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md010_test.rs +0 -0
  206. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md011_test.rs +0 -0
  207. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md012_test.rs +0 -0
  208. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md013_test.rs +0 -0
  209. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md014_test.rs +0 -0
  210. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md018_test.rs +0 -0
  211. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md020_test.rs +0 -0
  212. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md021_test.rs +0 -0
  213. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md022_test.rs +0 -0
  214. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md023_extended_test.rs +0 -0
  215. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md023_test.rs +0 -0
  216. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md024_test.rs +0 -0
  217. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md025_test.rs +0 -0
  218. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md026_test.rs +0 -0
  219. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md027_test.rs +0 -0
  220. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md028_test.rs +0 -0
  221. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md029_test.rs +0 -0
  222. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md030_test.rs +0 -0
  223. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md031_test.rs +0 -0
  224. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md032_test.rs +0 -0
  225. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md033_extended_test.rs +0 -0
  226. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md033_test.rs +0 -0
  227. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md034_test.rs +0 -0
  228. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md035_test.rs +0 -0
  229. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md036_test.rs +0 -0
  230. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md037_test.rs +0 -0
  231. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md038_test.rs +0 -0
  232. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md039_test.rs +0 -0
  233. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md040_test.rs +0 -0
  234. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md041_test.rs +0 -0
  235. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md042_test.rs +0 -0
  236. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md043_test.rs +0 -0
  237. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md044_test.rs +0 -0
  238. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md045_test.rs +0 -0
  239. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md046_test.rs +0 -0
  240. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md047_test.rs +0 -0
  241. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md048_test.rs +0 -0
  242. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md049_test.rs +0 -0
  243. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md050_test.rs +0 -0
  244. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md051_test.rs +0 -0
  245. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md052_test.rs +0 -0
  246. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_additional_test.rs +0 -0
  247. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_proptest.rs +0 -0
  248. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_test.rs +0 -0
  249. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md054_test.rs +0 -0
  250. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md054_unicode_test.rs +0 -0
  251. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md055_test.rs +0 -0
  252. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md056_test.rs +0 -0
  253. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md057_test.rs +0 -0
  254. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md058_test.rs +0 -0
  255. {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/mod.rs +0 -0
  256. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/blockquote_utils_test.rs +0 -0
  257. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/code_block_utils_extended_test.rs +0 -0
  258. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/code_block_utils_test.rs +0 -0
  259. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/core_utils_test.rs +0 -0
  260. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/front_matter_utils_test.rs +0 -0
  261. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/line_index_test.rs +0 -0
  262. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/mod.rs +0 -0
  263. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils_markdown_edge_cases.rs +0 -0
  264. {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils_tests.rs +0 -0
@@ -244,9 +244,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
244
244
 
245
245
  [[package]]
246
246
  name = "cc"
247
- version = "1.2.24"
247
+ version = "1.2.25"
248
248
  source = "registry+https://github.com/rust-lang/crates.io-index"
249
- checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
249
+ checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
250
250
  dependencies = [
251
251
  "shlex",
252
252
  ]
@@ -869,12 +869,6 @@ version = "0.5.0"
869
869
  source = "registry+https://github.com/rust-lang/crates.io-index"
870
870
  checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
871
871
 
872
- [[package]]
873
- name = "hermit-abi"
874
- version = "0.3.9"
875
- source = "registry+https://github.com/rust-lang/crates.io-index"
876
- checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
877
-
878
872
  [[package]]
879
873
  name = "hermit-abi"
880
874
  version = "0.5.1"
@@ -1069,7 +1063,7 @@ version = "0.4.16"
1069
1063
  source = "registry+https://github.com/rust-lang/crates.io-index"
1070
1064
  checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
1071
1065
  dependencies = [
1072
- "hermit-abi 0.5.1",
1066
+ "hermit-abi",
1073
1067
  "libc",
1074
1068
  "windows-sys 0.59.0",
1075
1069
  ]
@@ -1175,9 +1169,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
1175
1169
 
1176
1170
  [[package]]
1177
1171
  name = "lock_api"
1178
- version = "0.4.12"
1172
+ version = "0.4.13"
1179
1173
  source = "registry+https://github.com/rust-lang/crates.io-index"
1180
- checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
1174
+ checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
1181
1175
  dependencies = [
1182
1176
  "autocfg",
1183
1177
  "scopeguard",
@@ -1272,11 +1266,11 @@ dependencies = [
1272
1266
 
1273
1267
  [[package]]
1274
1268
  name = "num_cpus"
1275
- version = "1.16.0"
1269
+ version = "1.17.0"
1276
1270
  source = "registry+https://github.com/rust-lang/crates.io-index"
1277
- checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
1271
+ checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
1278
1272
  dependencies = [
1279
- "hermit-abi 0.3.9",
1273
+ "hermit-abi",
1280
1274
  "libc",
1281
1275
  ]
1282
1276
 
@@ -1325,9 +1319,9 @@ dependencies = [
1325
1319
 
1326
1320
  [[package]]
1327
1321
  name = "parking_lot"
1328
- version = "0.12.3"
1322
+ version = "0.12.4"
1329
1323
  source = "registry+https://github.com/rust-lang/crates.io-index"
1330
- checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
1324
+ checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
1331
1325
  dependencies = [
1332
1326
  "lock_api",
1333
1327
  "parking_lot_core",
@@ -1335,9 +1329,9 @@ dependencies = [
1335
1329
 
1336
1330
  [[package]]
1337
1331
  name = "parking_lot_core"
1338
- version = "0.9.10"
1332
+ version = "0.9.11"
1339
1333
  source = "registry+https://github.com/rust-lang/crates.io-index"
1340
- checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
1334
+ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
1341
1335
  dependencies = [
1342
1336
  "cfg-if",
1343
1337
  "libc",
@@ -1789,7 +1783,7 @@ dependencies = [
1789
1783
 
1790
1784
  [[package]]
1791
1785
  name = "rumdl"
1792
- version = "0.0.75"
1786
+ version = "0.0.77"
1793
1787
  dependencies = [
1794
1788
  "anyhow",
1795
1789
  "assert_cmd",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rumdl"
3
- version = "0.0.75"
3
+ version = "0.0.77"
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>"]
@@ -21,6 +21,26 @@ crate-type = ["cdylib", "rlib"]
21
21
  name = "rumdl"
22
22
  path = "src/main.rs"
23
23
 
24
+ [[bench]]
25
+ name = "rule_performance"
26
+ harness = false
27
+
28
+ [[bench]]
29
+ name = "fix_performance"
30
+ harness = false
31
+
32
+ [[bench]]
33
+ name = "range_performance"
34
+ harness = false
35
+
36
+ [[bench]]
37
+ name = "simple_fix_bench"
38
+ harness = false
39
+
40
+ [[bench]]
41
+ name = "range_utils_benchmark"
42
+ harness = false
43
+
24
44
  [dependencies]
25
45
  clap = { version = "4.5", features = ["derive"] }
26
46
  colored = "3.0.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rumdl
3
- Version: 0.0.75
3
+ Version: 0.0.77
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -0,0 +1,451 @@
1
+ //!
2
+ //! Rule MD005: Inconsistent indentation for list items at the same level
3
+ //!
4
+ //! See [docs/md005.md](../../docs/md005.md) for full documentation, configuration, and examples.
5
+
6
+ use crate::utils::range_utils::{calculate_match_range, LineIndex};
7
+
8
+ use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
9
+ use crate::utils::document_structure::DocumentStructure;
10
+ use lazy_static::lazy_static;
11
+ use regex::Regex;
12
+ use std::collections::HashMap;
13
+ use toml;
14
+
15
+ lazy_static! {
16
+ static ref LIST_MARKER_REGEX: Regex = Regex::new(r"^\d+[.)]").unwrap();
17
+ // Pre-compiled pattern for quick list detection
18
+ static ref QUICK_LIST_CHECK: Regex = Regex::new(r"^( *)([*+\-]|\d+[.)])").unwrap();
19
+ }
20
+
21
+ /// Rule MD005: Inconsistent indentation for list items at the same level
22
+ #[derive(Clone)]
23
+ pub struct MD005ListIndent;
24
+
25
+ impl MD005ListIndent {
26
+ #[inline]
27
+ fn get_list_marker_info(line: &str) -> Option<(usize, char, usize)> {
28
+ // Early return for empty or whitespace-only lines
29
+ if line.is_empty() || line.trim().is_empty() {
30
+ return None;
31
+ }
32
+
33
+ let indentation = line.len() - line.trim_start().len();
34
+ let trimmed = line.trim_start();
35
+
36
+ // Fast path check for unordered list markers
37
+ if !trimmed.is_empty() {
38
+ let first_char = trimmed.chars().next().unwrap();
39
+
40
+ // Check for unordered list markers (* - +)
41
+ if (first_char == '*' || first_char == '-' || first_char == '+')
42
+ && trimmed.len() > 1
43
+ && trimmed.chars().nth(1).map_or(false, |c| c.is_whitespace())
44
+ {
45
+ return Some((indentation, first_char, 1)); // 1 char marker
46
+ }
47
+
48
+ // Fast path check for ordered list markers (digits followed by . or ))
49
+ if first_char.is_ascii_digit() {
50
+ if let Some(marker_match) = LIST_MARKER_REGEX.find(trimmed) {
51
+ let marker_char = trimmed.chars().nth(marker_match.end() - 1).unwrap();
52
+ if trimmed.len() > marker_match.end()
53
+ && trimmed
54
+ .chars()
55
+ .nth(marker_match.end())
56
+ .map_or(false, |c| c.is_whitespace())
57
+ {
58
+ return Some((indentation, marker_char, marker_match.end()));
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ None
65
+ }
66
+
67
+ #[inline]
68
+ fn is_blank_line(line: &str) -> bool {
69
+ line.trim().is_empty()
70
+ }
71
+
72
+ // Determine the expected indentation for a list item at a specific level
73
+ #[inline]
74
+ fn get_expected_indent(level: usize) -> usize {
75
+ if level == 1 {
76
+ 0 // Top level items should be at the start of the line
77
+ } else {
78
+ 2 * (level - 1) // Nested items should be indented by 2 spaces per level
79
+ }
80
+ }
81
+
82
+ // Determine if a line is a continuation of a list item
83
+ #[inline]
84
+ fn is_list_continuation(prev_line: &str, current_line: &str) -> bool {
85
+ // Early return for empty lines
86
+ if current_line.trim().is_empty() {
87
+ return false;
88
+ }
89
+
90
+ // If the previous line is a list item and the current line has more indentation
91
+ // but is not a list item itself, it's a continuation
92
+ if let Some((prev_indent, _, _)) = Self::get_list_marker_info(prev_line) {
93
+ let current_indent = current_line.len() - current_line.trim_start().len();
94
+ return current_indent > prev_indent
95
+ && Self::get_list_marker_info(current_line).is_none();
96
+ }
97
+ false
98
+ }
99
+
100
+ /// Optimized check that combines all passes into one
101
+ fn check_optimized(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
102
+ let content = ctx.content;
103
+
104
+ // Early returns for common cases
105
+ if content.is_empty() {
106
+ return Ok(Vec::new());
107
+ }
108
+
109
+ // Quick check to avoid processing files without list markers
110
+ if !QUICK_LIST_CHECK.is_match(content) {
111
+ return Ok(Vec::new());
112
+ }
113
+
114
+ let line_index = LineIndex::new(content.to_string());
115
+ let lines: Vec<&str> = content.lines().collect();
116
+
117
+ // Early return if there are no lines
118
+ if lines.is_empty() {
119
+ return Ok(Vec::new());
120
+ }
121
+
122
+ let mut warnings = Vec::new();
123
+
124
+ // Pre-compute code blocks once
125
+ let mut in_code_blocks = vec![false; lines.len()];
126
+ let mut in_block = false;
127
+ for (i, line) in lines.iter().enumerate() {
128
+ let trimmed = line.trim();
129
+ if trimmed.starts_with("```") || trimmed.starts_with("~~~") {
130
+ in_block = !in_block;
131
+ }
132
+ in_code_blocks[i] = in_block;
133
+ }
134
+
135
+ // Single pass processing with efficient data structures
136
+ let mut list_items: Vec<(usize, usize, usize)> = Vec::new(); // (line_num, indent, list_id)
137
+ let mut current_list_id = 0;
138
+ let mut in_list = false;
139
+ let mut list_level_maps: HashMap<usize, HashMap<usize, usize>> = HashMap::new(); // list_id -> { indent -> level }
140
+ let mut level_indents: HashMap<(usize, usize), usize> = HashMap::new(); // (list_id, level) -> expected_indent
141
+
142
+ for (line_num, line) in lines.iter().enumerate() {
143
+ // Skip blank lines and code blocks
144
+ if Self::is_blank_line(line) || in_code_blocks[line_num] {
145
+ continue;
146
+ }
147
+
148
+ // Check if this is a list item
149
+ if let Some((indent, _marker, _)) = Self::get_list_marker_info(line) {
150
+ // Determine if this starts a new list
151
+ let is_new_list = !in_list
152
+ || indent == 0
153
+ || (list_items.last().map_or(false, |(_, prev_indent, _)| {
154
+ prev_indent > &0 && indent < prev_indent / 2
155
+ }));
156
+
157
+ if is_new_list {
158
+ current_list_id += 1;
159
+ in_list = true;
160
+ }
161
+
162
+ // Determine level for this item
163
+ let level_map = list_level_maps.entry(current_list_id).or_default();
164
+ let level = if level_map.is_empty() {
165
+ level_map.insert(indent, 1);
166
+ level_indents.insert((current_list_id, 1), indent);
167
+ 1
168
+ } else {
169
+ // Find appropriate level
170
+ if let Some(&existing_level) = level_map.get(&indent) {
171
+ existing_level
172
+ } else {
173
+ // Find parent level
174
+ let mut level = 1;
175
+ let mut parent_indent = 0;
176
+
177
+ for (&prev_indent, &prev_level) in level_map.iter() {
178
+ if prev_indent < indent && (prev_level >= level || prev_indent > parent_indent) {
179
+ level = prev_level + 1;
180
+ parent_indent = prev_indent;
181
+ }
182
+ }
183
+
184
+ level_map.insert(indent, level);
185
+ level
186
+ }
187
+ };
188
+
189
+ list_items.push((line_num, indent, current_list_id));
190
+
191
+ // Check indentation immediately
192
+ let expected_indent = Self::get_expected_indent(level);
193
+ if indent != expected_indent {
194
+ let inconsistent_message = format!(
195
+ "List item indentation is {} {}, expected {} for level {}",
196
+ indent,
197
+ if indent == 1 { "space" } else { "spaces" },
198
+ expected_indent,
199
+ level
200
+ );
201
+
202
+ let (start_line, start_col, end_line, end_col) = if indent > 0 {
203
+ calculate_match_range(line_num + 1, line, 0, indent)
204
+ } else {
205
+ calculate_match_range(line_num + 1, line, 0, 1)
206
+ };
207
+
208
+ // Fix range should span from start of line to end of indentation
209
+ let fix_range = if indent > 0 {
210
+ // Replace the current indentation with expected indentation
211
+ let start_byte = line_index.line_col_to_byte_range(line_num + 1, 1).start;
212
+ let end_byte = line_index.line_col_to_byte_range(line_num + 1, indent + 1).start;
213
+ start_byte..end_byte
214
+ } else {
215
+ // For no indentation, insert at start of line
216
+ let byte_pos = line_index.line_col_to_byte_range(line_num + 1, 1).start;
217
+ byte_pos..byte_pos
218
+ };
219
+
220
+ // Replacement should be just the corrected indentation
221
+ let replacement = if expected_indent > 0 {
222
+ " ".repeat(expected_indent)
223
+ } else {
224
+ String::new()
225
+ };
226
+
227
+ warnings.push(LintWarning {
228
+ rule_name: Some(self.name()),
229
+ line: start_line,
230
+ column: start_col,
231
+ end_line,
232
+ end_column: end_col,
233
+ message: inconsistent_message,
234
+ severity: Severity::Warning,
235
+ fix: Some(Fix {
236
+ range: fix_range,
237
+ replacement,
238
+ }),
239
+ });
240
+ }
241
+
242
+ // Track level consistency
243
+ let key = (current_list_id, level);
244
+ if let Some(reference_indent) = level_indents.get(&key) {
245
+ if indent != *reference_indent {
246
+ let inconsistent_message = format!(
247
+ "List item indentation is inconsistent with other items at the same level (found: {}, expected: {})",
248
+ indent, reference_indent
249
+ );
250
+
251
+ // Only add if we don't already have a warning for this line
252
+ if !warnings.iter().any(|w| w.line == line_num + 1) {
253
+ let (start_line, start_col, end_line, end_col) = if indent > 0 {
254
+ calculate_match_range(line_num + 1, line, 0, indent)
255
+ } else {
256
+ calculate_match_range(line_num + 1, line, 0, 1)
257
+ };
258
+
259
+ // Fix range should span from start of line to end of indentation
260
+ let fix_range = if indent > 0 {
261
+ // Replace the current indentation with expected indentation
262
+ let start_byte = line_index.line_col_to_byte_range(line_num + 1, 1).start;
263
+ let end_byte = line_index.line_col_to_byte_range(line_num + 1, indent + 1).start;
264
+ start_byte..end_byte
265
+ } else {
266
+ // For no indentation, insert at start of line
267
+ let byte_pos = line_index.line_col_to_byte_range(line_num + 1, 1).start;
268
+ byte_pos..byte_pos
269
+ };
270
+
271
+ // Replacement should be just the corrected indentation
272
+ let replacement = if *reference_indent > 0 {
273
+ " ".repeat(*reference_indent)
274
+ } else {
275
+ String::new()
276
+ };
277
+
278
+ warnings.push(LintWarning {
279
+ rule_name: Some(self.name()),
280
+ line: start_line,
281
+ column: start_col,
282
+ end_line,
283
+ end_column: end_col,
284
+ message: inconsistent_message,
285
+ severity: Severity::Warning,
286
+ fix: Some(Fix {
287
+ range: fix_range,
288
+ replacement,
289
+ }),
290
+ });
291
+ }
292
+ }
293
+ } else {
294
+ level_indents.insert(key, indent);
295
+ }
296
+
297
+ } else {
298
+ // Check if it's a list continuation
299
+ if list_items.is_empty() || !in_list {
300
+ continue;
301
+ }
302
+
303
+ let (prev_line_num, _, _) = list_items.last().unwrap();
304
+ if !Self::is_list_continuation(lines[*prev_line_num], line) {
305
+ in_list = false;
306
+ }
307
+ }
308
+ }
309
+
310
+ Ok(warnings)
311
+ }
312
+ }
313
+
314
+ impl Default for MD005ListIndent {
315
+ fn default() -> Self {
316
+ Self
317
+ }
318
+ }
319
+
320
+ impl Rule for MD005ListIndent {
321
+ fn name(&self) -> &'static str {
322
+ "MD005"
323
+ }
324
+
325
+ fn description(&self) -> &'static str {
326
+ "List indentation should be consistent"
327
+ }
328
+
329
+ fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
330
+ // Use optimized version
331
+ self.check_optimized(ctx)
332
+ }
333
+
334
+ fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
335
+ let warnings = self.check(ctx)?;
336
+ if warnings.is_empty() {
337
+ return Ok(ctx.content.to_string());
338
+ }
339
+
340
+ // Sort warnings by position (descending) to apply from end to start
341
+ let mut warnings_with_fixes: Vec<_> = warnings
342
+ .into_iter()
343
+ .filter_map(|w| {
344
+ if let Some(fix) = w.fix.clone() {
345
+ Some((w, fix))
346
+ } else {
347
+ None
348
+ }
349
+ })
350
+ .collect();
351
+ warnings_with_fixes.sort_by_key(|(_, fix)| std::cmp::Reverse(fix.range.start));
352
+
353
+ // Apply fixes to content
354
+ let mut content = ctx.content.to_string();
355
+ for (_, fix) in warnings_with_fixes {
356
+ if fix.range.start <= content.len() && fix.range.end <= content.len() {
357
+ content.replace_range(fix.range, &fix.replacement);
358
+ }
359
+ }
360
+
361
+ Ok(content)
362
+ }
363
+
364
+ fn category(&self) -> RuleCategory {
365
+ RuleCategory::List
366
+ }
367
+
368
+ /// Check if this rule should be skipped
369
+ fn should_skip(&self, ctx: &crate::lint_context::LintContext) -> bool {
370
+ let content = ctx.content;
371
+ content.is_empty() || !QUICK_LIST_CHECK.is_match(content)
372
+ }
373
+
374
+ /// Optimized check using document structure
375
+ fn check_with_structure(
376
+ &self,
377
+ ctx: &crate::lint_context::LintContext,
378
+ structure: &DocumentStructure,
379
+ ) -> LintResult {
380
+ // If no lists in structure, return early
381
+ if structure.list_lines.is_empty() {
382
+ return Ok(Vec::new());
383
+ }
384
+
385
+ // Use optimized check - it's already efficient enough
386
+ self.check_optimized(ctx)
387
+ }
388
+
389
+ fn as_any(&self) -> &dyn std::any::Any {
390
+ self
391
+ }
392
+
393
+ fn as_maybe_document_structure(&self) -> Option<&dyn crate::rule::MaybeDocumentStructure> {
394
+ Some(self)
395
+ }
396
+
397
+ fn default_config_section(&self) -> Option<(String, toml::Value)> {
398
+ None
399
+ }
400
+
401
+ fn from_config(_config: &crate::config::Config) -> Box<dyn Rule>
402
+ where
403
+ Self: Sized,
404
+ {
405
+ Box::new(MD005ListIndent)
406
+ }
407
+ }
408
+
409
+ impl crate::utils::document_structure::DocumentStructureExtensions for MD005ListIndent {
410
+ fn has_relevant_elements(
411
+ &self,
412
+ _ctx: &crate::lint_context::LintContext,
413
+ doc_structure: &crate::utils::document_structure::DocumentStructure,
414
+ ) -> bool {
415
+ !doc_structure.list_lines.is_empty()
416
+ }
417
+ }
418
+
419
+ #[cfg(test)]
420
+ mod tests {
421
+ use super::*;
422
+ use crate::lint_context::LintContext;
423
+
424
+ // ... existing tests ...
425
+
426
+ #[test]
427
+ fn test_with_document_structure() {
428
+ let rule = MD005ListIndent;
429
+
430
+ // Test with consistent list indentation
431
+ let content = "* Item 1\n* Item 2\n * Nested item\n * Another nested item";
432
+ let structure = DocumentStructure::new(content);
433
+ let ctx = LintContext::new(content);
434
+ let result = rule.check_with_structure(&ctx, &structure).unwrap();
435
+ assert!(result.is_empty());
436
+
437
+ // Test with inconsistent list indentation
438
+ let content = "* Item 1\n* Item 2\n * Nested item\n * Another nested item";
439
+ let structure = DocumentStructure::new(content);
440
+ let ctx = LintContext::new(content);
441
+ let result = rule.check_with_structure(&ctx, &structure).unwrap();
442
+ assert!(!result.is_empty()); // Should have at least one warning
443
+
444
+ // Test with different level indentation issues
445
+ let content = "* Item 1\n * Nested item\n * Another nested item with wrong indent";
446
+ let structure = DocumentStructure::new(content);
447
+ let ctx = LintContext::new(content);
448
+ let result = rule.check_with_structure(&ctx, &structure).unwrap();
449
+ assert!(!result.is_empty()); // Should have at least one warning
450
+ }
451
+ }
@@ -237,20 +237,21 @@ impl Rule for MD006StartBullets {
237
237
  valid_bullet_lines[line_idx] = is_valid;
238
238
 
239
239
  if !is_valid {
240
- let fixed_line = line.trim_start();
241
- let needs_blank_line = line_idx > 0
242
- && !Self::is_blank_line(lines[line_idx - 1])
243
- && Self::is_bullet_list_item(lines[line_idx - 1]).is_none();
244
- let replacement = if needs_blank_line {
245
- format!("\n{}", fixed_line)
240
+ // Calculate the precise range for the indentation that needs to be removed
241
+ // For " * Indented bullet", we want to highlight the indentation, marker, and space after marker " * " (columns 1-4)
242
+ let start_col = 1; // Start from beginning of line
243
+ let end_col = indent + 3; // Include marker and space after it (indent + 1 for marker + 1 for space + 1 for inclusive range)
244
+
245
+ // For the fix, we need to replace the highlighted part (" *") with just the bullet marker ("* ")
246
+ let line = lines[line_idx];
247
+ let trimmed = line.trim_start();
248
+ // Extract just the bullet marker and normalize to single space
249
+ let bullet_part = if let Some(captures) = BULLET_PATTERN.captures(trimmed) {
250
+ format!("{} ", captures.get(2).unwrap().as_str()) // Always use single space
246
251
  } else {
247
- fixed_line.to_string()
252
+ "* ".to_string() // fallback
248
253
  };
249
-
250
- // Calculate the range to highlight: from first indentation character to end of list marker
251
- let start_col = if indent > 0 { 2 } else { 1 }; // Start from first indentation space if indented
252
- let marker_pos = line.find(|c: char| c == '*' || c == '-' || c == '+').unwrap_or(0);
253
- let end_col = marker_pos + 3; // +1 for the marker itself, +1 for 1-based indexing, +1 for space after marker
254
+ let replacement = bullet_part;
254
255
 
255
256
  result.push(LintWarning {
256
257
  rule_name: Some(self.name()),
@@ -261,8 +262,12 @@ impl Rule for MD006StartBullets {
261
262
  end_column: end_col,
262
263
  message: "List item indentation".to_string(),
263
264
  fix: Some(Fix {
264
- range: line_index.line_col_to_byte_range(line_num, 1),
265
- replacement,
265
+ range: {
266
+ let start_byte = line_index.line_col_to_byte_range(line_num, start_col).start;
267
+ let end_byte = line_index.line_col_to_byte_range(line_num, end_col).start;
268
+ start_byte..end_byte
269
+ },
270
+ replacement,
266
271
  }),
267
272
  });
268
273
  }