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.
- {rumdl-0.0.75 → rumdl-0.0.77}/Cargo.lock +13 -19
- {rumdl-0.0.75 → rumdl-0.0.77}/Cargo.toml +21 -1
- {rumdl-0.0.75 → rumdl-0.0.77}/PKG-INFO +1 -1
- rumdl-0.0.77/src/rules/md005_list_indent.rs +451 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md006_start_bullets.rs +19 -14
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md007_ul_indent.rs +34 -52
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md011_no_reversed_links.rs +6 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md019_no_multiple_space_atx.rs +8 -5
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md021_no_multiple_space_closed_atx.rs +12 -34
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md023_heading_start_left.rs +24 -107
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md026_no_trailing_punctuation.rs +113 -139
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md027_multiple_spaces_blockquote.rs +6 -3
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md028_no_blanks_blockquote.rs +6 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md030_list_marker_space.rs +13 -25
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md031_blanks_around_fences.rs +6 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md037_spaces_around_emphasis.rs +146 -152
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md039_no_space_in_links.rs +170 -150
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md051_link_fragments.rs +122 -101
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/additional_tests.rs +2 -2
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/basic_tests.rs +1 -1
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/extended_tests.rs +4 -4
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md007_test.rs +3 -3
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md019_test.rs +1 -1
- rumdl-0.0.77/tests/vscode_extension_fixes.rs +414 -0
- rumdl-0.0.75/src/rules/md005_list_indent.rs +0 -844
- {rumdl-0.0.75 → rumdl-0.0.77}/.rumdl.toml +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/MANIFEST.in +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/Makefile +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/README.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/assets/logo.png +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/benches/fix_performance.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/benches/range_performance.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/benches/range_utils_benchmark.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/benches/rule_performance.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/benches/simple_fix_bench.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/RULES.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md001.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md002.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md003.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md004.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md005.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md006.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md007.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md009.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md010.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md011.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md012.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md013.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md014.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md018.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md019.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md020.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md021.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md022.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md023.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md024.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md025.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md026.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md027.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md028.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md029.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md030.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md031.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md032.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md033.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md034.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md035.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md036.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md037.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md038.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md039.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md040.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md041.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md042.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md043.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md044.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md045.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md046.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md047.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md048.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md049.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md050.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md051.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md052.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md053.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md054.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md055.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md056.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md057.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/docs/md058.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/issues/plan-rule-parity-with-markdownlint.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/parity_check.py +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/pyproject.toml +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/python/MANIFEST.in +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/python/PYTHON-README.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/__init__.py +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/__main__.py +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/python/rumdl/py.typed +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/rumdl.toml.example +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/config.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/init.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/lib.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/lint_context.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/server.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/lsp/types.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/main.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/markdownlint_config.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/parallel.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/performance.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/profiling.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/python.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rule.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/blockquote_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/code_block_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/code_fence_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/emphasis_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/front_matter_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/heading_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/list_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md001_heading_increment.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md002_first_heading_h1.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md003_heading_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md004_unordered_list_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md009_trailing_spaces.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md010_no_hard_tabs.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md012_no_multiple_blanks.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md013_line_length.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md014_commands_show_output.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md018_no_missing_space_atx.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md020_no_missing_space_closed_atx.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md022_blanks_around_headings.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md024_no_duplicate_heading.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md025_single_title.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md029_ordered_list_prefix.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md032_blanks_around_lists.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md033_no_inline_html.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md034_no_bare_urls.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md035_hr_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md036_no_emphasis_only_first.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md038_no_space_in_code.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md040_fenced_code_language.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md041_first_line_heading.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md042_no_empty_links.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md043_required_headings.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md044_proper_names.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md045_no_alt_text.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md046_code_block_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md047_single_trailing_newline.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md048_code_fence_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md049_emphasis_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md050_strong_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md052_reference_links_images.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md053_link_image_reference_definitions.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md054_link_image_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md055_table_pipe_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md056_table_column_count.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md057_existing_relative_links.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/md058_blanks_around_tables.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/rules/strong_style.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/ast_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/code_block_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/document_structure.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/early_returns.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/element_cache.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/markdown_elements.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/range_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/regex_cache.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/string_interner.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/src/utils/table_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/advanced_integration_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/comprehensive_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/character_ranges/unicode_utils.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/cli_duplication_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/cli_integration_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/commonmark_compliance_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/comprehensive_integration_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/config_application_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/config_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/init_command_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/init_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/integration_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/json_output_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/lib.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/lsp_integration_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/lsp_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/markdownlint_cli_integration.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/markdownlint_config_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/md030_edge_cases.md +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/output_format_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/perf_check.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/pyproject_config_tests.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md001_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md001_unicode_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md002_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md003_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md004_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md005_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md006_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md006_unicode_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md009_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md010_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md011_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md012_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md013_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md014_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md018_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md020_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md021_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md022_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md023_extended_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md023_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md024_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md025_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md026_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md027_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md028_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md029_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md030_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md031_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md032_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md033_extended_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md033_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md034_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md035_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md036_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md037_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md038_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md039_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md040_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md041_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md042_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md043_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md044_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md045_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md046_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md047_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md048_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md049_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md050_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md051_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md052_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_additional_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_proptest.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md053_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md054_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md054_unicode_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md055_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md056_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md057_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/md058_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/rules/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/blockquote_utils_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/code_block_utils_extended_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/code_block_utils_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/core_utils_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/front_matter_utils_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/line_index_test.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils/mod.rs +0 -0
- {rumdl-0.0.75 → rumdl-0.0.77}/tests/utils_markdown_edge_cases.rs +0 -0
- {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.
|
|
247
|
+
version = "1.2.25"
|
|
248
248
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
249
|
-
checksum = "
|
|
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
|
|
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.
|
|
1172
|
+
version = "0.4.13"
|
|
1179
1173
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1180
|
-
checksum = "
|
|
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.
|
|
1269
|
+
version = "1.17.0"
|
|
1276
1270
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1277
|
-
checksum = "
|
|
1271
|
+
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
|
1278
1272
|
dependencies = [
|
|
1279
|
-
"hermit-abi
|
|
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.
|
|
1322
|
+
version = "0.12.4"
|
|
1329
1323
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1330
|
-
checksum = "
|
|
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.
|
|
1332
|
+
version = "0.9.11"
|
|
1339
1333
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1340
|
-
checksum = "
|
|
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.
|
|
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.
|
|
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"
|
|
@@ -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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
}
|