From 7adb3ff2eca2f604040f943cb11031d10a706b89 Mon Sep 17 00:00:00 2001 From: Jack De Winter Date: Fri, 22 Dec 2023 10:24:36 -0800 Subject: [PATCH] https://github.com/jackdewinter/pymarkdown/issues/813 --- changelog.md | 2 + docs/rules/rule_md019.md | 9 ++ publish/coverage.json | 8 +- publish/test-results.json | 2 +- pymarkdown/plugins/rule_md_019.py | 25 ++++- test/rules/test_md019.py | 177 ++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index 89610f241..0b82437db 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,8 @@ for a version 1.0 release in early 2 - Extension: Strikethrough - [Issue 805](https://github.com/jackdewinter/pymarkdown/issues/805) - Extension: Task List Items +- [Issie 813](https://github.com/jackdewinter/pymarkdown/issues/813) + - Rule MD019 - Added fix options - [Issue 814](https://github.com/jackdewinter/pymarkdown/issues/814) - Rule MD021 - Added fix options - [Issue 816](https://github.com/jackdewinter/pymarkdown/issues/816) diff --git a/docs/rules/rule_md019.md b/docs/rules/rule_md019.md index e78f39e9d..ab2402471 100644 --- a/docs/rules/rule_md019.md +++ b/docs/rules/rule_md019.md @@ -5,6 +5,10 @@ | `md019` | | `no-multiple-space-atx` | +| Autofix Available | +| --- | +| Yes | + ## Summary Multiple spaces are present after hash character on Atx Heading. @@ -73,3 +77,8 @@ this rule was developed to ignore any leading spaces. The rationale is that if leading spaces before Atx Heading elements are not desired, there should only be one rule's configuration that needs to be set to enforce that. + +## Fix Description + +Any instances of 1+ space characters within a normal AtxHeading are replaced with +a single space character. diff --git a/publish/coverage.json b/publish/coverage.json index 47c37a12c..7d6b424da 100644 --- a/publish/coverage.json +++ b/publish/coverage.json @@ -2,12 +2,12 @@ "projectName": "pymarkdown", "reportSource": "pytest", "branchLevel": { - "totalMeasured": 4513, - "totalCovered": 4513 + "totalMeasured": 4515, + "totalCovered": 4515 }, "lineLevel": { - "totalMeasured": 18589, - "totalCovered": 18589 + "totalMeasured": 18594, + "totalCovered": 18594 } } diff --git a/publish/test-results.json b/publish/test-results.json index e5038203a..d7f86413c 100644 --- a/publish/test-results.json +++ b/publish/test-results.json @@ -1260,7 +1260,7 @@ }, { "name": "test.rules.test_md019", - "totalTests": 5, + "totalTests": 9, "failedTests": 0, "errorTests": 0, "skippedTests": 0, diff --git a/pymarkdown/plugins/rule_md_019.py b/pymarkdown/plugins/rule_md_019.py index 48eea9eb7..1ccac2a26 100644 --- a/pymarkdown/plugins/rule_md_019.py +++ b/pymarkdown/plugins/rule_md_019.py @@ -5,7 +5,7 @@ from typing import Optional, cast from pymarkdown.general.parser_helper import ParserHelper -from pymarkdown.plugin_manager.plugin_details import PluginDetails +from pymarkdown.plugin_manager.plugin_details import PluginDetailsV2 from pymarkdown.plugin_manager.plugin_scan_context import PluginScanContext from pymarkdown.plugin_manager.rule_plugin import RulePlugin from pymarkdown.tokens.atx_heading_markdown_token import AtxHeadingMarkdownToken @@ -23,18 +23,18 @@ def __init__(self) -> None: super().__init__() self.__atx_heading_token: Optional[MarkdownToken] = None - def get_details(self) -> PluginDetails: + def get_details(self) -> PluginDetailsV2: """ Get the details for the plugin. """ - return PluginDetails( + return PluginDetailsV2( plugin_name="no-multiple-space-atx", plugin_id="MD019", plugin_enabled_by_default=True, plugin_description="Multiple spaces are present after hash character on Atx Heading.", plugin_version="0.5.0", - plugin_interface_version=1, plugin_url="https://github.com/jackdewinter/pymarkdown/blob/main/docs/rules/rule_md019.md", + plugin_supports_fix=True, ) def starting_new_file(self) -> None: @@ -43,6 +43,21 @@ def starting_new_file(self) -> None: """ self.__atx_heading_token = None + def __report( + self, context: PluginScanContext, text_token: TextMarkdownToken + ) -> None: + assert self.__atx_heading_token is not None + if context.in_fix_mode: + self.register_fix_token_request( + context, + text_token, + "next_token", + "extracted_whitespace", + " ", + ) + else: + self.report_next_token_error(context, self.__atx_heading_token) + def next_token(self, context: PluginScanContext, token: MarkdownToken) -> None: """ Event that a new token is being processed. @@ -59,4 +74,4 @@ def next_token(self, context: PluginScanContext, token: MarkdownToken) -> None: text_token.extracted_whitespace ) if self.__atx_heading_token and len(resolved_extracted_whitespace) > 1: - self.report_next_token_error(context, self.__atx_heading_token) + self.__report(context, text_token) diff --git a/test/rules/test_md019.py b/test/rules/test_md019.py index 447f66406..af0201982 100644 --- a/test/rules/test_md019.py +++ b/test/rules/test_md019.py @@ -3,11 +3,14 @@ """ import os from test.markdown_scanner import MarkdownScanner +from test.utils import assert_file_is_as_expected, copy_to_temp_file import pytest # pylint: disable=too-many-lines +source_path = os.path.join("test", "resources", "rules", "md019") + os.sep + @pytest.mark.rules def test_md019_good_single_spacing(): @@ -74,6 +77,47 @@ def test_md019_bad_multiple_spacing(): ) +@pytest.mark.rules +def test_md019_bad_multiple_spacing_fix(): + """ + Test to make sure this rule does not trigger with a document that + contains Atx Headings with multiple spaces before text. + """ + + # Arrange + scanner = MarkdownScanner() + with copy_to_temp_file(source_path + "multiple_spacing.md") as temp_source_path: + original_file_contents = """# Heading 1 + +## Heading 2 +""" + assert_file_is_as_expected(temp_source_path, original_file_contents) + + supplied_arguments = [ + "-x-fix", + "scan", + temp_source_path, + ] + + expected_return_code = 3 + expected_output = f"Fixed: {temp_source_path}" + expected_error = "" + + expected_file_contents = """# Heading 1 + +## Heading 2 +""" + + # Act + execute_results = scanner.invoke_main(arguments=supplied_arguments) + + # Assert + execute_results.assert_results( + expected_output, expected_error, expected_return_code + ) + assert_file_is_as_expected(temp_source_path, expected_file_contents) + + def test_md019_bad_multiple_spacing_with_inline(): """ Test to make sure this rule does not trigger with a document that @@ -109,6 +153,49 @@ def test_md019_bad_multiple_spacing_with_inline(): ) +@pytest.mark.rules +def test_md019_bad_multiple_spacing_with_inline_fix(): + """ + Test to make sure this rule does not trigger with a document that + contains Atx Headings with multiple spaces before text. + """ + + # Arrange + scanner = MarkdownScanner() + with copy_to_temp_file( + source_path + "multiple_spacing_with_inline.md" + ) as temp_source_path: + original_file_contents = """# Heading *number* 1 + +## Heading *number* 2 +""" + assert_file_is_as_expected(temp_source_path, original_file_contents) + + supplied_arguments = [ + "-x-fix", + "scan", + temp_source_path, + ] + + expected_return_code = 3 + expected_output = f"Fixed: {temp_source_path}" + expected_error = "" + + expected_file_contents = """# Heading *number* 1 + +## Heading *number* 2 +""" + + # Act + execute_results = scanner.invoke_main(arguments=supplied_arguments) + + # Assert + execute_results.assert_results( + expected_output, expected_error, expected_return_code + ) + assert_file_is_as_expected(temp_source_path, expected_file_contents) + + def test_md019_bad_multiple_spacing_with_indent(): """ Test to make sure this rule does not trigger with a document that @@ -146,6 +233,51 @@ def test_md019_bad_multiple_spacing_with_indent(): ) +@pytest.mark.rules +def test_md019_bad_multiple_spacing_with_indent_fix(): + """ + Test to make sure this rule does not trigger with a document that + contains Atx Headings with multiple spaces before text. + """ + + # Arrange + scanner = MarkdownScanner() + with copy_to_temp_file( + source_path + "multiple_spacing_with_indent.md" + ) as temp_source_path: + original_file_contents = """ # Heading 1 + + ## Heading 2 +""" + assert_file_is_as_expected(temp_source_path, original_file_contents) + + supplied_arguments = [ + "--disable-rules", + "md023", + "-x-fix", + "scan", + temp_source_path, + ] + + expected_return_code = 3 + expected_output = f"Fixed: {temp_source_path}" + expected_error = "" + + expected_file_contents = """ # Heading 1 + + ## Heading 2 +""" + + # Act + execute_results = scanner.invoke_main(arguments=supplied_arguments) + + # Assert + execute_results.assert_results( + expected_output, expected_error, expected_return_code + ) + assert_file_is_as_expected(temp_source_path, expected_file_contents) + + def test_md019_bad_single_space_single_tab(): """ Test to make sure this rule does trigger with a document that @@ -180,3 +312,48 @@ def test_md019_bad_single_space_single_tab(): execute_results.assert_results( expected_output, expected_error, expected_return_code ) + + +@pytest.mark.rules +def test_md019_bad_single_space_single_tab_fix(): + """ + Test to make sure this rule does not trigger with a document that + contains Atx Headings with multiple spaces before text. + """ + + # Arrange + scanner = MarkdownScanner() + with copy_to_temp_file( + source_path + "single_space_single_tab.md" + ) as temp_source_path: + original_file_contents = """# \tHeading 1 + +## \tHeading 2 +""" + assert_file_is_as_expected(temp_source_path, original_file_contents) + + supplied_arguments = [ + "--disable-rules", + "md010", + "-x-fix", + "scan", + temp_source_path, + ] + + expected_return_code = 3 + expected_output = f"Fixed: {temp_source_path}" + expected_error = "" + + expected_file_contents = """# Heading 1 + +## Heading 2 +""" + + # Act + execute_results = scanner.invoke_main(arguments=supplied_arguments) + + # Assert + execute_results.assert_results( + expected_output, expected_error, expected_return_code + ) + assert_file_is_as_expected(temp_source_path, expected_file_contents)