rumdl 0.0.80__tar.gz → 0.0.81__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.80 → rumdl-0.0.81}/Cargo.lock +1 -1
- {rumdl-0.0.80 → rumdl-0.0.81}/Cargo.toml +1 -1
- {rumdl-0.0.80 → rumdl-0.0.81}/PKG-INFO +1 -1
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md022_blanks_around_headings.rs +163 -249
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md040_fenced_code_language.rs +70 -8
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md055_table_pipe_style.rs +23 -13
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/fix_utils.rs +48 -2
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/mod.rs +12 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/cli_lsp_fix_consistency.rs +54 -54
- rumdl-0.0.81/tests/configuration_inheritance_tests.rs +326 -0
- rumdl-0.0.81/tests/cross_platform_compatibility_tests.rs +417 -0
- rumdl-0.0.81/tests/final_confidence_assessment.rs +512 -0
- rumdl-0.0.81/tests/lsp_editor_integration_tests.rs +326 -0
- rumdl-0.0.81/tests/lsp_memory_leak_tests.rs +396 -0
- rumdl-0.0.81/tests/malformed_markdown_stress_tests.rs +314 -0
- rumdl-0.0.81/tests/performance_validation_tests.rs +232 -0
- rumdl-0.0.81/tests/real_world_repository_tests.rs +447 -0
- rumdl-0.0.81/tests/thread_safety_tests.rs +416 -0
- rumdl-0.0.81/tests/unicode_edge_case_tests.rs +166 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/.rumdl.toml +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/MANIFEST.in +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/Makefile +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/README.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/assets/logo.png +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/benches/fix_performance.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/benches/range_performance.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/benches/range_utils_benchmark.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/benches/rule_performance.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/benches/simple_fix_bench.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/RULES.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/global-settings.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md001.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md002.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md003.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md004.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md005.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md006.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md007.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md009.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md010.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md011.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md012.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md013.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md014.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md018.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md019.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md020.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md021.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md022.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md023.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md024.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md025.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md026.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md027.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md028.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md029.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md030.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md031.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md032.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md033.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md034.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md035.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md036.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md037.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md038.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md039.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md040.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md041.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md042.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md043.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md044.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md045.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md046.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md047.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md048.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md049.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md050.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md051.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md052.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md053.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md054.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md055.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md056.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md057.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/docs/md058.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/issues/plan-rule-parity-with-markdownlint.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/parity_check.py +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/pyproject.toml +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/python/MANIFEST.in +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/python/PYTHON-README.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/python/rumdl/__init__.py +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/python/rumdl/__main__.py +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/python/rumdl/py.typed +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/rumdl.toml.example +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/config.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/init.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/lib.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/lint_context.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/lsp/mod.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/lsp/server.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/lsp/types.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/main.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/markdownlint_config.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/parallel.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/performance.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/profiling.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/python.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rule.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/blockquote_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/code_block_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/code_fence_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/emphasis_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/front_matter_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/heading_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/list_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md001_heading_increment.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md002_first_heading_h1.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md003_heading_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md004_unordered_list_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md005_list_indent.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md006_start_bullets.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md007_ul_indent.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md009_trailing_spaces.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md010_no_hard_tabs.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md011_no_reversed_links.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md012_no_multiple_blanks.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md013_line_length.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md014_commands_show_output.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md018_no_missing_space_atx.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md019_no_multiple_space_atx.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md020_no_missing_space_closed_atx.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md021_no_multiple_space_closed_atx.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md023_heading_start_left.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md024_no_duplicate_heading.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md025_single_title.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md026_no_trailing_punctuation.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md027_multiple_spaces_blockquote.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md028_no_blanks_blockquote.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md029_ordered_list_prefix.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md030_list_marker_space.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md031_blanks_around_fences.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md032_blanks_around_lists.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md033_no_inline_html.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md034_no_bare_urls.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md035_hr_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md036_no_emphasis_only_first.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md037_spaces_around_emphasis.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md038_no_space_in_code.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md039_no_space_in_links.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md041_first_line_heading.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md042_no_empty_links.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md043_required_headings.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md044_proper_names.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md045_no_alt_text.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md046_code_block_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md047_single_trailing_newline.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md048_code_fence_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md049_emphasis_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md050_strong_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md051_link_fragments.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md052_reference_links_images.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md053_link_image_reference_definitions.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md054_link_image_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md056_table_column_count.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md057_existing_relative_links.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/md058_blanks_around_tables.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/mod.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/rules/strong_style.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/ast_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/code_block_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/document_structure.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/early_returns.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/element_cache.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/markdown_elements.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/range_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/regex_cache.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/string_interner.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/src/utils/table_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/advanced_integration_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/additional_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/basic_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/comprehensive_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/extended_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/mod.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/character_ranges/unicode_utils.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/cli_duplication_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/cli_integration_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/commonmark_compliance_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/comprehensive_integration_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/config_application_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/config_file_command_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/config_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/consistency_regression_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/init_command_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/init_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/integration_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/json_output_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/lib.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/lsp_integration_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/lsp_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/markdownlint_cli_integration.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/markdownlint_config_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/md030_edge_cases.md +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/output_format_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/perf_check.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/pyproject_config_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/regression_prevention_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md001_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md001_unicode_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md002_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md003_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md004_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md005_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md006_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md006_unicode_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md007_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md009_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md010_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md011_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md012_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md013_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md014_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md018_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md019_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md020_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md021_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md022_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md023_extended_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md023_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md024_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md025_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md026_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md027_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md028_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md029_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md030_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md031_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md032_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md033_extended_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md033_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md034_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md035_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md036_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md037_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md038_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md039_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md040_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md041_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md042_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md043_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md044_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md045_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md046_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md047_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md048_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md049_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md050_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md051_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md052_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md053_additional_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md053_proptest.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md053_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md054_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md054_unicode_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md055_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md056_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md057_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/md058_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/rules/mod.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/blockquote_utils_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/code_block_utils_extended_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/code_block_utils_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/core_utils_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/front_matter_utils_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/line_index_test.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils/mod.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils_markdown_edge_cases.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/utils_tests.rs +0 -0
- {rumdl-0.0.80 → rumdl-0.0.81}/tests/vscode_extension_fixes.rs +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule, RuleCategory, Severity};
|
|
5
5
|
use crate::rules::heading_utils::{is_heading, is_setext_heading_marker};
|
|
6
6
|
use crate::utils::document_structure::{DocumentStructure, DocumentStructureExtensions};
|
|
7
|
-
use crate::utils::range_utils::{calculate_heading_range
|
|
7
|
+
use crate::utils::range_utils::{calculate_heading_range};
|
|
8
8
|
use fancy_regex::Regex;
|
|
9
9
|
use lazy_static::lazy_static;
|
|
10
10
|
use toml;
|
|
@@ -251,7 +251,9 @@ impl MD022BlanksAroundHeadings {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/// Fix a document by adding appropriate blank lines around headings
|
|
254
|
-
fn _fix_content(&self, lines: &[&str]) -> String {
|
|
254
|
+
fn _fix_content(&self, lines: &[&str], original_content: &str) -> String {
|
|
255
|
+
let line_ending = crate::utils::detect_line_ending(original_content);
|
|
256
|
+
let had_trailing_newline = original_content.ends_with('\n') || original_content.ends_with("\r\n");
|
|
255
257
|
let mut result = Vec::new();
|
|
256
258
|
let mut in_code_block = false;
|
|
257
259
|
let mut fence_char = None;
|
|
@@ -463,7 +465,21 @@ impl MD022BlanksAroundHeadings {
|
|
|
463
465
|
}
|
|
464
466
|
}
|
|
465
467
|
|
|
466
|
-
result.join(
|
|
468
|
+
let joined = result.join(line_ending);
|
|
469
|
+
|
|
470
|
+
// Preserve original trailing newline behavior
|
|
471
|
+
if had_trailing_newline && !joined.ends_with('\n') && !joined.ends_with("\r\n") {
|
|
472
|
+
format!("{}{}", joined, line_ending)
|
|
473
|
+
} else if !had_trailing_newline && (joined.ends_with('\n') || joined.ends_with("\r\n")) {
|
|
474
|
+
// Remove trailing newline if original didn't have one
|
|
475
|
+
if joined.ends_with("\r\n") {
|
|
476
|
+
joined[..joined.len()-2].to_string()
|
|
477
|
+
} else {
|
|
478
|
+
joined[..joined.len()-1].to_string()
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
joined
|
|
482
|
+
}
|
|
467
483
|
}
|
|
468
484
|
}
|
|
469
485
|
|
|
@@ -478,6 +494,7 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
478
494
|
|
|
479
495
|
fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
|
|
480
496
|
let content = ctx.content;
|
|
497
|
+
let line_ending = crate::utils::detect_line_ending(content);
|
|
481
498
|
let mut result = Vec::new();
|
|
482
499
|
let lines: Vec<&str> = content.lines().collect();
|
|
483
500
|
|
|
@@ -486,33 +503,30 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
486
503
|
return Ok(result);
|
|
487
504
|
}
|
|
488
505
|
|
|
506
|
+
// Early return for files without headings
|
|
507
|
+
if !content.contains('#') && !content.contains('=') && !content.contains('-') {
|
|
508
|
+
return Ok(result);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Pre-compute line byte offsets for efficient range calculations
|
|
512
|
+
let mut line_starts = Vec::with_capacity(lines.len() + 1);
|
|
513
|
+
let mut current_pos = 0;
|
|
514
|
+
line_starts.push(0);
|
|
515
|
+
|
|
516
|
+
for line in &lines {
|
|
517
|
+
current_pos += line.len() + line_ending.len();
|
|
518
|
+
line_starts.push(current_pos);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Pre-compute which lines are blank for efficient lookup
|
|
522
|
+
let blank_lines: Vec<bool> = lines.iter().map(|line| line.trim().is_empty()).collect();
|
|
523
|
+
|
|
524
|
+
// Pre-compute code block regions for efficient lookup
|
|
525
|
+
let mut code_block_lines = vec![false; lines.len()];
|
|
489
526
|
let mut in_code_block = false;
|
|
490
527
|
let mut fence_char = None;
|
|
491
|
-
let mut in_front_matter = false;
|
|
492
|
-
let mut front_matter_start_detected = false;
|
|
493
|
-
let mut _is_first_line = true;
|
|
494
|
-
let mut prev_heading_index: Option<usize> = None;
|
|
495
|
-
let mut processed_headings = std::collections::HashSet::new();
|
|
496
528
|
|
|
497
529
|
for (i, line) in lines.iter().enumerate() {
|
|
498
|
-
// Handle front matter - only consider it front matter if at the start
|
|
499
|
-
if FRONT_MATTER_PATTERN.is_match(line).unwrap_or(false) {
|
|
500
|
-
// Only start front matter if at the beginning of the document (allowing for blank lines)
|
|
501
|
-
if !front_matter_start_detected && i == 0
|
|
502
|
-
|| (i > 0 && lines[..i].iter().all(|l| l.trim().is_empty()))
|
|
503
|
-
{
|
|
504
|
-
in_front_matter = true;
|
|
505
|
-
front_matter_start_detected = true;
|
|
506
|
-
} else if in_front_matter {
|
|
507
|
-
// End front matter if we're in it
|
|
508
|
-
in_front_matter = false;
|
|
509
|
-
}
|
|
510
|
-
// Otherwise it's just a horizontal rule, not front matter
|
|
511
|
-
_is_first_line = false;
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Check for code block fences
|
|
516
530
|
let trimmed = line.trim();
|
|
517
531
|
let is_code_fence = (trimmed.starts_with("```") || trimmed.starts_with("~~~"))
|
|
518
532
|
&& (trimmed == "```"
|
|
@@ -524,7 +538,6 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
524
538
|
.map_or(true, |c| c.is_whitespace() || c.is_alphabetic()));
|
|
525
539
|
|
|
526
540
|
if is_code_fence {
|
|
527
|
-
// Toggle code block state and update fence character
|
|
528
541
|
if !in_code_block {
|
|
529
542
|
fence_char = Some(&trimmed[..3]);
|
|
530
543
|
in_code_block = true;
|
|
@@ -534,32 +547,46 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
534
547
|
fence_char = None;
|
|
535
548
|
}
|
|
536
549
|
}
|
|
537
|
-
continue;
|
|
538
550
|
}
|
|
539
551
|
|
|
540
|
-
|
|
541
|
-
|
|
552
|
+
code_block_lines[i] = in_code_block;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Pre-compute front matter region
|
|
556
|
+
let mut front_matter_lines = vec![false; lines.len()];
|
|
557
|
+
let mut in_front_matter = false;
|
|
558
|
+
let mut front_matter_start_detected = false;
|
|
559
|
+
|
|
560
|
+
for (i, line) in lines.iter().enumerate() {
|
|
561
|
+
if FRONT_MATTER_PATTERN.is_match(line).unwrap_or(false) {
|
|
562
|
+
if !front_matter_start_detected && (i == 0 || lines[..i].iter().all(|l| l.trim().is_empty())) {
|
|
563
|
+
in_front_matter = true;
|
|
564
|
+
front_matter_start_detected = true;
|
|
565
|
+
} else if in_front_matter {
|
|
566
|
+
in_front_matter = false;
|
|
567
|
+
}
|
|
542
568
|
}
|
|
569
|
+
front_matter_lines[i] = in_front_matter;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Collect all headings first to batch process
|
|
573
|
+
let mut heading_violations = Vec::new();
|
|
574
|
+
let mut processed_headings = std::collections::HashSet::new();
|
|
543
575
|
|
|
544
|
-
|
|
576
|
+
for (i, line) in lines.iter().enumerate() {
|
|
577
|
+
// Skip code blocks and front matter
|
|
578
|
+
if code_block_lines[i] || front_matter_lines[i] {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
545
581
|
|
|
546
582
|
// Check if it's a heading
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
{
|
|
552
|
-
let heading_line = if is_setext_heading_marker(line) {
|
|
553
|
-
i - 1
|
|
554
|
-
} else {
|
|
555
|
-
i
|
|
556
|
-
};
|
|
557
|
-
let heading_display_line = heading_line + 1; // 1-indexed for display
|
|
583
|
+
let is_atx_heading = is_heading(line);
|
|
584
|
+
let is_setext_heading = i > 0
|
|
585
|
+
&& is_setext_heading_marker(line)
|
|
586
|
+
&& is_heading(&format!("{} {}", lines[i - 1], line));
|
|
558
587
|
|
|
559
|
-
|
|
560
|
-
if
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
588
|
+
if is_atx_heading || is_setext_heading {
|
|
589
|
+
let heading_line = if is_setext_heading { i - 1 } else { i };
|
|
563
590
|
|
|
564
591
|
// Skip if we've already processed this heading
|
|
565
592
|
if processed_headings.contains(&heading_line) {
|
|
@@ -568,230 +595,127 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
568
595
|
|
|
569
596
|
processed_headings.insert(heading_line);
|
|
570
597
|
|
|
571
|
-
//
|
|
572
|
-
let
|
|
573
|
-
|
|
574
|
-
// Check consecutive headings
|
|
575
|
-
if let Some(prev_idx) = prev_heading_index {
|
|
576
|
-
let blanks_between = heading_line - prev_idx - 1;
|
|
577
|
-
let required_blanks = self.lines_above.max(self.lines_below);
|
|
578
|
-
|
|
579
|
-
if blanks_between < required_blanks {
|
|
580
|
-
let line_word = if required_blanks == 1 {
|
|
581
|
-
"line"
|
|
582
|
-
} else {
|
|
583
|
-
"lines"
|
|
584
|
-
};
|
|
585
|
-
issues.push(format!("Expected {} blank {} between headings", required_blanks, line_word));
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Check blank lines above
|
|
590
|
-
if heading_line > 0 {
|
|
591
|
-
let mut blank_lines_above = 0;
|
|
598
|
+
// Efficiently count blank lines above using pre-computed data
|
|
599
|
+
let blank_lines_above = if heading_line > 0 {
|
|
600
|
+
let mut count = 0;
|
|
592
601
|
for j in (0..heading_line).rev() {
|
|
593
|
-
if
|
|
594
|
-
|
|
602
|
+
if blank_lines[j] {
|
|
603
|
+
count += 1;
|
|
595
604
|
} else {
|
|
596
605
|
break;
|
|
597
606
|
}
|
|
598
607
|
}
|
|
608
|
+
count
|
|
609
|
+
} else {
|
|
610
|
+
0
|
|
611
|
+
};
|
|
599
612
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
"lines"
|
|
605
|
-
};
|
|
606
|
-
issues.push(format!(
|
|
607
|
-
"Expected {} blank {} above heading",
|
|
608
|
-
self.lines_above, line_word
|
|
609
|
-
));
|
|
610
|
-
}
|
|
613
|
+
// Check if we need blank lines above
|
|
614
|
+
if heading_line > 0 && blank_lines_above < self.lines_above {
|
|
615
|
+
let needed_blanks = self.lines_above - blank_lines_above;
|
|
616
|
+
heading_violations.push((heading_line, "above", needed_blanks));
|
|
611
617
|
}
|
|
612
618
|
|
|
613
619
|
// Check blank lines below
|
|
614
620
|
let effective_heading_line = heading_line;
|
|
615
621
|
if effective_heading_line < lines.len() - 1 {
|
|
616
|
-
//
|
|
622
|
+
// Find next non-blank line efficiently
|
|
617
623
|
let mut next_non_blank_idx = effective_heading_line + 1;
|
|
618
|
-
while next_non_blank_idx < lines.len()
|
|
619
|
-
&& lines[next_non_blank_idx].trim().is_empty()
|
|
620
|
-
{
|
|
624
|
+
while next_non_blank_idx < lines.len() && blank_lines[next_non_blank_idx] {
|
|
621
625
|
next_non_blank_idx += 1;
|
|
622
626
|
}
|
|
623
627
|
|
|
624
|
-
let next_line_is_code_fence = next_non_blank_idx < lines.len() && {
|
|
625
|
-
let next_trimmed = lines[next_non_blank_idx].trim();
|
|
626
|
-
(next_trimmed.starts_with("```") || next_trimmed.starts_with("~~~"))
|
|
627
|
-
&& (next_trimmed == "```"
|
|
628
|
-
|| next_trimmed == "~~~"
|
|
629
|
-
|| next_trimmed.len() >= 3
|
|
630
|
-
&& next_trimmed[3..]
|
|
631
|
-
.chars()
|
|
632
|
-
.next()
|
|
633
|
-
.map_or(true, |c| c.is_whitespace() || c.is_alphabetic()))
|
|
634
|
-
};
|
|
635
|
-
|
|
636
628
|
// Check if next line is a code fence or list item
|
|
637
|
-
let
|
|
638
|
-
let next_trimmed = lines[next_non_blank_idx].trim();
|
|
639
|
-
next_trimmed.starts_with("- ")
|
|
640
|
-
|| next_trimmed.starts_with("* ")
|
|
641
|
-
|| next_trimmed.starts_with("+ ")
|
|
642
|
-
|| next_trimmed
|
|
643
|
-
.chars()
|
|
644
|
-
.next()
|
|
645
|
-
.map_or(false, |c| c.is_ascii_digit())
|
|
646
|
-
&& next_trimmed.contains(". ")
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
// If next line is a code fence or list item, we don't need blank lines between
|
|
650
|
-
if !next_line_is_code_fence && !next_line_is_list {
|
|
651
|
-
let mut blank_lines_below = 0;
|
|
652
|
-
for line in lines.iter().skip(effective_heading_line + 1) {
|
|
653
|
-
if !line.trim().is_empty() {
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
blank_lines_below += 1;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if blank_lines_below < self.lines_below {
|
|
660
|
-
let line_word = if self.lines_below == 1 {
|
|
661
|
-
"line"
|
|
662
|
-
} else {
|
|
663
|
-
"lines"
|
|
664
|
-
};
|
|
665
|
-
issues.push(format!(
|
|
666
|
-
"Expected {} blank {} below heading",
|
|
667
|
-
self.lines_below, line_word
|
|
668
|
-
));
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Generate individual warnings with proper LSP fixes instead of combined warnings
|
|
674
|
-
let line_index = LineIndex::new(content.to_string());
|
|
675
|
-
|
|
676
|
-
// Check if we need blank lines above
|
|
677
|
-
if heading_line > 0 {
|
|
678
|
-
let mut blank_lines_above = 0;
|
|
679
|
-
for j in (0..heading_line).rev() {
|
|
680
|
-
if lines[j].trim().is_empty() {
|
|
681
|
-
blank_lines_above += 1;
|
|
682
|
-
} else {
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
if blank_lines_above < self.lines_above {
|
|
688
|
-
let needed_blanks = self.lines_above - blank_lines_above;
|
|
689
|
-
let insertion_point = heading_line + 1; // Insert before the heading line (1-indexed: +1 for 1-indexed)
|
|
690
|
-
|
|
691
|
-
// Calculate precise character range for inserting blank lines before heading
|
|
692
|
-
let (start_line, start_col, end_line, end_col) =
|
|
693
|
-
calculate_heading_range(heading_display_line, line);
|
|
694
|
-
|
|
695
|
-
result.push(LintWarning {
|
|
696
|
-
rule_name: Some(self.name()),
|
|
697
|
-
message: format!(
|
|
698
|
-
"Expected {} blank {} above heading",
|
|
699
|
-
self.lines_above,
|
|
700
|
-
if self.lines_above == 1 { "line" } else { "lines" }
|
|
701
|
-
),
|
|
702
|
-
line: start_line,
|
|
703
|
-
column: start_col,
|
|
704
|
-
end_line,
|
|
705
|
-
end_column: end_col,
|
|
706
|
-
severity: Severity::Warning,
|
|
707
|
-
fix: Some(Fix {
|
|
708
|
-
range: line_index.line_col_to_byte_range_with_length(insertion_point, 1, 0),
|
|
709
|
-
replacement: "\n".repeat(needed_blanks),
|
|
710
|
-
}),
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Check if we need blank lines below (but skip if next line is list or code fence)
|
|
716
|
-
let effective_heading_line = heading_line;
|
|
717
|
-
if effective_heading_line < lines.len() - 1 {
|
|
718
|
-
// Check what the next non-blank line is
|
|
719
|
-
let mut next_non_blank_idx = effective_heading_line + 1;
|
|
720
|
-
while next_non_blank_idx < lines.len()
|
|
721
|
-
&& lines[next_non_blank_idx].trim().is_empty()
|
|
722
|
-
{
|
|
723
|
-
next_non_blank_idx += 1;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Skip warning if next line is a code fence or list item (same logic as earlier)
|
|
727
|
-
let next_line_is_code_fence = next_non_blank_idx < lines.len() && {
|
|
629
|
+
let next_line_is_special = next_non_blank_idx < lines.len() && {
|
|
728
630
|
let next_trimmed = lines[next_non_blank_idx].trim();
|
|
729
|
-
|
|
631
|
+
// Code fence check
|
|
632
|
+
((next_trimmed.starts_with("```") || next_trimmed.starts_with("~~~"))
|
|
730
633
|
&& (next_trimmed == "```"
|
|
731
634
|
|| next_trimmed == "~~~"
|
|
732
635
|
|| next_trimmed.len() >= 3
|
|
733
636
|
&& next_trimmed[3..]
|
|
734
637
|
.chars()
|
|
735
638
|
.next()
|
|
736
|
-
.map_or(true, |c| c.is_whitespace() || c.is_alphabetic()))
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
next_trimmed
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
.next()
|
|
747
|
-
.map_or(false, |c| c.is_ascii_digit())
|
|
748
|
-
&& next_trimmed.contains(". ")
|
|
639
|
+
.map_or(true, |c| c.is_whitespace() || c.is_alphabetic())))
|
|
640
|
+
// List item check
|
|
641
|
+
|| next_trimmed.starts_with("- ")
|
|
642
|
+
|| next_trimmed.starts_with("* ")
|
|
643
|
+
|| next_trimmed.starts_with("+ ")
|
|
644
|
+
|| (next_trimmed
|
|
645
|
+
.chars()
|
|
646
|
+
.next()
|
|
647
|
+
.map_or(false, |c| c.is_ascii_digit())
|
|
648
|
+
&& next_trimmed.contains(". "))
|
|
749
649
|
};
|
|
750
650
|
|
|
751
651
|
// Only generate warning if next line is NOT a code fence or list item
|
|
752
|
-
if !
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if !line.trim().is_empty() {
|
|
756
|
-
break;
|
|
757
|
-
}
|
|
758
|
-
blank_lines_below += 1;
|
|
759
|
-
}
|
|
652
|
+
if !next_line_is_special {
|
|
653
|
+
// Efficiently count blank lines below using pre-computed data
|
|
654
|
+
let blank_lines_below = next_non_blank_idx - effective_heading_line - 1;
|
|
760
655
|
|
|
761
656
|
if blank_lines_below < self.lines_below {
|
|
762
657
|
let needed_blanks = self.lines_below - blank_lines_below;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// Calculate precise character range for inserting blank lines after heading
|
|
766
|
-
let (start_line, start_col, end_line, end_col) =
|
|
767
|
-
calculate_heading_range(heading_display_line, line);
|
|
768
|
-
|
|
769
|
-
result.push(LintWarning {
|
|
770
|
-
rule_name: Some(self.name()),
|
|
771
|
-
message: format!(
|
|
772
|
-
"Expected {} blank {} below heading",
|
|
773
|
-
self.lines_below,
|
|
774
|
-
if self.lines_below == 1 { "line" } else { "lines" }
|
|
775
|
-
),
|
|
776
|
-
line: start_line,
|
|
777
|
-
column: start_col,
|
|
778
|
-
end_line,
|
|
779
|
-
end_column: end_col,
|
|
780
|
-
severity: Severity::Warning,
|
|
781
|
-
fix: Some(Fix {
|
|
782
|
-
range: line_index.line_col_to_byte_range_with_length(insertion_point, 1, 0),
|
|
783
|
-
replacement: "\n".repeat(needed_blanks),
|
|
784
|
-
}),
|
|
785
|
-
});
|
|
658
|
+
heading_violations.push((heading_line, "below", needed_blanks));
|
|
786
659
|
}
|
|
787
660
|
}
|
|
788
661
|
}
|
|
789
|
-
|
|
790
|
-
// Update previous heading index
|
|
791
|
-
prev_heading_index = Some(heading_line);
|
|
792
662
|
}
|
|
793
663
|
}
|
|
794
664
|
|
|
665
|
+
// Generate warnings for all violations
|
|
666
|
+
for (heading_line, position, needed_blanks) in heading_violations {
|
|
667
|
+
let heading_display_line = heading_line + 1; // 1-indexed for display
|
|
668
|
+
let line = lines[heading_line];
|
|
669
|
+
|
|
670
|
+
// Calculate precise character range for the heading
|
|
671
|
+
let (start_line, start_col, end_line, end_col) =
|
|
672
|
+
calculate_heading_range(heading_display_line, line);
|
|
673
|
+
|
|
674
|
+
let (message, insertion_point) = match position {
|
|
675
|
+
"above" => (
|
|
676
|
+
format!(
|
|
677
|
+
"Expected {} blank {} above heading",
|
|
678
|
+
self.lines_above,
|
|
679
|
+
if self.lines_above == 1 { "line" } else { "lines" }
|
|
680
|
+
),
|
|
681
|
+
heading_line + 1
|
|
682
|
+
),
|
|
683
|
+
"below" => (
|
|
684
|
+
format!(
|
|
685
|
+
"Expected {} blank {} below heading",
|
|
686
|
+
self.lines_below,
|
|
687
|
+
if self.lines_below == 1 { "line" } else { "lines" }
|
|
688
|
+
),
|
|
689
|
+
heading_line + 2
|
|
690
|
+
),
|
|
691
|
+
_ => continue,
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Use pre-computed line starts for efficient byte range calculation
|
|
695
|
+
let byte_range = if insertion_point <= lines.len() {
|
|
696
|
+
let start_byte = line_starts[insertion_point - 1]; // Convert 1-indexed to 0-indexed
|
|
697
|
+
start_byte..start_byte
|
|
698
|
+
} else {
|
|
699
|
+
// Insert at end of file
|
|
700
|
+
let last_byte = line_starts[lines.len()];
|
|
701
|
+
last_byte..last_byte
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
result.push(LintWarning {
|
|
705
|
+
rule_name: Some(self.name()),
|
|
706
|
+
message,
|
|
707
|
+
line: start_line,
|
|
708
|
+
column: start_col,
|
|
709
|
+
end_line,
|
|
710
|
+
end_column: end_col,
|
|
711
|
+
severity: Severity::Warning,
|
|
712
|
+
fix: Some(Fix {
|
|
713
|
+
range: byte_range,
|
|
714
|
+
replacement: line_ending.repeat(needed_blanks),
|
|
715
|
+
}),
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
795
719
|
Ok(result)
|
|
796
720
|
}
|
|
797
721
|
|
|
@@ -801,21 +725,11 @@ impl Rule for MD022BlanksAroundHeadings {
|
|
|
801
725
|
return Ok(content.to_string());
|
|
802
726
|
}
|
|
803
727
|
|
|
804
|
-
// Check if original content ended with newline
|
|
805
|
-
let had_trailing_newline = content.ends_with('\n');
|
|
806
|
-
|
|
807
728
|
// Use a consolidated fix that avoids adding multiple blank lines
|
|
808
729
|
let lines: Vec<&str> = content.lines().collect();
|
|
809
|
-
let fixed = self._fix_content(&lines);
|
|
810
|
-
|
|
811
|
-
// Preserve original trailing newline if it existed
|
|
812
|
-
let result = if had_trailing_newline && !fixed.ends_with('\n') {
|
|
813
|
-
format!("{}\n", fixed)
|
|
814
|
-
} else {
|
|
815
|
-
fixed
|
|
816
|
-
};
|
|
730
|
+
let fixed = self._fix_content(&lines, content);
|
|
817
731
|
|
|
818
|
-
Ok(
|
|
732
|
+
Ok(fixed)
|
|
819
733
|
}
|
|
820
734
|
|
|
821
735
|
/// Optimized check using document structure
|
|
@@ -1334,7 +1248,7 @@ Even more content.
|
|
|
1334
1248
|
|
|
1335
1249
|
Final content.";
|
|
1336
1250
|
|
|
1337
|
-
let result = rule._fix_content(&content.lines().collect::<Vec<&str>>());
|
|
1251
|
+
let result = rule._fix_content(&content.lines().collect::<Vec<&str>>(), content);
|
|
1338
1252
|
assert_eq!(
|
|
1339
1253
|
result, expected,
|
|
1340
1254
|
"Fix should only add missing blank lines, never remove existing ones"
|
|
@@ -1368,7 +1282,7 @@ Final content.";
|
|
|
1368
1282
|
|
|
1369
1283
|
let expected = "## Configuration\n\nThis rule has the following configuration options:\n\n- `option1`: Description of option 1.\n- `option2`: Description of option 2.\n\n## Another Section\n\nSome content here.";
|
|
1370
1284
|
|
|
1371
|
-
let result = rule._fix_content(&content.lines().collect::<Vec<&str>>());
|
|
1285
|
+
let result = rule._fix_content(&content.lines().collect::<Vec<&str>>(), content);
|
|
1372
1286
|
assert_eq!(
|
|
1373
1287
|
result, expected,
|
|
1374
1288
|
"Fix should not add blank lines before lists"
|
|
@@ -160,21 +160,68 @@ impl Rule for MD040FencedCodeLanguage {
|
|
|
160
160
|
let _line_index = LineIndex::new(content.to_string());
|
|
161
161
|
|
|
162
162
|
let mut result = String::new();
|
|
163
|
-
|
|
164
163
|
let mut in_code_block = false;
|
|
165
|
-
|
|
166
164
|
let mut fence_char = None;
|
|
165
|
+
let mut fence_needs_language = false;
|
|
166
|
+
let mut original_indent = String::new();
|
|
167
167
|
|
|
168
168
|
let lines: Vec<&str> = content.lines().collect();
|
|
169
|
-
|
|
169
|
+
|
|
170
|
+
// Helper function to check if we're in a nested context
|
|
171
|
+
let is_in_nested_context = |line_idx: usize| -> bool {
|
|
172
|
+
// Look for blockquote or list context above this line
|
|
173
|
+
for i in (0..line_idx).rev() {
|
|
174
|
+
let line = lines.get(i).unwrap_or(&"");
|
|
175
|
+
let trimmed = line.trim();
|
|
176
|
+
|
|
177
|
+
// If we hit a blank line, check if context continues
|
|
178
|
+
if trimmed.is_empty() {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check for blockquote markers
|
|
183
|
+
if line.trim_start().starts_with('>') {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check for list markers with sufficient indentation
|
|
188
|
+
if line.len() - line.trim_start().len() >= 2 {
|
|
189
|
+
let after_indent = line.trim_start();
|
|
190
|
+
if after_indent.starts_with("- ") || after_indent.starts_with("* ") ||
|
|
191
|
+
after_indent.starts_with("+ ") ||
|
|
192
|
+
(after_indent.len() > 2 && after_indent.chars().nth(0).unwrap_or(' ').is_ascii_digit() &&
|
|
193
|
+
after_indent.chars().nth(1).unwrap_or(' ') == '.' &&
|
|
194
|
+
after_indent.chars().nth(2).unwrap_or(' ') == ' ') {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If we find content that's not indented, we're not in nested context
|
|
200
|
+
if line.starts_with(|c: char| !c.is_whitespace()) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
false
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
for (i, line) in lines.iter().enumerate() {
|
|
170
208
|
let trimmed = line.trim();
|
|
171
209
|
|
|
172
210
|
if let Some(ref current_fence) = fence_char {
|
|
173
211
|
if trimmed.starts_with(current_fence) {
|
|
174
|
-
// This is a closing fence
|
|
175
|
-
|
|
212
|
+
// This is a closing fence
|
|
213
|
+
if fence_needs_language {
|
|
214
|
+
// Use the same indentation as the opening fence
|
|
215
|
+
result.push_str(&format!("{}{}\n", original_indent, trimmed));
|
|
216
|
+
} else {
|
|
217
|
+
// Preserve original line as-is
|
|
218
|
+
result.push_str(line);
|
|
219
|
+
result.push('\n');
|
|
220
|
+
}
|
|
176
221
|
in_code_block = false;
|
|
177
222
|
fence_char = None;
|
|
223
|
+
fence_needs_language = false;
|
|
224
|
+
original_indent.clear();
|
|
178
225
|
continue;
|
|
179
226
|
}
|
|
180
227
|
|
|
@@ -190,15 +237,30 @@ impl Rule for MD040FencedCodeLanguage {
|
|
|
190
237
|
};
|
|
191
238
|
fence_char = Some(fence.to_string());
|
|
192
239
|
|
|
240
|
+
// Capture the original indentation
|
|
241
|
+
let line_indent = line[..line.len() - line.trim_start().len()].to_string();
|
|
242
|
+
|
|
193
243
|
// Add 'text' as default language for opening fence if no language specified
|
|
194
244
|
let after_fence = trimmed[fence.len()..].trim();
|
|
195
245
|
if after_fence.is_empty() {
|
|
196
|
-
//
|
|
197
|
-
|
|
246
|
+
// Decide whether to preserve indentation based on context
|
|
247
|
+
let should_preserve_indent = is_in_nested_context(i);
|
|
248
|
+
|
|
249
|
+
if should_preserve_indent {
|
|
250
|
+
// Preserve indentation for nested contexts
|
|
251
|
+
original_indent = line_indent;
|
|
252
|
+
result.push_str(&format!("{}{}text\n", original_indent, fence));
|
|
253
|
+
} else {
|
|
254
|
+
// Remove indentation for standalone code blocks
|
|
255
|
+
original_indent = String::new();
|
|
256
|
+
result.push_str(&format!("{}text\n", fence));
|
|
257
|
+
}
|
|
258
|
+
fence_needs_language = true;
|
|
198
259
|
} else {
|
|
199
|
-
// Keep original
|
|
260
|
+
// Keep original line as-is since it already has a language
|
|
200
261
|
result.push_str(line);
|
|
201
262
|
result.push('\n');
|
|
263
|
+
fence_needs_language = false;
|
|
202
264
|
}
|
|
203
265
|
} else {
|
|
204
266
|
result.push_str(line);
|