rumdl 0.0.74__tar.gz → 0.0.75__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.74 → rumdl-0.0.75}/Cargo.lock +1 -1
- {rumdl-0.0.74 → rumdl-0.0.75}/Cargo.toml +1 -1
- {rumdl-0.0.74 → rumdl-0.0.75}/PKG-INFO +1 -1
- rumdl-0.0.75/src/rules/md011_no_reversed_links.rs +447 -0
- rumdl-0.0.75/src/rules/md018_no_missing_space_atx.rs +479 -0
- rumdl-0.0.75/src/rules/md027_multiple_spaces_blockquote.rs +269 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md032_blanks_around_lists.rs +2 -2
- rumdl-0.0.74/src/rules/md011_no_reversed_links.rs +0 -233
- rumdl-0.0.74/src/rules/md018_no_missing_space_atx.rs +0 -257
- rumdl-0.0.74/src/rules/md027_multiple_spaces_blockquote.rs +0 -111
- {rumdl-0.0.74 → rumdl-0.0.75}/.rumdl.toml +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/MANIFEST.in +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/Makefile +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/README.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/assets/logo.png +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/benches/fix_performance.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/benches/range_performance.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/benches/range_utils_benchmark.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/benches/rule_performance.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/benches/simple_fix_bench.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/RULES.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md001.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md002.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md003.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md004.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md005.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md006.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md007.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md009.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md010.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md011.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md012.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md013.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md014.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md018.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md019.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md020.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md021.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md022.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md023.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md024.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md025.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md026.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md027.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md028.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md029.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md030.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md031.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md032.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md033.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md034.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md035.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md036.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md037.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md038.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md039.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md040.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md041.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md042.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md043.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md044.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md045.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md046.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md047.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md048.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md049.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md050.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md051.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md052.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md053.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md054.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md055.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md056.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md057.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/docs/md058.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/issues/plan-rule-parity-with-markdownlint.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/parity_check.py +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/pyproject.toml +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/python/MANIFEST.in +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/python/PYTHON-README.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/python/rumdl/__init__.py +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/python/rumdl/__main__.py +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/python/rumdl/py.typed +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/rumdl.toml.example +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/config.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/init.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/lib.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/lint_context.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/lsp/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/lsp/server.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/lsp/types.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/main.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/markdownlint_config.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/parallel.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/performance.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/profiling.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/python.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rule.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/blockquote_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/code_block_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/code_fence_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/emphasis_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/front_matter_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/heading_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/list_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md001_heading_increment.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md002_first_heading_h1.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md003_heading_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md004_unordered_list_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md005_list_indent.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md006_start_bullets.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md007_ul_indent.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md009_trailing_spaces.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md010_no_hard_tabs.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md012_no_multiple_blanks.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md013_line_length.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md014_commands_show_output.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md019_no_multiple_space_atx.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md020_no_missing_space_closed_atx.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md021_no_multiple_space_closed_atx.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md022_blanks_around_headings.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md023_heading_start_left.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md024_no_duplicate_heading.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md025_single_title.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md026_no_trailing_punctuation.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md028_no_blanks_blockquote.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md029_ordered_list_prefix.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md030_list_marker_space.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md031_blanks_around_fences.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md033_no_inline_html.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md034_no_bare_urls.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md035_hr_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md036_no_emphasis_only_first.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md037_spaces_around_emphasis.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md038_no_space_in_code.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md039_no_space_in_links.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md040_fenced_code_language.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md041_first_line_heading.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md042_no_empty_links.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md043_required_headings.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md044_proper_names.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md045_no_alt_text.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md046_code_block_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md047_single_trailing_newline.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md048_code_fence_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md049_emphasis_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md050_strong_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md051_link_fragments.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md052_reference_links_images.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md053_link_image_reference_definitions.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md054_link_image_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md055_table_pipe_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md056_table_column_count.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md057_existing_relative_links.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/md058_blanks_around_tables.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/rules/strong_style.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/ast_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/code_block_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/document_structure.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/early_returns.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/element_cache.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/markdown_elements.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/range_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/regex_cache.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/string_interner.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/src/utils/table_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/advanced_integration_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/additional_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/basic_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/comprehensive_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/extended_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/character_ranges/unicode_utils.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/cli_duplication_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/cli_integration_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/commonmark_compliance_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/comprehensive_integration_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/config_application_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/config_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/init_command_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/init_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/integration_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/json_output_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/lib.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/lsp_integration_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/lsp_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/markdownlint_cli_integration.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/markdownlint_config_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/md030_edge_cases.md +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/output_format_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/perf_check.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/pyproject_config_tests.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md001_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md001_unicode_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md002_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md003_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md004_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md005_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md006_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md006_unicode_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md007_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md009_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md010_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md011_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md012_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md013_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md014_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md018_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md019_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md020_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md021_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md022_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md023_extended_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md023_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md024_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md025_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md026_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md027_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md028_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md029_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md030_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md031_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md032_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md033_extended_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md033_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md034_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md035_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md036_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md037_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md038_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md039_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md040_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md041_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md042_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md043_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md044_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md045_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md046_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md047_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md048_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md049_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md050_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md051_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md052_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md053_additional_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md053_proptest.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md053_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md054_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md054_unicode_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md055_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md056_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md057_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/md058_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/rules/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/blockquote_utils_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/code_block_utils_extended_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/code_block_utils_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/core_utils_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/front_matter_utils_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/line_index_test.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils/mod.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils_markdown_edge_cases.rs +0 -0
- {rumdl-0.0.74 → rumdl-0.0.75}/tests/utils_tests.rs +0 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/// Rule MD011: No reversed link syntax
|
|
2
|
+
///
|
|
3
|
+
/// See [docs/md011.md](../../docs/md011.md) for full documentation, configuration, and examples.
|
|
4
|
+
use crate::rule::{Fix, LintError, LintResult, LintWarning, Rule, Severity};
|
|
5
|
+
use crate::utils::range_utils::calculate_match_range;
|
|
6
|
+
use lazy_static::lazy_static;
|
|
7
|
+
use regex::Regex;
|
|
8
|
+
|
|
9
|
+
lazy_static! {
|
|
10
|
+
static ref REVERSED_LINK_REGEX: Regex =
|
|
11
|
+
Regex::new(r"\[([^\]]+)\]\(([^)]+)\)|(\([^)]+\))\[([^\]]+)\]").unwrap();
|
|
12
|
+
static ref REVERSED_LINK_CHECK_REGEX: Regex = Regex::new(r"\(([^)]+)\)\[([^\]]+)\]").unwrap();
|
|
13
|
+
static ref CODE_FENCE_REGEX: Regex = Regex::new(r"^(\s*)(```|~~~)").unwrap();
|
|
14
|
+
|
|
15
|
+
// New patterns for detecting malformed link attempts where user intent is clear
|
|
16
|
+
static ref MALFORMED_LINK_PATTERNS: Vec<(Regex, &'static str)> = vec]+)\)\[([^\]]*$)").unwrap(), "missing closing bracket"),
|
|
19
|
+
(Regex::new(r"\[([^\]]+)\]\(([^)]*$)").unwrap(), "missing closing parenthesis"),
|
|
20
|
+
|
|
21
|
+
// Wrong bracket types: {URL}[text] or [text]{URL}
|
|
22
|
+
(Regex::new(r"\{([^}]+)\}\[([^\]]+)\]").unwrap(), "wrong bracket type (curly instead of parentheses)"),
|
|
23
|
+
(Regex::new(r"\[([^\]]+)\]\{([^}]+)\}").unwrap(), "wrong bracket type (curly instead of parentheses)"),
|
|
24
|
+
|
|
25
|
+
// URL and text swapped in correct syntax: [URL](text) where URL is clearly a URL
|
|
26
|
+
(Regex::new(r"\[(https?://[^\]]+)\]\(([^)]+)\)").unwrap(), "URL and text appear to be swapped"),
|
|
27
|
+
(Regex::new(r"\[(www\.[^\]]+)\]\(([^)]+)\)").unwrap(), "URL and text appear to be swapped"),
|
|
28
|
+
(Regex::new(r"\[([^\]]*\.[a-z]{2,4}[^\]]*)\]\(([^)]+)\)").unwrap(), "URL and text appear to be swapped"),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Clone)]
|
|
33
|
+
pub struct MD011NoReversedLinks;
|
|
34
|
+
|
|
35
|
+
impl MD011NoReversedLinks {
|
|
36
|
+
fn find_reversed_links(content: &str) -> Vec<(usize, usize, String, String)> {
|
|
37
|
+
let mut results = Vec::new();
|
|
38
|
+
let mut line_start = 0;
|
|
39
|
+
let mut current_line = 1;
|
|
40
|
+
|
|
41
|
+
for line in content.lines() {
|
|
42
|
+
for cap in REVERSED_LINK_REGEX.captures_iter(line) {
|
|
43
|
+
if cap.get(3).is_some() {
|
|
44
|
+
// Found reversed link syntax (text)[url]
|
|
45
|
+
let text = cap[3].trim_matches('(').trim_matches(')');
|
|
46
|
+
let url = &cap[4];
|
|
47
|
+
let start = line_start + cap.get(0).unwrap().start();
|
|
48
|
+
results.push((
|
|
49
|
+
current_line,
|
|
50
|
+
start - line_start + 1,
|
|
51
|
+
text.to_string(),
|
|
52
|
+
url.to_string(),
|
|
53
|
+
));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
line_start += line.len() + 1; // +1 for newline
|
|
57
|
+
current_line += 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
results
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Detect malformed link attempts where user intent is clear
|
|
64
|
+
fn detect_malformed_link_attempts(&self, line: &str) -> Vec<(usize, usize, String, String)> {
|
|
65
|
+
let mut results = Vec::new();
|
|
66
|
+
let mut processed_ranges = Vec::new(); // Track processed character ranges to avoid duplicates
|
|
67
|
+
|
|
68
|
+
for (pattern, issue_type) in MALFORMED_LINK_PATTERNS.iter() {
|
|
69
|
+
for cap in pattern.captures_iter(line) {
|
|
70
|
+
let match_obj = cap.get(0).unwrap();
|
|
71
|
+
let start = match_obj.start();
|
|
72
|
+
let len = match_obj.len();
|
|
73
|
+
let end = start + len;
|
|
74
|
+
|
|
75
|
+
// Skip if this range overlaps with already processed ranges
|
|
76
|
+
if processed_ranges.iter().any(|(proc_start, proc_end)|
|
|
77
|
+
(start < *proc_end && end > *proc_start)
|
|
78
|
+
) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Extract potential URL and text based on the pattern
|
|
83
|
+
if let Some((url, text)) = self.extract_url_and_text_from_match(&cap, issue_type) {
|
|
84
|
+
// Only proceed if this looks like a genuine link attempt
|
|
85
|
+
if self.looks_like_link_attempt(&url, &text) {
|
|
86
|
+
results.push((start, len, url, text));
|
|
87
|
+
processed_ranges.push((start, end));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
results
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Extract URL and text from regex match based on the issue type
|
|
97
|
+
fn extract_url_and_text_from_match(&self, cap: ®ex::Captures, issue_type: &str) -> Option<(String, String)> {
|
|
98
|
+
match issue_type {
|
|
99
|
+
"missing closing bracket" => {
|
|
100
|
+
// (URL)[text -> cap[1] = URL, cap[2] = incomplete text
|
|
101
|
+
Some((cap[1].to_string(), format!("{}]", &cap[2])))
|
|
102
|
+
},
|
|
103
|
+
"missing closing parenthesis" => {
|
|
104
|
+
// [text](URL -> cap[1] = text, cap[2] = incomplete URL
|
|
105
|
+
Some((format!("{})", &cap[2]), cap[1].to_string()))
|
|
106
|
+
},
|
|
107
|
+
"wrong bracket type (curly instead of parentheses)" => {
|
|
108
|
+
// {URL}[text] or [text]{URL} -> cap[1] and cap[2]
|
|
109
|
+
if cap.get(0).unwrap().as_str().starts_with('{') {
|
|
110
|
+
// {URL}[text] -> swap and fix brackets
|
|
111
|
+
Some((cap[1].to_string(), cap[2].to_string()))
|
|
112
|
+
} else {
|
|
113
|
+
// [text]{URL} -> already in correct order, fix brackets
|
|
114
|
+
Some((cap[2].to_string(), cap[1].to_string()))
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"URL and text appear to be swapped" => {
|
|
118
|
+
// [URL](text) -> cap[1] = URL, cap[2] = text, need to swap
|
|
119
|
+
Some((cap[1].to_string(), cap[2].to_string()))
|
|
120
|
+
},
|
|
121
|
+
_ => None,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Check if the extracted URL and text look like a genuine link attempt
|
|
126
|
+
fn looks_like_link_attempt(&self, url: &str, text: &str) -> bool {
|
|
127
|
+
// URL should look like a URL
|
|
128
|
+
let url_indicators = [
|
|
129
|
+
"http://", "https://", "www.", "ftp://",
|
|
130
|
+
".com", ".org", ".net", ".edu", ".gov", ".io", ".co"
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
let has_url_indicator = url_indicators.iter().any(|indicator|
|
|
134
|
+
url.to_lowercase().contains(indicator)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Text should be reasonable length and not look like a URL
|
|
138
|
+
let text_looks_reasonable = text.len() >= 3 && text.len() <= 50
|
|
139
|
+
&& !url_indicators.iter().any(|indicator|
|
|
140
|
+
text.to_lowercase().contains(indicator)
|
|
141
|
+
)
|
|
142
|
+
&& !text.to_lowercase().starts_with("http")
|
|
143
|
+
&& text.chars().any(|c| c.is_alphabetic()); // Must contain at least one letter
|
|
144
|
+
|
|
145
|
+
// URL should not be too short or contain only non-URL characters
|
|
146
|
+
let url_looks_reasonable = url.len() >= 4
|
|
147
|
+
&& (has_url_indicator || url.contains('.'))
|
|
148
|
+
&& !url.chars().all(|c| c.is_alphabetic()); // Shouldn't be just letters
|
|
149
|
+
|
|
150
|
+
// Both URL and text should look reasonable for this to be a link attempt
|
|
151
|
+
has_url_indicator && text_looks_reasonable && url_looks_reasonable
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn is_in_code_block(&self, content: &str, position: usize) -> bool {
|
|
155
|
+
let mut in_code_block = false;
|
|
156
|
+
let mut current_pos = 0;
|
|
157
|
+
|
|
158
|
+
for line in content.lines() {
|
|
159
|
+
if CODE_FENCE_REGEX.is_match(line) {
|
|
160
|
+
in_code_block = !in_code_block;
|
|
161
|
+
}
|
|
162
|
+
current_pos += line.len() + 1;
|
|
163
|
+
if current_pos > position {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
in_code_block
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
impl Rule for MD011NoReversedLinks {
|
|
173
|
+
fn name(&self) -> &'static str {
|
|
174
|
+
"MD011"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn description(&self) -> &'static str {
|
|
178
|
+
"Link syntax should not be reversed"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn check(&self, ctx: &crate::lint_context::LintContext) -> LintResult {
|
|
182
|
+
let content = ctx.content;
|
|
183
|
+
let mut warnings = Vec::new();
|
|
184
|
+
|
|
185
|
+
for (line_num, line) in content.lines().enumerate() {
|
|
186
|
+
// Part 1: Check for existing perfectly formed reversed links
|
|
187
|
+
for cap in REVERSED_LINK_CHECK_REGEX.captures_iter(line) {
|
|
188
|
+
let match_obj = cap.get(0).unwrap();
|
|
189
|
+
|
|
190
|
+
// Calculate precise character range for the reversed syntax
|
|
191
|
+
let (start_line, start_col, end_line, end_col) =
|
|
192
|
+
calculate_match_range(line_num + 1, line, match_obj.start(), match_obj.len());
|
|
193
|
+
|
|
194
|
+
warnings.push(LintWarning {
|
|
195
|
+
rule_name: Some(self.name()),
|
|
196
|
+
message: "Reversed link syntax".to_string(),
|
|
197
|
+
line: start_line,
|
|
198
|
+
column: start_col,
|
|
199
|
+
end_line,
|
|
200
|
+
end_column: end_col,
|
|
201
|
+
severity: Severity::Warning,
|
|
202
|
+
fix: Some(Fix {
|
|
203
|
+
range: (0..0), // TODO: Replace with correct byte range if available
|
|
204
|
+
replacement: format!("[{}]({})", &cap[2], &cap[1]),
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Part 2: Check for malformed link attempts where user intent is clear
|
|
210
|
+
let malformed_attempts = self.detect_malformed_link_attempts(line);
|
|
211
|
+
for (start, len, url, text) in malformed_attempts {
|
|
212
|
+
// Calculate precise character range for the malformed syntax
|
|
213
|
+
let (start_line, start_col, end_line, end_col) =
|
|
214
|
+
calculate_match_range(line_num + 1, line, start, len);
|
|
215
|
+
|
|
216
|
+
warnings.push(LintWarning {
|
|
217
|
+
rule_name: Some(self.name()),
|
|
218
|
+
message: "Malformed link syntax".to_string(),
|
|
219
|
+
line: start_line,
|
|
220
|
+
column: start_col,
|
|
221
|
+
end_line,
|
|
222
|
+
end_column: end_col,
|
|
223
|
+
severity: Severity::Warning,
|
|
224
|
+
fix: Some(Fix {
|
|
225
|
+
range: (0..0), // TODO: Replace with correct byte range if available
|
|
226
|
+
replacement: format!("[{}]({})", text, url),
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Ok(warnings)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fn fix(&self, ctx: &crate::lint_context::LintContext) -> Result<String, LintError> {
|
|
236
|
+
let content = ctx.content;
|
|
237
|
+
let mut result = content.to_string();
|
|
238
|
+
let mut offset: usize = 0;
|
|
239
|
+
|
|
240
|
+
for (line_num, column, text, url) in Self::find_reversed_links(content) {
|
|
241
|
+
// Calculate absolute position in original content
|
|
242
|
+
let mut pos = 0;
|
|
243
|
+
for (i, line) in content.lines().enumerate() {
|
|
244
|
+
if i + 1 == line_num {
|
|
245
|
+
pos += column - 1;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
pos += line.len() + 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if !self.is_in_code_block(content, pos) {
|
|
252
|
+
let adjusted_pos = pos + offset;
|
|
253
|
+
let original_len = format!("({})[{}]", url, text).len();
|
|
254
|
+
let replacement = format!("[{}]({})", text, url);
|
|
255
|
+
result.replace_range(adjusted_pos..adjusted_pos + original_len, &replacement);
|
|
256
|
+
// Update offset based on the difference in lengths
|
|
257
|
+
if replacement.len() > original_len {
|
|
258
|
+
offset += replacement.len() - original_len;
|
|
259
|
+
} else {
|
|
260
|
+
offset = offset.saturating_sub(original_len - replacement.len());
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Ok(result)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn as_any(&self) -> &dyn std::any::Any {
|
|
269
|
+
self
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn from_config(_config: &crate::config::Config) -> Box<dyn Rule>
|
|
273
|
+
where
|
|
274
|
+
Self: Sized,
|
|
275
|
+
{
|
|
276
|
+
Box::new(MD011NoReversedLinks)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#[cfg(test)]
|
|
281
|
+
mod tests {
|
|
282
|
+
use super::*;
|
|
283
|
+
use crate::lint_context::LintContext;
|
|
284
|
+
|
|
285
|
+
#[test]
|
|
286
|
+
fn test_capture_group_order_fix() {
|
|
287
|
+
// This test confirms that the capture group order bug is fixed
|
|
288
|
+
// The regex pattern \(([^)]+)\)\[([^\]]+)\] captures:
|
|
289
|
+
// cap[1] = URL (inside parentheses)
|
|
290
|
+
// cap[2] = text (inside brackets)
|
|
291
|
+
// So (URL)[text] should become [text](URL)
|
|
292
|
+
|
|
293
|
+
let rule = MD011NoReversedLinks;
|
|
294
|
+
|
|
295
|
+
// Test with reversed link syntax
|
|
296
|
+
let content = "Check out (https://example.com)[this link] for more info.";
|
|
297
|
+
let ctx = LintContext::new(content);
|
|
298
|
+
|
|
299
|
+
// This should detect the reversed syntax
|
|
300
|
+
let result = rule.check(&ctx).unwrap();
|
|
301
|
+
assert_eq!(result.len(), 1);
|
|
302
|
+
assert!(result[0].message.contains("Reversed link syntax"));
|
|
303
|
+
|
|
304
|
+
// Verify the fix produces correct output
|
|
305
|
+
let fix = result[0].fix.as_ref().unwrap();
|
|
306
|
+
assert_eq!(fix.replacement, "[this link](https://example.com)");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#[test]
|
|
310
|
+
fn test_multiple_reversed_links() {
|
|
311
|
+
// Test multiple reversed links in the same content
|
|
312
|
+
let rule = MD011NoReversedLinks;
|
|
313
|
+
|
|
314
|
+
let content = "Visit (https://example.com)[Example] and (https://test.com)[Test Site].";
|
|
315
|
+
let ctx = LintContext::new(content);
|
|
316
|
+
|
|
317
|
+
let result = rule.check(&ctx).unwrap();
|
|
318
|
+
assert_eq!(result.len(), 2);
|
|
319
|
+
|
|
320
|
+
// Verify both fixes are correct
|
|
321
|
+
assert_eq!(result[0].fix.as_ref().unwrap().replacement, "[Example](https://example.com)");
|
|
322
|
+
assert_eq!(result[1].fix.as_ref().unwrap().replacement, "[Test Site](https://test.com)");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[test]
|
|
326
|
+
fn test_normal_links_not_flagged() {
|
|
327
|
+
// Test that normal link syntax is not flagged
|
|
328
|
+
let rule = MD011NoReversedLinks;
|
|
329
|
+
|
|
330
|
+
let content = "This is a normal [link](https://example.com) and another [link](https://test.com).";
|
|
331
|
+
let ctx = LintContext::new(content);
|
|
332
|
+
|
|
333
|
+
let result = rule.check(&ctx).unwrap();
|
|
334
|
+
assert_eq!(result.len(), 0);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#[test]
|
|
338
|
+
fn debug_capture_groups() {
|
|
339
|
+
// Debug test to understand capture group behavior
|
|
340
|
+
let pattern = r"\(([^)]+)\)\[([^\]]+)\]";
|
|
341
|
+
let regex = Regex::new(pattern).unwrap();
|
|
342
|
+
|
|
343
|
+
let test_text = "(https://example.com)[Click here]";
|
|
344
|
+
|
|
345
|
+
if let Some(cap) = regex.captures(test_text) {
|
|
346
|
+
println!("Full match: {}", &cap[0]);
|
|
347
|
+
println!("cap[1] (first group): {}", &cap[1]);
|
|
348
|
+
println!("cap[2] (second group): {}", &cap[2]);
|
|
349
|
+
|
|
350
|
+
// Current fix format
|
|
351
|
+
let current_fix = format!("[{}]({})", &cap[2], &cap[1]);
|
|
352
|
+
println!("Current fix produces: {}", current_fix);
|
|
353
|
+
|
|
354
|
+
// Test what the actual rule produces
|
|
355
|
+
let rule = MD011NoReversedLinks;
|
|
356
|
+
let ctx = LintContext::new(test_text);
|
|
357
|
+
let result = rule.check(&ctx).unwrap();
|
|
358
|
+
if !result.is_empty() {
|
|
359
|
+
println!("Rule fix produces: {}", result[0].fix.as_ref().unwrap().replacement);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#[test]
|
|
365
|
+
fn test_malformed_link_detection() {
|
|
366
|
+
let rule = MD011NoReversedLinks;
|
|
367
|
+
|
|
368
|
+
// Test wrong bracket types
|
|
369
|
+
let content = "Check out {https://example.com}[this website].";
|
|
370
|
+
let ctx = LintContext::new(content);
|
|
371
|
+
let result = rule.check(&ctx).unwrap();
|
|
372
|
+
assert_eq!(result.len(), 1);
|
|
373
|
+
assert!(result[0].message.contains("Malformed link syntax"));
|
|
374
|
+
|
|
375
|
+
// Test URL and text swapped
|
|
376
|
+
let content = "Visit [https://example.com](Click Here).";
|
|
377
|
+
let ctx = LintContext::new(content);
|
|
378
|
+
let result = rule.check(&ctx).unwrap();
|
|
379
|
+
assert_eq!(result.len(), 1);
|
|
380
|
+
assert!(result[0].message.contains("Malformed link syntax"));
|
|
381
|
+
|
|
382
|
+
// Test that valid links are not flagged
|
|
383
|
+
let content = "This is a [normal link](https://example.com).";
|
|
384
|
+
let ctx = LintContext::new(content);
|
|
385
|
+
let result = rule.check(&ctx).unwrap();
|
|
386
|
+
assert_eq!(result.len(), 0);
|
|
387
|
+
|
|
388
|
+
// Test that non-links are not flagged
|
|
389
|
+
let content = "Regular text with [brackets] and (parentheses).";
|
|
390
|
+
let ctx = LintContext::new(content);
|
|
391
|
+
let result = rule.check(&ctx).unwrap();
|
|
392
|
+
assert_eq!(result.len(), 0);
|
|
393
|
+
|
|
394
|
+
// Test that risky patterns are NOT flagged (conservative approach)
|
|
395
|
+
let content = "(example.com)is a test domain.";
|
|
396
|
+
let ctx = LintContext::new(content);
|
|
397
|
+
let result = rule.check(&ctx).unwrap();
|
|
398
|
+
assert_eq!(result.len(), 0);
|
|
399
|
+
|
|
400
|
+
let content = "(optional)parameter should not be flagged.";
|
|
401
|
+
let ctx = LintContext::new(content);
|
|
402
|
+
let result = rule.check(&ctx).unwrap();
|
|
403
|
+
assert_eq!(result.len(), 0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
#[test]
|
|
407
|
+
fn test_malformed_link_fixes() {
|
|
408
|
+
let rule = MD011NoReversedLinks;
|
|
409
|
+
|
|
410
|
+
// Test wrong bracket types fix
|
|
411
|
+
let content = "Check out {https://example.com}[this website].";
|
|
412
|
+
let ctx = LintContext::new(content);
|
|
413
|
+
let result = rule.check(&ctx).unwrap();
|
|
414
|
+
assert_eq!(result.len(), 1);
|
|
415
|
+
let fix = result[0].fix.as_ref().unwrap();
|
|
416
|
+
assert_eq!(fix.replacement, "[this website](https://example.com)");
|
|
417
|
+
|
|
418
|
+
// Test URL and text swapped fix
|
|
419
|
+
let content = "Visit [https://example.com](Click Here).";
|
|
420
|
+
let ctx = LintContext::new(content);
|
|
421
|
+
let result = rule.check(&ctx).unwrap();
|
|
422
|
+
assert_eq!(result.len(), 1);
|
|
423
|
+
let fix = result[0].fix.as_ref().unwrap();
|
|
424
|
+
assert_eq!(fix.replacement, "[Click Here](https://example.com)");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[test]
|
|
428
|
+
fn test_conservative_detection() {
|
|
429
|
+
let rule = MD011NoReversedLinks;
|
|
430
|
+
|
|
431
|
+
// Test that edge cases are not flagged
|
|
432
|
+
let content = "This (not-a-url)text should be ignored.";
|
|
433
|
+
let ctx = LintContext::new(content);
|
|
434
|
+
let result = rule.check(&ctx).unwrap();
|
|
435
|
+
assert_eq!(result.len(), 0);
|
|
436
|
+
|
|
437
|
+
let content = "Also [regular text](not a url) should be ignored.";
|
|
438
|
+
let ctx = LintContext::new(content);
|
|
439
|
+
let result = rule.check(&ctx).unwrap();
|
|
440
|
+
assert_eq!(result.len(), 0);
|
|
441
|
+
|
|
442
|
+
let content = "And {not-url}[not-text] should be ignored.";
|
|
443
|
+
let ctx = LintContext::new(content);
|
|
444
|
+
let result = rule.check(&ctx).unwrap();
|
|
445
|
+
assert_eq!(result.len(), 0);
|
|
446
|
+
}
|
|
447
|
+
}
|