diff --git a/newdocs/src/changelog.md b/newdocs/src/changelog.md index f6a85b51c..b0a563305 100644 --- a/newdocs/src/changelog.md +++ b/newdocs/src/changelog.md @@ -8,13 +8,24 @@ ### Fixed -- [None](https://github.com/jackdewinter/pymarkdown/issues/1120) -- https://github.com/jackdewinter/pymarkdown/issues/1122 -- https://github.com/jackdewinter/pymarkdown/issues/1123 -- https://github.com/jackdewinter/pymarkdown/issues/1124 -https://github.com/jackdewinter/pymarkdown/issues/1125 -https://github.com/jackdewinter/pymarkdown/issues/1126 -https://github.com/jackdewinter/pymarkdown/issues/1127 +- [Issue 1120](https://github.com/jackdewinter/pymarkdown/issues/1120) + - within Block-List, thematic break can sometimes not report newlines to the + list block +- [Issue 1122](https://github.com/jackdewinter/pymarkdown/issues/1122) + - opening a fenced code block in a Bq-List-Bq was closing the outer BQ +- [Issue 1123](https://github.com/jackdewinter/pymarkdown/issues/1123) + - in some cases within a Bq-List-Bq, not counting the newlines properly +- [Issue 1124](https://github.com/jackdewinter/pymarkdown/issues/1124) + - list items within a Bq-List-Bq can have incorrect starting text regarding + the innermost block +- [Issue 1125](https://github.com/jackdewinter/pymarkdown/issues/1125) + - parsing of blank lines within Bq-List-Bq does not always add the right + newlines to the list +- [Issue 1126](https://github.com/jackdewinter/pymarkdown/issues/1126) + - under some circumstances, with a Bq-List-Bq, thematic break can cause + the block quote to close +- [Issue 1127](https://github.com/jackdewinter/pymarkdown/issues/1127) + - rehydration can be wrong with indented blocks in Bq-List-Bq ### Changed diff --git a/publish/coverage.json b/publish/coverage.json index 292a4a498..5da73cade 100644 --- a/publish/coverage.json +++ b/publish/coverage.json @@ -2,12 +2,12 @@ "projectName": "pymarkdown", "reportSource": "pytest", "branchLevel": { - "totalMeasured": 4941, - "totalCovered": 4941 + "totalMeasured": 5023, + "totalCovered": 5023 }, "lineLevel": { - "totalMeasured": 19691, - "totalCovered": 19691 + "totalMeasured": 19960, + "totalCovered": 19960 } } diff --git a/publish/pylint_suppression.json b/publish/pylint_suppression.json index 8f868ded9..910010854 100644 --- a/publish/pylint_suppression.json +++ b/publish/pylint_suppression.json @@ -15,7 +15,7 @@ }, "pymarkdown/application_logging.py": {}, "pymarkdown/block_quotes/block_quote_count_helper.py": { - "too-many-arguments": 7 + "too-many-arguments": 8 }, "pymarkdown/block_quotes/block_quote_data.py": {}, "pymarkdown/block_quotes/block_quote_non_fenced_helper.py": { @@ -24,7 +24,8 @@ "too-many-locals": 1 }, "pymarkdown/block_quotes/block_quote_processor.py": { - "too-many-arguments": 4 + "too-many-arguments": 5, + "too-many-locals": 2 }, "pymarkdown/coalesce/coalesce_processor.py": { "too-few-public-methods": 1 @@ -320,7 +321,9 @@ "pymarkdown/plugins/rule_md_030.py": { "too-many-instance-attributes": 1 }, - "pymarkdown/plugins/rule_md_031.py": {}, + "pymarkdown/plugins/rule_md_031.py": { + "too-many-instance-attributes": 1 + }, "pymarkdown/plugins/rule_md_032.py": {}, "pymarkdown/plugins/rule_md_033.py": {}, "pymarkdown/plugins/rule_md_034.py": { @@ -483,7 +486,7 @@ "too-many-boolean-expressions": 1 }, "pymarkdown/transform_markdown/transform_list_block.py": { - "too-many-arguments": 2 + "too-many-arguments": 4 }, "pymarkdown/transform_markdown/transform_new_list_item.py": { "too-few-public-methods": 1, @@ -496,11 +499,11 @@ "pymarkdown/version.py": {} }, "disables-by-name": { - "too-many-instance-attributes": 24, + "too-many-instance-attributes": 25, "too-many-public-methods": 4, "too-few-public-methods": 39, - "too-many-arguments": 227, - "too-many-locals": 40, + "too-many-arguments": 231, + "too-many-locals": 42, "chained-comparison": 1, "too-many-boolean-expressions": 2, "protected-access": 25, diff --git a/publish/test-results.json b/publish/test-results.json index 8c6b13b9d..d4dc4ade9 100644 --- a/publish/test-results.json +++ b/publish/test-results.json @@ -1300,7 +1300,7 @@ }, { "name": "test.rules.test_md027", - "totalTests": 109, + "totalTests": 110, "failedTests": 0, "errorTests": 0, "skippedTests": 0, @@ -1364,7 +1364,7 @@ }, { "name": "test.rules.test_md031", - "totalTests": 25, + "totalTests": 110, "failedTests": 0, "errorTests": 0, "skippedTests": 0, @@ -1620,10 +1620,10 @@ }, { "name": "test.test_markdown_extra", - "totalTests": 148, + "totalTests": 163, "failedTests": 0, "errorTests": 0, - "skippedTests": 0, + "skippedTests": 1, "elapsedTimeInMilliseconds": 0 }, { @@ -1692,7 +1692,7 @@ }, { "name": "test.tokens.test_markdown_token", - "totalTests": 3, + "totalTests": 5, "failedTests": 0, "errorTests": 0, "skippedTests": 0, diff --git a/pymarkdown/block_quotes/block_quote_count_helper.py b/pymarkdown/block_quotes/block_quote_count_helper.py index bb9e4c42e..66d5e76bd 100644 --- a/pymarkdown/block_quotes/block_quote_count_helper.py +++ b/pymarkdown/block_quotes/block_quote_count_helper.py @@ -24,6 +24,9 @@ ParagraphStackToken, ) +# pylint: disable=too-many-lines + + POGGER = ParserLogger(logging.getLogger(__name__)) @@ -149,6 +152,41 @@ def __handle_bq_whitespace(adjusted_line: str, start_index: int) -> Tuple[str, i start_index += 1 return adjusted_line, start_index + # pylint: disable=too-many-arguments + @staticmethod + def __xx( + parser_state: ParserState, + adjusted_line: str, + start_index: int, + current_count: int, + stack_count: int, + last_block_quote_index: int, + ) -> Tuple[bool, int, int, int]: + ( + continue_processing, + start_index, + ) = BlockQuoteCountHelper.__is_special_double_block_case( + parser_state, adjusted_line, start_index, current_count, stack_count + ) + if not continue_processing and current_count < stack_count: + continue_proc, stack_token_index = BlockQuoteCountHelper.__xx_part_one( + parser_state, start_index, current_count + ) + if continue_proc: + current_count, start_index, last_block_quote_index = ( + BlockQuoteCountHelper.__xx_part_two( + parser_state, + stack_token_index, + start_index, + current_count, + stack_count, + last_block_quote_index, + ) + ) + return continue_processing, current_count, start_index, last_block_quote_index + + # pylint: enable=too-many-arguments + # pylint: disable=too-many-arguments @staticmethod def __should_continue_processing( @@ -214,14 +252,17 @@ def __should_continue_processing( ): ( continue_processing, + current_count, + start_index, + last_block_quote_index, + ) = BlockQuoteCountHelper.__xx( + parser_state, + adjusted_line, start_index, - ) = BlockQuoteCountHelper.__is_special_double_block_case( - parser_state, adjusted_line, start_index, current_count, stack_count + current_count, + stack_count, + last_block_quote_index, ) - if not continue_processing and current_count < stack_count: - continue_proc, stack_token_index = BlockQuoteCountHelper.__xx_part_one(parser_state, start_index, current_count, stack_count) - if continue_proc: - current_count, start_index, last_block_quote_index = BlockQuoteCountHelper.__xx_part_two(parser_state, stack_token_index, start_index, current_count, stack_count, last_block_quote_index) else: continue_processing = True return ( @@ -234,47 +275,77 @@ def __should_continue_processing( ) @staticmethod - def __xx_part_one(parser_state:ParserState, start_index, current_count, stack_count): + def __xx_part_one( + parser_state: ParserState, start_index: int, current_count: int + ) -> Tuple[bool, int]: if parser_state.token_stack[-1].is_fenced_code_block: return False, -1 - block_quote_character_count = ParserHelper.count_characters_in_text(parser_state.original_line_to_parse[:start_index], ">") + assert parser_state.original_line_to_parse is not None + block_quote_character_count = ParserHelper.count_characters_in_text( + parser_state.original_line_to_parse[:start_index], ">" + ) if block_quote_character_count > current_count: return False, -1 count_block_quotes = 0 - for stack_token_index in range(len(parser_state.token_stack)): - if parser_state.token_stack[stack_token_index].is_block_quote: + found_index = -2 + for stack_token_index, stack_token in enumerate( + parser_state.token_stack + ): # pragma: no cover + if stack_token.is_block_quote: count_block_quotes += 1 if count_block_quotes == block_quote_character_count: + found_index = stack_token_index break - assert stack_token_index != len(parser_state.token_stack), "should have completed before this" - stack_token_index += 1 - return not parser_state.token_stack[stack_token_index].is_block_quote, stack_token_index + assert found_index != -2 + assert found_index != len( + parser_state.token_stack + ), "should have completed before this" + found_index += 1 + return ( + not parser_state.token_stack[found_index].is_block_quote, + found_index, + ) @staticmethod - def __xx_part_two(parser_state:ParserState, stack_index, start_index, current_count, stack_count, last_block_quote_index): + def __xx_part_two( + parser_state: ParserState, + stack_index: int, + start_index: int, + current_count: int, + stack_count: int, + last_block_quote_index: int, + ) -> Tuple[int, int, int]: # At this point, we have a "prefix", which may be partial, that has the # current_count of > characters, and ends with a list. If we are here, # we know that previous lines have had at least one more > character and # counted block quote. - assert parser_state.token_stack[stack_index].is_list, "If not bq, must be a list." + assert parser_state.token_stack[ + stack_index + ].is_list, "If not bq, must be a list." while parser_state.token_stack[stack_index].is_list: stack_index += 1 - embedded_list_stack_token = parser_state.token_stack[stack_index-1] - if parser_state.original_line_to_parse[start_index:embedded_list_stack_token.indent_level].strip(): + embedded_list_stack_token = cast( + ListStackToken, parser_state.token_stack[stack_index - 1] + ) + assert parser_state.original_line_to_parse is not None + if parser_state.original_line_to_parse[ + start_index : embedded_list_stack_token.indent_level + ].strip(): return current_count, start_index, last_block_quote_index assert current_count + 1 == stack_count if ( - parser_state.original_line_to_parse[ - embedded_list_stack_token.indent_level - ] + parser_state.original_line_to_parse[embedded_list_stack_token.indent_level] != ">" ): return current_count, start_index, last_block_quote_index last_block_quote_index = embedded_list_stack_token.indent_level + 1 if last_block_quote_index < len(parser_state.original_line_to_parse): - character_after_block_quote = parser_state.original_line_to_parse[last_block_quote_index] - if character_after_block_quote == " ": - last_block_quote_index += 1 + character_after_block_quote = parser_state.original_line_to_parse[ + last_block_quote_index + ] + assert character_after_block_quote == " " + # if character_after_block_quote == " ": + last_block_quote_index += 1 return current_count + 1, last_block_quote_index, last_block_quote_index @@ -389,13 +460,14 @@ def __increase_stack( stack_count, block_quote_data, ) - if not skip: - block_quote_data = BlockQuoteCountHelper.decrease_stack_to_level( - parser_state, - block_quote_data.current_count, - stack_count, - container_level_tokens, - ) + assert not skip + # if not skip: + block_quote_data = BlockQuoteCountHelper.decrease_stack_to_level( + parser_state, + block_quote_data.current_count, + stack_count, + container_level_tokens, + ) POGGER.debug( "container_level_tokens>>$", container_level_tokens, @@ -955,21 +1027,24 @@ def __calculate_eligible_stack_hard_limit( indent_text_count += delta length_of_available_whitespace -= delta extra_consumed_whitespace += delta - if adjust_current_block_quote: - POGGER.debug( - "__calculate_stack_hard_limit>>last_block_token>>$", - parser_state.token_stack[last_bq_index].matching_markdown_token, - ) - block_token = cast( - BlockQuoteMarkdownToken, - parser_state.token_stack[last_bq_index].matching_markdown_token, - ) - block_token.add_bleading_spaces( - ParserHelper.repeat_string(ParserHelper.space_character, delta), True - ) - POGGER.debug( - "__calculate_stack_hard_limit>>last_block_token>>$", block_token - ) + + assert not adjust_current_block_quote + _ = last_bq_index + # if adjust_current_block_quote: + # POGGER.debug( + # "__calculate_stack_hard_limit>>last_block_token>>$", + # parser_state.token_stack[last_bq_index].matching_markdown_token, + # ) + # block_token = cast( + # BlockQuoteMarkdownToken, + # parser_state.token_stack[last_bq_index].matching_markdown_token, + # ) + # block_token.add_bleading_spaces( + # ParserHelper.repeat_string(ParserHelper.space_character, delta), True + # ) + # POGGER.debug( + # "__calculate_stack_hard_limit>>last_block_token>>$", block_token + # ) return ( current_stack_index, diff --git a/pymarkdown/block_quotes/block_quote_non_fenced_helper.py b/pymarkdown/block_quotes/block_quote_non_fenced_helper.py index 5f3875368..95c8f0127 100644 --- a/pymarkdown/block_quotes/block_quote_non_fenced_helper.py +++ b/pymarkdown/block_quotes/block_quote_non_fenced_helper.py @@ -600,26 +600,28 @@ def __adjust_2_fix_leading_spaces( ) -> Tuple[bool, str]: POGGER.debug("original_removed_text>>:$:", original_removed_text) POGGER.debug("adjusted_removed_text>>:$:", adjusted_removed_text) - if len(current_leading_spaces) > len(original_block_quote_bleading_spaces): - current_leading_spaces = current_leading_spaces[ - len(original_block_quote_bleading_spaces) : - ] - POGGER.debug("current_leading_spaces>>:$:", current_leading_spaces) - assert ( - current_leading_spaces[0] == "\n" - ), "In these cases, the leading spaces will always start with a \n." - current_leading_spaces = current_leading_spaces[1:] - POGGER.debug( - "current_leading_spaces>>:$:($)", - current_leading_spaces, - len(current_leading_spaces), - ) - special_case = True - if not extra_consumed_whitespace: - extra_consumed_whitespace = 0 - adjusted_removed_text = original_removed_text[ - len(current_leading_spaces) - extra_consumed_whitespace : - ] + assert len(current_leading_spaces) <= len(original_block_quote_bleading_spaces) + _ = extra_consumed_whitespace + # if len(current_leading_spaces) > len(original_block_quote_bleading_spaces): + # current_leading_spaces = current_leading_spaces[ + # len(original_block_quote_bleading_spaces) : + # ] + # POGGER.debug("current_leading_spaces>>:$:", current_leading_spaces) + # assert ( + # current_leading_spaces[0] == "\n" + # ), "In these cases, the leading spaces will always start with a \n." + # current_leading_spaces = current_leading_spaces[1:] + # POGGER.debug( + # "current_leading_spaces>>:$:($)", + # current_leading_spaces, + # len(current_leading_spaces), + # ) + # special_case = True + # if not extra_consumed_whitespace: + # extra_consumed_whitespace = 0 + # adjusted_removed_text = original_removed_text[ + # len(current_leading_spaces) - extra_consumed_whitespace : + # ] return special_case, adjusted_removed_text # pylint: enable=too-many-arguments diff --git a/pymarkdown/block_quotes/block_quote_processor.py b/pymarkdown/block_quotes/block_quote_processor.py index 1fb892111..f7414fe40 100644 --- a/pymarkdown/block_quotes/block_quote_processor.py +++ b/pymarkdown/block_quotes/block_quote_processor.py @@ -150,7 +150,9 @@ def __handle_block_quote_block_really_start( ), "If starting here, we need a block quote count." POGGER.debug("handle_block_quote_block>>block-start") POGGER.debug("original_line:>:$:<", grab_bag.original_line) - POGGER.debug("container_start_bq_count:>:$:<", grab_bag.container_start_bq_count) + POGGER.debug( + "container_start_bq_count:>:$:<", grab_bag.container_start_bq_count + ) ( adjusted_text_to_parse, adjusted_index_number, @@ -556,7 +558,11 @@ def __handle_block_quote_section( parser_state.token_stack[-1].is_fenced_code_block, parser_state.token_stack[-1].is_html_block, ) - POGGER.debug("block_quote_data>>:curr=$:stack=$:", block_quote_data.current_count, block_quote_data.stack_count) + POGGER.debug( + "block_quote_data>>:curr=$:stack=$:", + block_quote_data.current_count, + block_quote_data.stack_count, + ) POGGER.debug("start_index>>:$:", start_index) POGGER.debug("line_to_parse>>:$:", line_to_parse) POGGER.debug("last_block_quote_index>>:$:", last_block_quote_index) @@ -602,23 +608,45 @@ def __handle_block_quote_section( # pylint: enable=too-many-arguments @staticmethod - def __handle_existing_block_quote_fenced_special(parser_state, start_index, block_quote_data): - block_quote_character_count = ParserHelper.count_characters_in_text(parser_state.original_line_to_parse[:start_index], ">") - assert block_quote_character_count <= block_quote_data.current_count, "if not, overreach" + def __handle_existing_block_quote_fenced_special( + parser_state: ParserState, start_index: int, block_quote_data: BlockQuoteData + ) -> Tuple[bool, int]: + assert parser_state.original_line_to_parse is not None + block_quote_character_count = ParserHelper.count_characters_in_text( + parser_state.original_line_to_parse[:start_index], ">" + ) + assert ( + block_quote_character_count <= block_quote_data.current_count + ), "if not, overreach" count_block_quotes = 0 - for stack_token_index in range(len(parser_state.token_stack)): - if parser_state.token_stack[stack_token_index].is_block_quote: + find_token_index = 0 + for stack_token_index, stack_token in enumerate( + parser_state.token_stack + ): # pragma: no cover + if stack_token.is_block_quote: count_block_quotes += 1 if count_block_quotes == block_quote_character_count: + find_token_index = stack_token_index break - assert stack_token_index != len(parser_state.token_stack), "should have completed before this" - stack_token_index += 1 - process_fenced_block = parser_state.token_stack[stack_token_index].is_block_quote - return process_fenced_block, stack_token_index - + assert find_token_index != len( + parser_state.token_stack + ), "should have completed before this" + find_token_index += 1 + process_fenced_block = parser_state.token_stack[find_token_index].is_block_quote + return process_fenced_block, find_token_index + + # pylint: disable=too-many-arguments, too-many-locals @staticmethod - def __handle_existing_block_quote_fenced_special_part_two(parser_state:ParserState, stack_index, line_to_parse, start_index, block_quote_data, leaf_tokens, - container_level_tokens, avoid_block_starts) -> Tuple[ + def __handle_existing_block_quote_fenced_special_part_two( + parser_state: ParserState, + stack_index: int, + line_to_parse: str, + start_index: int, + block_quote_data: BlockQuoteData, + leaf_tokens: List[MarkdownToken], + container_level_tokens: List[MarkdownToken], + avoid_block_starts: bool, + ) -> Tuple[ str, int, List[MarkdownToken], @@ -636,36 +664,52 @@ def __handle_existing_block_quote_fenced_special_part_two(parser_state:ParserSta # current_count of > characters, and ends with a list. If we are here, # we know that previous lines have had at least one more > character and # counted block quote. - assert parser_state.token_stack[stack_index].is_list, "If not bq, must be a list." + assert parser_state.token_stack[ + stack_index + ].is_list, "If not bq, must be a list." while parser_state.token_stack[stack_index].is_list: stack_index += 1 - embedded_list_stack_token = parser_state.token_stack[stack_index-1] + embedded_list_stack_token = cast( + ListStackToken, parser_state.token_stack[stack_index - 1] + ) block_stack_token = parser_state.token_stack[stack_index] - block_markdown_token = cast(BlockQuoteMarkdownToken, block_stack_token.matching_markdown_token) - list_markdown_token = cast(ListStartMarkdownToken, embedded_list_stack_token.matching_markdown_token) - character_after_list = parser_state.original_line_to_parse[start_index:embedded_list_stack_token.indent_level].strip() + block_markdown_token = cast( + BlockQuoteMarkdownToken, block_stack_token.matching_markdown_token + ) + list_markdown_token = cast( + ListStartMarkdownToken, embedded_list_stack_token.matching_markdown_token + ) + assert parser_state.original_line_to_parse is not None + character_after_list = parser_state.original_line_to_parse[ + start_index : embedded_list_stack_token.indent_level + ].strip() assert not character_after_list assert block_quote_data.current_count + 1 == block_quote_data.stack_count sd = parser_state.original_line_to_parse[embedded_list_stack_token.indent_level] assert sd == ">" last_block_quote_index = embedded_list_stack_token.indent_level + 1 - character_after_block_quote = parser_state.original_line_to_parse[last_block_quote_index] - if character_after_block_quote == " ": - last_block_quote_index += 1 + character_after_block_quote = parser_state.original_line_to_parse[ + last_block_quote_index + ] + assert character_after_block_quote == " " + last_block_quote_index += 1 # character_after_block_quote = parser_state.original_line_to_parse[last_block_quote_index] - start_index = last_block_quote_index - text_removed_by_container = parser_state.original_line_to_parse[:start_index] + text_removed_by_container = parser_state.original_line_to_parse[ + :last_block_quote_index + ] block_markdown_token.add_bleading_spaces(text_removed_by_container) if block_markdown_token.weird_kludge_one: block_markdown_token.weird_kludge_one += 1 else: block_markdown_token.weird_kludge_one = 1 list_markdown_token.add_leading_spaces("") - block_quote_data = BlockQuoteData(block_quote_data.current_count + 1, block_quote_data.stack_count) + block_quote_data = BlockQuoteData( + block_quote_data.current_count + 1, block_quote_data.stack_count + ) return ( - line_to_parse[start_index:], - start_index, + line_to_parse[last_block_quote_index:], + last_block_quote_index, leaf_tokens, container_level_tokens, block_quote_data, @@ -678,7 +722,9 @@ def __handle_existing_block_quote_fenced_special_part_two(parser_state:ParserSta False, ) - # pylint: disable=too-many-arguments + # pylint: enable=too-many-arguments, too-many-locals + + # pylint: disable=too-many-arguments, too-many-locals @staticmethod def __handle_existing_block_quote( parser_state: ParserState, @@ -732,11 +778,26 @@ def __handle_existing_block_quote( ) process_fenced_block = parser_state.token_stack[-1].is_fenced_code_block - if process_fenced_block and block_quote_data.current_count < block_quote_data.stack_count: - process_fenced_block, stack_index = BlockQuoteProcessor.__handle_existing_block_quote_fenced_special(parser_state, start_index, block_quote_data) + if ( + process_fenced_block + and block_quote_data.current_count < block_quote_data.stack_count + ): + process_fenced_block, stack_index = ( + BlockQuoteProcessor.__handle_existing_block_quote_fenced_special( + parser_state, start_index, block_quote_data + ) + ) if not process_fenced_block: - return BlockQuoteProcessor.__handle_existing_block_quote_fenced_special_part_two(parser_state, stack_index, line_to_parse, start_index, block_quote_data, leaf_tokens, - container_level_tokens, avoid_block_starts) + return BlockQuoteProcessor.__handle_existing_block_quote_fenced_special_part_two( + parser_state, + stack_index, + line_to_parse, + start_index, + block_quote_data, + leaf_tokens, + container_level_tokens, + avoid_block_starts, + ) if not process_fenced_block: return BlockQuoteNonFencedHelper.handle_non_fenced_code_section( parser_state, @@ -787,7 +848,7 @@ def __handle_existing_block_quote( False, ) - # pylint: enable=too-many-arguments + # pylint: enable=too-many-arguments, too-many-locals @staticmethod def __handle_fenced_code_section( diff --git a/pymarkdown/container_blocks/container_block_non_leaf_processor.py b/pymarkdown/container_blocks/container_block_non_leaf_processor.py index edeb60f12..ed72cee03 100644 --- a/pymarkdown/container_blocks/container_block_non_leaf_processor.py +++ b/pymarkdown/container_blocks/container_block_non_leaf_processor.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging -from typing import Optional, Tuple, cast +from typing import List, Optional, Tuple, cast from pymarkdown.block_quotes.block_quote_processor import BlockQuoteProcessor from pymarkdown.container_blocks.container_block_nested_processor import ( @@ -23,6 +23,7 @@ from pymarkdown.list_blocks.list_block_processor import ListBlockProcessor from pymarkdown.tokens.block_quote_markdown_token import BlockQuoteMarkdownToken from pymarkdown.tokens.list_start_markdown_token import ListStartMarkdownToken +from pymarkdown.tokens.markdown_token import MarkdownToken POGGER = ParserLogger(logging.getLogger(__name__)) @@ -737,14 +738,9 @@ def __get_block_start_index( POGGER.debug(">>requeuing lines after looking for block start. returning.") if grab_bag.did_blank: - assert block_leaf_tokens and block_leaf_tokens[-1].is_blank_line, "should be a blank at the end" - POGGER.debug(">>already handled blank line. returning.") - grab_bag.extend_container_tokens_with_leaf_tokens() - stack_index = len(parser_state.token_stack) - 1 - if stack_index > 2 and parser_state.token_stack[stack_index].is_block_quote and parser_state.token_stack[stack_index-1].is_block_quote and\ - parser_state.token_stack[stack_index-2].is_list and \ - parser_state.token_stack[stack_index-2].matching_markdown_token.line_number != block_leaf_tokens[-1].line_number: - parser_state.token_stack[stack_index-2].matching_markdown_token.add_leading_spaces("") + ContainerBlockNonLeafProcessor.__get_block_start_index_handle_blank_line( + parser_state, grab_bag, block_leaf_tokens + ) grab_bag.can_continue = ( not grab_bag.requeue_line_info and not grab_bag.did_blank @@ -755,6 +751,32 @@ def __get_block_start_index( avoid_block_starts, ) + @staticmethod + def __get_block_start_index_handle_blank_line( + parser_state: ParserState, + grab_bag: ContainerGrabBag, + block_leaf_tokens: List[MarkdownToken], + ) -> None: + assert ( + block_leaf_tokens and block_leaf_tokens[-1].is_blank_line + ), "should be a blank at the end" + POGGER.debug(">>already handled blank line. returning.") + grab_bag.extend_container_tokens_with_leaf_tokens() + stack_index = len(parser_state.token_stack) - 1 + if ( + stack_index > 2 + and parser_state.token_stack[stack_index].is_block_quote + and parser_state.token_stack[stack_index - 1].is_block_quote + and parser_state.token_stack[stack_index - 2].is_list + ): + list_token = cast( + ListStartMarkdownToken, + parser_state.token_stack[stack_index - 2].matching_markdown_token, + ) + assert list_token is not None + if list_token.line_number != block_leaf_tokens[-1].line_number: + list_token.add_leading_spaces("") + @staticmethod def __process_list_in_progress( parser_state: ParserState, diff --git a/pymarkdown/file_scan_helper.py b/pymarkdown/file_scan_helper.py index 75fe3dd18..119c86554 100644 --- a/pymarkdown/file_scan_helper.py +++ b/pymarkdown/file_scan_helper.py @@ -667,15 +667,20 @@ def __look_for_collisions( def __apply_replacement_fix( self, + context: PluginScanContext, next_replacement: ReplaceTokensRecord, actual_tokens: List[MarkdownToken], ) -> None: start_index = actual_tokens.index(next_replacement.start_token) end_index = actual_tokens.index(next_replacement.end_token) + index_delta = end_index - start_index + 1 new_tokens = actual_tokens[:start_index] new_tokens.extend(next_replacement.replacement_tokens) - new_tokens.extend(actual_tokens[end_index + 1 :]) + end_tokens = actual_tokens[end_index + 1 :] + for next_token in end_tokens: + next_token.adjust_line_number(context, index_delta) + new_tokens.extend(end_tokens) actual_tokens.clear() actual_tokens.extend(new_tokens) @@ -683,6 +688,7 @@ def __apply_replacement_fix( # pylint: disable=too-many-arguments def __xx( self, + context: PluginScanContext, did_any_tokens_get_fixed: bool, replace_tokens_list: List[ReplaceTokensRecord], actual_tokens: List[MarkdownToken], @@ -698,7 +704,7 @@ def __xx( ) for next_replace_index in replace_tokens_list: did_any_tokens_get_fixed = True - self.__apply_replacement_fix(next_replace_index, actual_tokens) + self.__apply_replacement_fix(context, next_replace_index, actual_tokens) return did_any_tokens_get_fixed # pylint: enable=too-many-arguments @@ -736,6 +742,7 @@ def __process_file_fix_tokens_apply_fixes( if fix_debug: print("--") did_any_tokens_get_fixed = self.__xx( + context, did_any_tokens_get_fixed, replace_tokens_list, actual_tokens, diff --git a/pymarkdown/leaf_blocks/thematic_leaf_block_processor.py b/pymarkdown/leaf_blocks/thematic_leaf_block_processor.py index be911185a..7dfc2633c 100644 --- a/pymarkdown/leaf_blocks/thematic_leaf_block_processor.py +++ b/pymarkdown/leaf_blocks/thematic_leaf_block_processor.py @@ -89,8 +89,15 @@ def is_thematic_break( return thematic_break_character, end_of_break_index @staticmethod - def __handle_existing_paragraph_special(parser_state:ParserState, grab_bag:ContainerGrabBag, new_tokens:List[MarkdownToken]) -> None: - if parser_state.token_stack[-1].is_list and grab_bag.text_removed_by_container is not None: + def __handle_existing_paragraph_special( + parser_state: ParserState, + grab_bag: ContainerGrabBag, + new_tokens: List[MarkdownToken], + ) -> None: + if ( + parser_state.token_stack[-1].is_list + and grab_bag.text_removed_by_container is not None + ): stack_list_token = cast(ListStackToken, parser_state.token_stack[-1]) indent_delta = stack_list_token.indent_level - len( grab_bag.text_removed_by_container @@ -112,7 +119,10 @@ def __handle_existing_paragraph_special(parser_state:ParserState, grab_bag:Conta @staticmethod def __handle_existing_paragraph( - parser_state:ParserState, grab_bag:ContainerGrabBag, new_tokens:List[MarkdownToken], block_quote_data:BlockQuoteData + parser_state: ParserState, + grab_bag: ContainerGrabBag, + new_tokens: List[MarkdownToken], + block_quote_data: BlockQuoteData, ) -> List[MarkdownToken]: force_paragraph_close_if_present = ( block_quote_data.current_count == 0 and block_quote_data.stack_count > 0 diff --git a/pymarkdown/plugins/rule_md_027.py b/pymarkdown/plugins/rule_md_027.py index 8b40d733e..aa77b8790 100644 --- a/pymarkdown/plugins/rule_md_027.py +++ b/pymarkdown/plugins/rule_md_027.py @@ -476,6 +476,9 @@ def __handle_block_quote_end( # ) del self.__bq_line_index[num_container_tokens] del self.__container_tokens[-1] + end_token = cast(EndMarkdownToken, token) + if end_token.extra_end_data and num_container_tokens > 1: + self.__bq_line_index[num_container_tokens - 1] -= 1 def __get_last_block_quote(self) -> Optional[MarkdownToken]: return next( diff --git a/pymarkdown/plugins/rule_md_031.py b/pymarkdown/plugins/rule_md_031.py index eb9faaf71..bdcf054ff 100644 --- a/pymarkdown/plugins/rule_md_031.py +++ b/pymarkdown/plugins/rule_md_031.py @@ -2,9 +2,11 @@ Module to implement a plugin that ensures that blank lines surround fenced block quotes. """ -from typing import List, Optional, cast +from dataclasses import dataclass +from typing import List, Optional, Tuple, cast from pymarkdown.general.parser_helper import ParserHelper +from pymarkdown.general.position_marker import PositionMarker from pymarkdown.plugin_manager.plugin_details import ( PluginDetails, PluginDetailsV3, @@ -12,10 +14,33 @@ ) from pymarkdown.plugin_manager.plugin_scan_context import PluginScanContext from pymarkdown.plugin_manager.rule_plugin import RulePlugin +from pymarkdown.tokens.blank_line_markdown_token import BlankLineMarkdownToken +from pymarkdown.tokens.block_quote_markdown_token import BlockQuoteMarkdownToken +from pymarkdown.tokens.list_start_markdown_token import ListStartMarkdownToken from pymarkdown.tokens.markdown_token import EndMarkdownToken, MarkdownToken from pymarkdown.tokens.text_markdown_token import TextMarkdownToken +@dataclass +class ClosedContainerAdjustments: + """ + Keep track of line space used by already closed containers. + """ + + adjustment: int = 0 + + +@dataclass(frozen=True) +class PendingContainerAdjustment: + """ + Keep track of the adjustments we need to make on the container. + """ + + insert_index: int + leading_space_to_insert: str + + +# pylint: disable=too-many-instance-attributes class RuleMd031(RulePlugin): """ Class to implement a plugin that ensures that blank lines surround fenced block quotes. @@ -24,9 +49,15 @@ class RuleMd031(RulePlugin): def __init__(self) -> None: super().__init__() self.__trigger_in_list_items: bool = True - self.__end_fenced_code_block_token: Optional[MarkdownToken] = None + self.__end_fenced_code_block_token: Optional[EndMarkdownToken] = None self.__last_non_end_token: Optional[MarkdownToken] = None + self.__last_token: Optional[MarkdownToken] = None + self.__second_last_token: Optional[MarkdownToken] = None self.__container_token_stack: List[MarkdownToken] = [] + self.__pending_container_ends = 0 + self.__container_adjustments: List[List[PendingContainerAdjustment]] = [] + self.__closed_container_adjustments: List[ClosedContainerAdjustments] = [] + self.__end_tokens: List[EndMarkdownToken] = [] def get_details(self) -> PluginDetails: """ @@ -37,9 +68,10 @@ def get_details(self) -> PluginDetails: plugin_id="MD031", plugin_enabled_by_default=True, plugin_description="Fenced code blocks should be surrounded by blank lines", - plugin_version="0.6.0", + plugin_version="0.7.0", plugin_url="https://pymarkdown.readthedocs.io/en/latest/plugins/rule_md031.md", plugin_configuration="list_items", + plugin_supports_fix=True, ) def initialize_from_config(self) -> None: @@ -63,12 +95,139 @@ def starting_new_file(self) -> None: Event that the a new file to be scanned is starting. """ self.__last_non_end_token = None + self.__last_token = None self.__end_fenced_code_block_token = None self.__container_token_stack = [] + self.__container_adjustments = [] + self.__closed_container_adjustments = [] + self.__end_tokens = [] + self.__pending_container_ends = 0 - def __handle_fenced_code_block( + def __fix_spacing_special_case( self, context: PluginScanContext, token: MarkdownToken ) -> None: + assert ( + self.__last_token is not None + ), "Special case means at least a block token." + replacement_tokens = [ + BlankLineMarkdownToken( + extracted_whitespace="", position_marker=PositionMarker(0, 0, "") + ), + self.__last_token, + token, + ] + self.register_replace_tokens_request( + context, self.__last_token, token, replacement_tokens + ) + end_token = cast(EndMarkdownToken, self.__last_token) + block_quote_start_token = cast( + BlockQuoteMarkdownToken, end_token.start_markdown_token + ) + assert ( + block_quote_start_token.bleading_spaces is not None + ), "At least one line should have been processed." + split_bleading_spaces = block_quote_start_token.bleading_spaces.split("\n") + self.__container_adjustments[-1].append( + PendingContainerAdjustment( + len(split_bleading_spaces) - 1, split_bleading_spaces[-1].rstrip() + ) + ) + + def __fix_spacing_block_quote(self, token: MarkdownToken) -> None: + container_index = len(self.__container_token_stack) - 1 + block_quote_token = cast( + BlockQuoteMarkdownToken, self.__container_token_stack[container_index] + ) + assert ( + block_quote_token.bleading_spaces is not None + ), "At least one line should have been processed." + split_leading_space = block_quote_token.bleading_spaces.split("\n") + leading_space_insert_index = ( + token.line_number - block_quote_token.line_number + ) - self.__closed_container_adjustments[-1].adjustment + former_item_leading_space = split_leading_space[ + leading_space_insert_index + ].rstrip() + self.__container_adjustments[container_index].append( + PendingContainerAdjustment( + leading_space_insert_index, former_item_leading_space + ) + ) + + while ( + container_index > 0 + and not self.__container_token_stack[container_index - 1].is_list_start + ): + container_index -= 1 + + if ( + container_index > 0 + and self.__container_token_stack[container_index - 1].is_list_start + ): + leading_space_insert_index = ( + token.line_number + - self.__container_token_stack[container_index - 1].line_number + ) + self.__container_adjustments[container_index - 1].append( + PendingContainerAdjustment(leading_space_insert_index, "") + ) + + def __fix_spacing_list(self, token: MarkdownToken) -> None: + initial_index = container_index = len(self.__container_token_stack) - 1 + while ( + container_index > 0 + and self.__container_token_stack[container_index - 1].is_list_start + ): + container_index -= 1 + if container_index: + block_quote_index = cast( + BlockQuoteMarkdownToken, + self.__container_token_stack[container_index - 1], + ) + index = token.line_number - block_quote_index.line_number + assert block_quote_index.bleading_spaces is not None + split_bleading_spaces = block_quote_index.bleading_spaces.split("\n") + self.__container_adjustments[container_index - 1].append( + PendingContainerAdjustment(index, split_bleading_spaces[index].rstrip()) + ) + adjust = ( + 0 + if initial_index >= 1 + and not container_index + and self.__closed_container_adjustments[-1].adjustment + else 1 + ) + index = ( + token.line_number - self.__container_token_stack[initial_index].line_number + ) + index -= self.__closed_container_adjustments[-1].adjustment + self.__container_adjustments[initial_index].append( + PendingContainerAdjustment(index - adjust, "") + ) + + def __fix_spacing( + self, context: PluginScanContext, token: MarkdownToken, special_case: bool + ) -> None: + if special_case: + self.__fix_spacing_special_case(context, token) + return + if self.__container_token_stack: + if self.__container_token_stack[-1].is_block_quote_start: + self.__fix_spacing_block_quote(token) + else: + self.__fix_spacing_list(token) + + replacement_tokens = [ + BlankLineMarkdownToken( + extracted_whitespace="", position_marker=PositionMarker(0, 0, "") + ), + token, + ] + self.register_replace_tokens_request(context, token, token, replacement_tokens) + + def __handle_fenced_code_block( + self, context: PluginScanContext, token: MarkdownToken, special_case: bool + ) -> None: can_trigger = ( self.__trigger_in_list_items @@ -81,63 +240,195 @@ def __handle_fenced_code_block( and not self.__last_non_end_token.is_blank_line and can_trigger ): - self.report_next_token_error(context, token) + if context.in_fix_mode: + self.__fix_spacing(context, token, special_case) + else: + self.report_next_token_error(context, token) + + def __calculate_deltas(self) -> Tuple[int, int]: + line_number_delta = 0 + assert self.__last_non_end_token is not None + if self.__last_non_end_token.is_text: + text_token = cast(TextMarkdownToken, self.__last_non_end_token) + line_number_delta = ( + text_token.token_text.count(ParserHelper.newline_character) + 2 + ) + else: + assert self.__last_non_end_token.is_fenced_code_block + line_number_delta = 1 + + assert self.__end_fenced_code_block_token is not None + column_number_delta = ( + self.__end_fenced_code_block_token.start_markdown_token.column_number + ) + start_token = cast( + EndMarkdownToken, + self.__end_fenced_code_block_token.start_markdown_token, + ) + if start_token.extracted_whitespace: + column_number_delta -= len(start_token.extracted_whitespace) + if ( + self.__end_fenced_code_block_token + and self.__end_fenced_code_block_token.extracted_whitespace + ): + column_number_delta += len( + self.__end_fenced_code_block_token.extracted_whitespace + ) + column_number_delta = -(column_number_delta) + + return line_number_delta, column_number_delta def __handle_end_fenced_code_block( self, context: PluginScanContext, token: MarkdownToken ) -> None: # sourcery skip: extract-method - can_trigger = True + can_trigger = not token.is_end_of_stream if ( self.__container_token_stack and self.__container_token_stack[-1].is_list_start ): can_trigger = self.__trigger_in_list_items - if not token.is_blank_line and can_trigger: - line_number_delta = 0 - assert self.__last_non_end_token - if self.__last_non_end_token.is_text: - text_token = cast(TextMarkdownToken, self.__last_non_end_token) + if ( + not token.is_blank_line + and self.__end_fenced_code_block_token is not None + and not self.__end_fenced_code_block_token.was_forced + and can_trigger + ): + if context.in_fix_mode: + self.__fix_spacing(context, token, False) + else: + assert self.__last_non_end_token + line_number_delta, column_number_delta = self.__calculate_deltas() + self.report_next_token_error( + context, + self.__end_fenced_code_block_token.start_markdown_token, + line_number_delta=line_number_delta, + column_number_delta=column_number_delta, + ) + self.__end_fenced_code_block_token = None + + def __process_pending_container_end_adjustment( + self, + context: PluginScanContext, + next_container_adjustment_list: List[PendingContainerAdjustment], + ) -> None: + if self.__container_token_stack[-1].is_block_quote_start: + token_part_name = "bleading_spaces" + block_quote_token = cast( + BlockQuoteMarkdownToken, self.__container_token_stack[-1] + ) + assert ( + block_quote_token.bleading_spaces is not None + ), "Pending containers means this should at least have a newline in it." + split_spaces = block_quote_token.bleading_spaces.split("\n") + else: + token_part_name = "leading_spaces" + list_token = cast(ListStartMarkdownToken, self.__container_token_stack[-1]) + assert ( + list_token.leading_spaces is not None + ), "Pending containers means this should at least have a newline in it." + split_spaces = list_token.leading_spaces.split("\n") + + for next_container_adjustment in next_container_adjustment_list[::-1]: + split_spaces.insert( + next_container_adjustment.insert_index, + next_container_adjustment.leading_space_to_insert, + ) + + self.register_fix_token_request( + context, + self.__container_token_stack[-1], + "next_token", + token_part_name, + "\n".join(split_spaces), + ) + + def __process_pending_container_end_block_quote(self, token: MarkdownToken) -> None: + for stack_index in range(len(self.__container_token_stack) - 2, -1, -1): + current_stack_token = self.__container_token_stack[stack_index] + if current_stack_token.is_block_quote_start: line_number_delta = ( - text_token.token_text.count(ParserHelper.newline_character) + 2 + token.line_number - self.__container_token_stack[-1].line_number ) - else: - assert self.__last_non_end_token.is_fenced_code_block - line_number_delta = 0 - end_token = cast(EndMarkdownToken, self.__end_fenced_code_block_token) - column_number_delta = end_token.start_markdown_token.column_number - start_token = cast(EndMarkdownToken, end_token.start_markdown_token) - if start_token.extracted_whitespace: - column_number_delta -= len(start_token.extracted_whitespace) - if end_token.extracted_whitespace: - column_number_delta += len(end_token.extracted_whitespace) - column_number_delta = -(column_number_delta) - self.report_next_token_error( - context, - end_token.start_markdown_token, - line_number_delta=line_number_delta, - column_number_delta=column_number_delta, + extra_end_data = self.__end_tokens[-1].extra_end_data + if extra_end_data is not None: + line_number_delta += 1 + self.__closed_container_adjustments[ + stack_index + ].adjustment += line_number_delta + break + + def __process_pending_container_end_list(self, token: MarkdownToken) -> None: + for stack_index in range(len(self.__container_token_stack) - 2, -1, -1): + current_stack_token = self.__container_token_stack[stack_index] + if current_stack_token.is_list_start: + line_number_delta = ( + token.line_number - self.__container_token_stack[-1].line_number + ) + self.__closed_container_adjustments[ + stack_index + ].adjustment += line_number_delta + break + + def __process_pending_container_end( + self, context: PluginScanContext, token: MarkdownToken + ) -> None: + if next_container_adjustment_list := self.__container_adjustments[-1]: + self.__process_pending_container_end_adjustment( + context, next_container_adjustment_list ) - self.__end_fenced_code_block_token = None + + if self.__container_token_stack[-1].is_block_quote_start: + self.__process_pending_container_end_block_quote(token) + else: + self.__process_pending_container_end_list(token) + + del self.__container_token_stack[-1] + del self.__container_adjustments[-1] + del self.__closed_container_adjustments[-1] + del self.__end_tokens[-1] + self.__pending_container_ends -= 1 + + def __calculate_special_case( + self, context: PluginScanContext, token: MarkdownToken + ) -> bool: + return bool( + context.in_fix_mode + and token.is_fenced_code_block + and self.__container_token_stack + and len(self.__container_token_stack) > 1 + and self.__container_token_stack[-1].is_block_quote_start + and self.__last_token + and self.__second_last_token + and self.__last_token.is_block_quote_end + and self.__second_last_token.is_paragraph_end + ) def next_token(self, context: PluginScanContext, token: MarkdownToken) -> None: """ Event that a new token is being processed. """ - if self.__end_fenced_code_block_token and not token.is_end_token: - self.__handle_end_fenced_code_block(context, token) - if token.is_block_quote_start: - self.__container_token_stack.append(token) - elif token.is_block_quote_end: - del self.__container_token_stack[-1] - elif token.is_list_start: + special_case = self.__calculate_special_case(context, token) + + if not token.is_end_token or token.is_end_of_stream: + while self.__pending_container_ends and not special_case: + self.__process_pending_container_end(context, token) + if self.__end_fenced_code_block_token: + self.__handle_end_fenced_code_block(context, token) + + if token.is_block_quote_start or token.is_list_start: self.__container_token_stack.append(token) - elif token.is_list_end: - del self.__container_token_stack[-1] + self.__container_adjustments.append([]) + self.__closed_container_adjustments.append(ClosedContainerAdjustments()) + elif token.is_block_quote_end or token.is_list_end: + self.__pending_container_ends += 1 + self.__end_tokens.append(cast(EndMarkdownToken, token)) elif token.is_fenced_code_block: - self.__handle_fenced_code_block(context, token) + self.__handle_fenced_code_block(context, token, special_case) + while self.__pending_container_ends and special_case: + self.__process_pending_container_end(context, token) elif token.is_fenced_code_block_end: - self.__end_fenced_code_block_token = token + self.__end_fenced_code_block_token = cast(EndMarkdownToken, token) if ( not token.is_end_token @@ -145,3 +436,9 @@ def next_token(self, context: PluginScanContext, token: MarkdownToken) -> None: and not token.is_list_start ): self.__last_non_end_token = token + + self.__second_last_token = self.__last_token + self.__last_token = token + + +# pylint: enable=too-many-instance-attributes diff --git a/pymarkdown/tokens/block_quote_markdown_token.py b/pymarkdown/tokens/block_quote_markdown_token.py index 098669fe7..1e8b037fd 100644 --- a/pymarkdown/tokens/block_quote_markdown_token.py +++ b/pymarkdown/tokens/block_quote_markdown_token.py @@ -44,7 +44,7 @@ def __init__( position_marker=position_marker, ) self.__compose_extra_data_field() - self.weird_kludge_one = None + self.weird_kludge_one: Optional[int] = None # pylint: disable=protected-access @staticmethod @@ -166,12 +166,13 @@ def calculate_next_bleading_space_part( ParserHelper.newline_character ) absolute_index = self.leading_text_index + delta - if allow_overflow and absolute_index >= len(split_leading_spaces): - leading_text = "" - else: - leading_text = split_leading_spaces[self.leading_text_index + delta] - if increment_index: - self.leading_text_index += 1 + assert not (allow_overflow and absolute_index >= len(split_leading_spaces)) + # if allow_overflow and absolute_index >= len(split_leading_spaces): + # leading_text = "" + # else: + leading_text = split_leading_spaces[self.leading_text_index + delta] + if increment_index: + self.leading_text_index += 1 if tabbed_leading is not None: leading_text = tabbed_leading diff --git a/pymarkdown/tokens/markdown_token.py b/pymarkdown/tokens/markdown_token.py index c931881bb..f767eb60a 100644 --- a/pymarkdown/tokens/markdown_token.py +++ b/pymarkdown/tokens/markdown_token.py @@ -590,7 +590,12 @@ def is_inline_image(self) -> bool: """ return self.token_name == MarkdownToken._token_inline_image - def adjust_line_number(self, context: PluginModifyContext, adjust_delta:int) -> None: + def adjust_line_number( + self, context: PluginModifyContext, adjust_delta: int + ) -> None: + """ + Adjust the line number by a given amount. + """ # By design, tokens can only be modified in fix mode during the token pass. if not context.in_fix_mode: raise BadPluginFixError( diff --git a/pymarkdown/transform_markdown/markdown_transform_context.py b/pymarkdown/transform_markdown/markdown_transform_context.py index 1f6d1b0cc..dd48cf602 100644 --- a/pymarkdown/transform_markdown/markdown_transform_context.py +++ b/pymarkdown/transform_markdown/markdown_transform_context.py @@ -2,8 +2,8 @@ Module to provide context to markdown transforms. """ -from dataclasses import dataclass import logging +from dataclasses import dataclass from typing import List, Optional from typing_extensions import Protocol @@ -17,7 +17,11 @@ @dataclass class IndentAdjustment: - adjustment:int = 0 + """ + Class to hold indent adjustments. + """ + + adjustment: int = 0 # pylint: disable=too-few-public-methods diff --git a/pymarkdown/transform_markdown/transform_block_quote.py b/pymarkdown/transform_markdown/transform_block_quote.py index a37925ba2..346ce707d 100644 --- a/pymarkdown/transform_markdown/transform_block_quote.py +++ b/pymarkdown/transform_markdown/transform_block_quote.py @@ -198,11 +198,17 @@ def rehydrate_block_quote_end( del context.container_token_indents[-1] if context.container_token_indents and any_non_container_end_tokens: - indent_adjust = actual_tokens[search_index].line_number - current_start_token.line_number - 1 + indent_adjust = ( + actual_tokens[search_index].line_number + - current_start_token.line_number + - 1 + ) - for indent_index in range(len(context.container_token_indents)-1, -1, -1): + for indent_index in range(len(context.container_token_indents) - 1, -1, -1): if context.container_token_stack[indent_index].is_block_quote_start: - context.container_token_indents[indent_index].adjustment += indent_adjust + context.container_token_indents[ + indent_index + ].adjustment += indent_adjust break del context.container_token_stack[-1] diff --git a/pymarkdown/transform_markdown/transform_containers.py b/pymarkdown/transform_markdown/transform_containers.py index af71dfd0b..e669ee9c8 100644 --- a/pymarkdown/transform_markdown/transform_containers.py +++ b/pymarkdown/transform_markdown/transform_containers.py @@ -550,7 +550,7 @@ def __adjust_for_list_check( + f"fg={leading_spaces_newline_count} + " + f"line={removed_block_token.line_number}" ) - weird_kludge_one_count = removed_tokens[-1].weird_kludge_one + weird_kludge_one_count = removed_block_token.weird_kludge_one new_list_item_adjust = leading_spaces_newline_count > 1 and ( weird_kludge_one_count is None or weird_kludge_one_count <= 1 ) diff --git a/pymarkdown/transform_markdown/transform_list_block.py b/pymarkdown/transform_markdown/transform_list_block.py index b84ecd9e6..65d1cf39d 100644 --- a/pymarkdown/transform_markdown/transform_list_block.py +++ b/pymarkdown/transform_markdown/transform_list_block.py @@ -397,6 +397,7 @@ def __rehydrate_list_start_contained_in_block_quote( return True, previous_indent + # pylint: disable=too-many-arguments @staticmethod def __rehydrate_list_start_contained_in_list( context: MarkdownTransformContext, @@ -422,8 +423,8 @@ def __rehydrate_list_start_contained_in_list( block_quote_leading_space_length, had_weird_block_quote_in_list, list_leading_space_length, - ) = TransformListBlock.__rehydrate_list_start_contained_in_list_start(context, - previous_token, current_token, deeper_containing_block_quote_token + ) = TransformListBlock.__rehydrate_list_start_contained_in_list_start( + context, previous_token, current_token, deeper_containing_block_quote_token ) list_start_content_length = 0 @@ -470,6 +471,8 @@ def __rehydrate_list_start_contained_in_list( had_weird_block_quote_in_list, ) + # pylint: enable=too-many-arguments + @staticmethod def __look_for_last_block_token( context: MarkdownTransformContext, @@ -514,8 +517,11 @@ def __rehydrate_list_start_contained_in_list_start( did_container_start_midline, block_quote_leading_space_length, had_weird_block_quote_in_list, - ) = TransformListBlock.__rehydrate_list_start_contained_in_list_deeper_block_quote(context, - previous_token, deeper_containing_block_quote_token, current_token + ) = TransformListBlock.__rehydrate_list_start_contained_in_list_deeper_block_quote( + context, + previous_token, + deeper_containing_block_quote_token, + current_token, ) if ( @@ -643,7 +649,10 @@ def __rehydrate_list_start_contained_in_list_deeper_block_quote( do_perform_block_quote_ending = ( projected_start_line != current_token.line_number ) - assert projected_start_line == current_token.line_number or (projected_start_line == current_token.line_number +1) + assert projected_start_line in [ + current_token.line_number, + current_token.line_number + 1, + ], "should be one of the two, unless we have miscalculated" ( block_quote_leading_space, starting_whitespace, @@ -670,6 +679,7 @@ def __rehydrate_list_start_contained_in_list_deeper_block_quote( had_weird_block_quote_in_list, ) + # pylint: disable=too-many-arguments @staticmethod def __rehydrate_list_start_deep( context: MarkdownTransformContext, @@ -734,18 +744,22 @@ def __rehydrate_list_start_deep( - deeper_containing_block_quote_token.line_number ) POGGER.debug(f"index:{line_number_delta}") - if deeper_containing_block_quote_token: - adjust_token_index = next( - ( - i - for i in range(len(context.container_token_stack)) - if context.container_token_stack[i] - == deeper_containing_block_quote_token - ), - None, - ) - assert adjust_token_index is not None - line_number_delta -= context.container_token_indents[adjust_token_index].adjustment + assert deeper_containing_block_quote_token + # if deeper_containing_block_quote_token: + adjust_token_index = next( # pragma: no cover + ( + i + for i in range(len(context.container_token_stack)) + if context.container_token_stack[i] + == deeper_containing_block_quote_token + ), + None, + ) + assert adjust_token_index is not None + line_number_delta -= context.container_token_indents[ + adjust_token_index + ].adjustment + # endif assert ( deeper_containing_block_quote_token.bleading_spaces is not None @@ -769,6 +783,8 @@ def __rehydrate_list_start_deep( check_list_for_indent, ) + # pylint: enable=too-many-arguments + # pylint: disable=too-many-arguments @staticmethod def __rehydrate_list_start_calculate_start( diff --git a/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py b/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py index c351cba49..4281ee469 100644 --- a/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py +++ b/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py @@ -1999,7 +1999,7 @@ def test_nested_three_unordered_block_ordered_with_blank_fenced_e1(): """ # Act & Assert - act_and_assert(source_markdown, expected_gfm, expected_tokens, show_debug=False) + act_and_assert(source_markdown, expected_gfm, expected_tokens) @pytest.mark.gfm diff --git a/test/resources/rules/md031/bad_block_quote_fall_off_after_fenced_open.md b/test/resources/rules/md031/bad_block_quote_fall_off_after_fenced_open.md deleted file mode 100644 index fec0963ab..000000000 --- a/test/resources/rules/md031/bad_block_quote_fall_off_after_fenced_open.md +++ /dev/null @@ -1,5 +0,0 @@ -> this is text -> -> ```text - this is not a tab in a code block - ``` diff --git a/test/resources/rules/md031/bad_fenced_block_in_block_quote.md b/test/resources/rules/md031/bad_fenced_block_in_block_quote.md deleted file mode 100644 index 92caa66be..000000000 --- a/test/resources/rules/md031/bad_fenced_block_in_block_quote.md +++ /dev/null @@ -1,5 +0,0 @@ -> block quote -```block -A code block -``` -> block quote diff --git a/test/resources/rules/md031/bad_fenced_block_in_block_quote_in_list.md b/test/resources/rules/md031/bad_fenced_block_in_block_quote_in_list.md deleted file mode 100644 index 48af085c6..000000000 --- a/test/resources/rules/md031/bad_fenced_block_in_block_quote_in_list.md +++ /dev/null @@ -1,5 +0,0 @@ -1. > block quote - ```block - A code block - ``` - > block quote diff --git a/test/resources/rules/md031/bad_fenced_block_in_list.md b/test/resources/rules/md031/bad_fenced_block_in_list.md deleted file mode 100644 index 1797f1fab..000000000 --- a/test/resources/rules/md031/bad_fenced_block_in_list.md +++ /dev/null @@ -1,5 +0,0 @@ -+ list -```block -A code block -``` -1. another list diff --git a/test/resources/rules/md031/bad_fenced_block_in_list_in_block_quote.md b/test/resources/rules/md031/bad_fenced_block_in_list_in_block_quote.md deleted file mode 100644 index 9e6ce1f02..000000000 --- a/test/resources/rules/md031/bad_fenced_block_in_list_in_block_quote.md +++ /dev/null @@ -1,5 +0,0 @@ -> + list -> ```block -> A code block -> ``` -> 1. another list diff --git a/test/resources/rules/md031/bad_fenced_block_only_after.md b/test/resources/rules/md031/bad_fenced_block_only_after.md deleted file mode 100644 index f408ac37e..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_after.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and no blank line. -```block -A code block -``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_after_in_block_quote.md b/test/resources/rules/md031/bad_fenced_block_only_after_in_block_quote.md deleted file mode 100644 index 5ece3a0a3..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_after_in_block_quote.md +++ /dev/null @@ -1,6 +0,0 @@ -> This is text and no blank line. -> ```block -> A code block -> ``` -> ->This is a blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_after_in_unordered_list.md b/test/resources/rules/md031/bad_fenced_block_only_after_in_unordered_list.md deleted file mode 100644 index 53faafec5..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_after_in_unordered_list.md +++ /dev/null @@ -1,6 +0,0 @@ -- This is text and no blank line. - ```block - A code block - ``` - - This is a blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_after_start_indent.md b/test/resources/rules/md031/bad_fenced_block_only_after_start_indent.md deleted file mode 100644 index 8779c205e..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_after_start_indent.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and no blank line. - ```block -A code block -``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_before.md b/test/resources/rules/md031/bad_fenced_block_only_before.md deleted file mode 100644 index 7baee75a1..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_before.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and a blank line. - -```block -A code block -``` -This is no blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_before_end_indent.md b/test/resources/rules/md031/bad_fenced_block_only_before_end_indent.md deleted file mode 100644 index c6ad2bef2..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_before_end_indent.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and a blank line. - -```block -A code block - ``` -This is no blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_before_in_block_quote.md b/test/resources/rules/md031/bad_fenced_block_only_before_in_block_quote.md deleted file mode 100644 index 84bc9b242..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_before_in_block_quote.md +++ /dev/null @@ -1,6 +0,0 @@ -> This is text and a blank line. -> -> ```block -> A code block -> ``` -> This is no blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_before_in_unordered_list.md b/test/resources/rules/md031/bad_fenced_block_only_before_in_unordered_list.md deleted file mode 100644 index bf8592a25..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_before_in_unordered_list.md +++ /dev/null @@ -1,6 +0,0 @@ -- This is text and a blank line. - - ```block - A code block - ``` - This is no blank line and some text. diff --git a/test/resources/rules/md031/bad_fenced_block_only_before_start_indent.md b/test/resources/rules/md031/bad_fenced_block_only_before_start_indent.md deleted file mode 100644 index 773d21c13..000000000 --- a/test/resources/rules/md031/bad_fenced_block_only_before_start_indent.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and a blank line. - - ```block -A code block -``` -This is no blank line and some text. diff --git a/test/resources/rules/md031/bad_issue_626.md b/test/resources/rules/md031/bad_issue_626.md deleted file mode 100644 index 08d427dd1..000000000 --- a/test/resources/rules/md031/bad_issue_626.md +++ /dev/null @@ -1,16 +0,0 @@ -# Steps - -1. First - - ```yaml - --- - apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 - ``` - -2. Create - - ```yaml - --- - resources: - - ../../base/git-common - ``` diff --git a/test/resources/rules/md031/good_fenced_block_at_end.md b/test/resources/rules/md031/good_fenced_block_at_end.md deleted file mode 100644 index b40dc350f..000000000 --- a/test/resources/rules/md031/good_fenced_block_at_end.md +++ /dev/null @@ -1,5 +0,0 @@ -This is text and a blank line. - -```block -A code block -``` \ No newline at end of file diff --git a/test/resources/rules/md031/good_fenced_block_at_start.md b/test/resources/rules/md031/good_fenced_block_at_start.md deleted file mode 100644 index 9022de925..000000000 --- a/test/resources/rules/md031/good_fenced_block_at_start.md +++ /dev/null @@ -1,5 +0,0 @@ -```block -A code block -``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/good_fenced_block_empty.md b/test/resources/rules/md031/good_fenced_block_empty.md deleted file mode 100644 index 0dbe9bdc2..000000000 --- a/test/resources/rules/md031/good_fenced_block_empty.md +++ /dev/null @@ -1,6 +0,0 @@ -This is text and a blank line. - -```block -``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/good_fenced_block_surrounded.md b/test/resources/rules/md031/good_fenced_block_surrounded.md deleted file mode 100644 index a71ea90fe..000000000 --- a/test/resources/rules/md031/good_fenced_block_surrounded.md +++ /dev/null @@ -1,7 +0,0 @@ -This is text and a blank line. - -```block -A code block -``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/good_fenced_block_surrounded_in_block_quote.md b/test/resources/rules/md031/good_fenced_block_surrounded_in_block_quote.md deleted file mode 100644 index bf524d14e..000000000 --- a/test/resources/rules/md031/good_fenced_block_surrounded_in_block_quote.md +++ /dev/null @@ -1,7 +0,0 @@ -This is text and a blank line. - ->```block ->A code block ->``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/good_fenced_block_surrounded_in_ordered_list.md b/test/resources/rules/md031/good_fenced_block_surrounded_in_ordered_list.md deleted file mode 100644 index 5f08e4276..000000000 --- a/test/resources/rules/md031/good_fenced_block_surrounded_in_ordered_list.md +++ /dev/null @@ -1,7 +0,0 @@ -This is text and a blank line. - -1. ```block - A code block - ``` - -This is a blank line and some text. diff --git a/test/resources/rules/md031/good_fenced_block_surrounded_in_unordered_list.md b/test/resources/rules/md031/good_fenced_block_surrounded_in_unordered_list.md deleted file mode 100644 index 77faf5126..000000000 --- a/test/resources/rules/md031/good_fenced_block_surrounded_in_unordered_list.md +++ /dev/null @@ -1,7 +0,0 @@ -This is text and a blank line. - -- ```block - A code block - ``` - -This is a blank line and some text. diff --git a/test/rules/test_md027.py b/test/rules/test_md027.py index 41441dd9d..c38eaa4c7 100644 --- a/test/rules/test_md027.py +++ b/test/rules/test_md027.py @@ -619,7 +619,7 @@ source_file_name=f"{source_path}issue-189-mini.md", ), pluginRuleTest( - "xxxx", + "bad_block_quote_with_interwoven_blank_lines", source_file_contents="""> * Heading 1 >\a > * Heading 2 @@ -642,7 +642,7 @@ ), ), pluginRuleTest( - "xxxx1", + "bad_block_quote_with_interwoven_indented_code", source_file_contents="""> * Heading 1 > fff > * Heading 2 @@ -660,6 +660,24 @@ > fff """, ), + pluginRuleTest( + "good_block_quote_with_deeper_nesting", + source_file_contents="""> > > block 3 +> > > block 3 +> > > block 3 +> > -------- +> > +> > ```block +> > A code block +> > ``` +> > +> > -------- +""", + disable_rules="md007,md009,md012,md030", + scan_expected_return_code=0, + # use_debug=True, + scan_expected_output="", + ), pluginRuleTest( "mix_md027_md007", source_file_contents="""> + first diff --git a/test/rules/test_md031.py b/test/rules/test_md031.py index 217776c6b..4237cc79d 100644 --- a/test/rules/test_md031.py +++ b/test/rules/test_md031.py @@ -3,866 +3,1379 @@ """ import os -from test.markdown_scanner import MarkdownScanner -from test.rules.utils import execute_query_configuration_test, pluginQueryConfigTest +from test.rules.utils import ( + execute_configuration_test, + execute_fix_test, + execute_query_configuration_test, + execute_scan_test, + id_test_plug_rule_fn, + pluginConfigErrorTest, + pluginQueryConfigTest, + pluginRuleTest, +) import pytest +source_path = os.path.join("test", "resources", "rules", "md031") + os.sep -@pytest.mark.rules -def test_md031_bad_configuration_list_items(): - """ - Test to verify that a configuration error is thrown when supplying the - list_items value with a string that is not a boolean. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "good_fenced_block_surrounded.md" - ) - supplied_arguments = [ - "--set", - "plugins.md031.list_items=bad", - "--strict-config", - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = "" - expected_error = ( - "BadPluginError encountered while configuring plugins:\n" - + "The value for property 'plugins.md031.list_items' must be of type 'bool'." - ) - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_good_fenced_block_surrounded(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block surrounded by blank lines. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "good_fenced_block_surrounded.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_bad_fenced_block_only_after(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block only followed by blank lines. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "bad_fenced_block_only_after.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_bad_fenced_block_only_before(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block only prefaced by blank lines. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "bad_fenced_block_only_before.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:5:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_good_fenced_block_at_start(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block at the start of the document. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "good_fenced_block_at_start.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_good_fenced_block_at_end(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block at the end of the document. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "good_fenced_block_at_end.md" - ) - supplied_arguments = [ - "--disable-rules", - "md047", - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_bad_fenced_block_only_after_start_indent(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block right after a text line, with the - fenced code block indented by 1. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_after_start_indent.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:2: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_bad_fenced_block_only_before_start_indent(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block right before a text line, with the - fenced code block indented by 1. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_before_start_indent.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:5:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_bad_fenced_block_only_before_end_indent(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block right before a text line, with the - end fenced code block indented by 1. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_before_end_indent.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:5:2: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_good_fenced_block_surrounded_in_block_quote(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block within a block quote surrounded by - blank lines. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "good_fenced_block_surrounded_in_block_quote.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] +configTests = [ + pluginConfigErrorTest( + "bad_configuration_list_items", + use_strict_config=True, + set_args=["plugins.md031.list_items=bad"], + expected_error="""BadPluginError encountered while configuring plugins: +The value for property 'plugins.md031.list_items' must be of type 'bool'.""", + ), +] - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_good_fenced_block_surrounded_in_ordered_list(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block within an ordered list surrounded by - blank lines. - """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "good_fenced_block_surrounded_in_ordered_list.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] +scanTests = [ + pluginRuleTest( + "good_both_fenced_with_consistent", + source_file_contents="""This is text and a blank line. - expected_return_code = 0 - expected_output = "" - expected_error = "" +```block +A code block +``` - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_after", + source_file_contents="""This is text and no blank line. +```block +A code block +``` + +This is a blank line and some text. +""", + scan_expected_return_code=1, + use_debug=True, + scan_expected_output="""{temp_source_path}:2:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""This is text and no blank line. - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) +```block +A code block +``` +This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_before", + source_file_contents="""This is text and a blank line. + +```block +A code block +``` +This is no blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""This is text and a blank line. -@pytest.mark.rules -def test_md031_good_fenced_block_surrounded_in_unordered_list(): - """ - Test to make sure this rule does not trigger with a document that - contains a fenced code block within an unordered list surrounded by - blank lines. - """ +```block +A code block +``` - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "good_fenced_block_surrounded_in_unordered_list.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] +This is no blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_at_start", + source_file_contents="""```block +A code block +``` + +This is a blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_at_end", + source_file_contents="""This is text and a blank line. + +```block +A code block +```""", + disable_rules="md047", + ), + pluginRuleTest( + "bad_fenced_block_only_after_start_indent", + source_file_contents="""This is text and no blank line. + ```block +A code block +``` + +This is a blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:2: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""This is text and no blank line. - expected_return_code = 0 - expected_output = "" - expected_error = "" + ```block +A code block +``` - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_before_start_indent", + source_file_contents="""This is text and a blank line. + + ```block +A code block +``` +This is no blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""This is text and a blank line. - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ```block +A code block +``` +This is no blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_before_end_indent", + source_file_contents="""This is text and a blank line. + +```block +A code block + ``` +This is no blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:2: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""This is text and a blank line. -@pytest.mark.rules -def test_md031_bad_fenced_block_only_after_in_block_quote(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block within a block quote the is immediately - after a text line. - """ +```block +A code block + ``` - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_after_in_block_quote.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:3: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" +This is no blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_surrounded_in_block_quote", + source_file_contents="""This is text and a blank line. - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +>```block +>A code block +>``` - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) +This is a blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_surrounded_in_ordered_list", + source_file_contents="""This is text and a blank line. +1. ```block + A code block + ``` -@pytest.mark.rules -def test_md031_bad_fenced_block_only_after_in_unordered_list(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block within an unordered list the is immediately - after a text line. - """ +This is a blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_surrounded_in_unordered_list", + source_file_contents="""This is text and a blank line. - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_after_in_unordered_list.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:3: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" +- ```block + A code block + ``` - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_after_in_block_quote", + source_file_contents="""> This is text and no blank line. +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_before_in_block_quote", + source_file_contents="""> This is text and no blank line. +> +> ```block +> A code block +> ``` +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote", + source_file_contents="""> This is text and no blank line. +> **** +> ```block +> A code block +> ``` +> **** +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:3:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> **** +> +> ```block +> A code block +> ``` +> +> **** +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_empty_in_block_quote", + source_file_contents="""> This is text and no blank line. +> **** +> ```block +> ``` +> **** +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:3:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> **** +> +> ```block +> ``` +> +> **** +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_bare", + source_file_contents="""> This is text and no blank line. +> ```block +> A code block +> ``` +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_with_previous_inner_block", + source_file_contents="""> > inner block +> > inner block +> +> This is text and no blank line. +> ```block +> A code block +> ``` +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> > inner block +> > inner block +> +> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_with_previous_inner_block_and_para_continue", + source_file_contents="""> > inner block +> > inner block +> This is text and no blank line. +> ```block +> A code block +> ``` +> This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:6:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> > inner block +> > inner block +> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +> This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_with_previous_inner_blocks", + source_file_contents="""> > inner block +> > > innermost block +> > > innermost block +> > inner block +> +> This is text and no blank line. +> ```block +> A code block +> ``` +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:7:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:9:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> > inner block +> > > innermost block +> > > innermost block +> > inner block +> +> This is text and no blank line. +> +> ```block +> A code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_only_after", + source_file_contents="""> This is text and no blank line. +> +> some paragraph +> ```block +> A good code block +> ``` +> +>This is a blank line and some text. +""", + use_debug=True, + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> This is text and no blank line. +> +> some paragraph +> +> ```block +> A good code block +> ``` +> +>This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_after_in_unordered_list", + source_file_contents="""- This is text and no blank line. + ```block + A code block + ``` + + This is a blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""- This is text and no blank line. - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ```block + A code block + ``` + This is a blank line and some text. +""", + ), + pluginRuleTest( + "bad_fenced_block_only_before_in_unordered_list", + source_file_contents="""- This is text and a blank line. + + ```block + A code block + ``` + This is no blank line and some text. +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""- This is text and a blank line. -@pytest.mark.rules -def test_md031_bad_fenced_block_only_before_in_unordered_list(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block within an unordered list tha is immediately - before a text line. - """ + ```block + A code block + ``` - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_before_in_unordered_list.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:5:3: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" + This is no blank line and some text. +""", + ), + pluginRuleTest( + "good_fenced_block_only_after_in_unordered_list_with_config", + source_file_contents="""- This is text and no blank line. + ```block + A code block + ``` + + This is a blank line and some text. +""", + set_args=["plugins.md031.list_items=$!False"], + use_strict_config=True, + ), + pluginRuleTest( + "good_fenced_block_only_before_in_unordered_list_with_config", + source_file_contents="""- This is text and a blank line. + + ```block + A code block + ``` + This is no blank line and some text. +""", + set_args=["plugins.md031.list_items=$!False"], + use_strict_config=True, + ), + pluginRuleTest( + "good_fenced_block_empty", + source_file_contents="""This is text and a blank line. - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +```block +``` - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) +This is a blank line and some text. +""", + set_args=["plugins.md031.list_items=$!False"], + use_strict_config=True, + ), + pluginRuleTest( + "bad_fenced_block_surrounded_by_block_quote", + source_file_contents="""> block quote +```block +A code block +``` +> block quote +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + fix_expected_file_contents="""> block quote +```block +A code block +``` -@pytest.mark.rules -def test_md031_good_fenced_block_only_after_in_unordered_list_with_config(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block within an unordered list tha is immediately - after a text line, but configuration. - """ +> block quote +""", + ), + pluginRuleTest( + "bad_fenced_block_surrounded_by_list", + source_file_contents="""+ list +```block +A code block +``` +1. another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:1: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_after_in_unordered_list.md", - ) - supplied_arguments = [ - "--set", - "plugins.md031.list_items=$!False", - "--strict-config", - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) +```block +A code block +``` +1. another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list", + source_file_contents="""+ list + ***** + ```block + A code block + ``` + ***** ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:3:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list + ***** -@pytest.mark.rules -def test_md031_good_fenced_block_only_before_in_unordered_list_with_config(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block within an unordered list tha is immediately - before a text line, but configuration. - """ + ```block + A code block + ``` - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_only_before_in_unordered_list.md", - ) - supplied_arguments = [ - "--set", - "plugins.md031.list_items=$!False", - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ***** ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_empty_in_list", + source_file_contents="""+ list + ***** + ```block + ``` + ***** ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:3:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list + ***** + ```block + ``` -@pytest.mark.rules -def test_md031_good_fenced_block_empty(): - """ - Test to make sure this rule does not trigger with a document that - contains an empty fenced code block surrounded by blank lines. - """ + ***** ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_bare_fenced", + source_file_contents="""+ list + ```block + A code block + ``` + list ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "good_fenced_block_empty.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] + ```block + A code block + ``` - expected_return_code = 0 - expected_output = "" - expected_error = "" + list ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_with_previous_inner_list", + source_file_contents="""+ list + + inner list + couple of lines + ----- + ```block + A code block + ``` + ----- ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list + + inner list + couple of lines + ----- + + ```block + A code block + ``` + + ----- ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_with_previous_inner_list_and_para_continue", + source_file_contents="""+ list + + inner list + couple of lines + continued line + ```block + A code block + ``` + ----- ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ list + + inner list + couple of lines + continued line + + ```block + A code block + ``` + + ----- ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_with_previous_inner_lists", + source_file_contents="""+ list + + innermost list + + innermost list + + inner list + couple of lines + original list + ```block + A code block + ``` + list ++ another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:7:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:9:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032,md007,md005", + fix_expected_file_contents="""+ list + + innermost list + + innermost list + + inner list + couple of lines + original list + + ```block + A code block + ``` + + list ++ another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote", + source_file_contents="""> > -------- +> > ```block +> > A code block +> > ``` +> > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > -------- +> > +> > ```block +> > A code block +> > ``` +> > +> > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote_bare_fenced", + source_file_contents="""> > some text +> > ```block +> > A code block +> > ``` +> > some other text +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > some text +> > +> > ```block +> > A code block +> > ``` +> > +> > some other text +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote_with_previous_inner_block", + source_file_contents="""> > > block 3 +> > > block 3 +> > > block 3 +> > -------- +> > ```block +> > A code block +> > ``` +> > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > > block 3 +> > > block 3 +> > > block 3 +> > -------- +> > +> > ```block +> > A code block +> > ``` +> > +> > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote_with_previous_inner_block_and_para_continue", + source_file_contents="""> > > block 3 +> > > block 3 +> > block 3 +> > ```block +> > A code block +> > ``` +> > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:6:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > > block 3 +> > > block 3 +> > block 3 +> > +> > ```block +> > A code block +> > ``` +> > +> > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote_in_block_quote", + source_file_contents="""> > > -------- +> > > ```block +> > > A code block +> > > ``` +> > > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > > -------- +> > > +> > > ```block +> > > A code block +> > > ``` +> > > +> > > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote_in_block_quote", + source_file_contents="""> > + -------- +> > ```block +> > A code block +> > ``` +> > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> > + -------- +> > +> > ```block +> > A code block +> > ``` +> > +> > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list", + source_file_contents="""1. > ---- + > ```block + > A code block + > ``` + > ---- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""1. > ---- + > + > ```block + > A code block + > ``` + > + > ---- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list_bare", + source_file_contents="""1. > block quote + > ```block + > A code block + > ``` + > block quote +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""1. > block quote + > + > ```block + > A code block + > ``` + > + > block quote +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list_with_previous_inner_block", + source_file_contents="""1. > > + > > block 3 + > > block 3 + > -------- + > ```block + > A code block + > ``` + > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""1. > > + > > block 3 + > > block 3 + > -------- + > + > ```block + > A code block + > ``` + > + > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list_with_previous_inner_block_and_para_continue", + source_file_contents="""1. > > + > > block 3 + > block 3 + > -------- + > ```block + > A code block + > ``` + > -------- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:6: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""1. > > + > > block 3 + > block 3 + > -------- + > + > ```block + > A code block + > ``` + > + > -------- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_block_quote_in_list", + source_file_contents="""1. > > ---- + > > ```block + > > A code block + > > ``` + > > ---- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:8: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:8: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + use_debug=True, + fix_expected_file_contents="""1. > > ---- + > > + > > ```block + > > A code block + > > ``` + > > + > > ---- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote_in_list", + source_file_contents="""1. > + ---- + > ```block + > A code block + > ``` + > ---- +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:8: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:8: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""1. > + ---- + > + > ```block + > A code block + > ``` + > + > ---- +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote", + source_file_contents="""> + ----- +> ```block +> A code block +> ``` +> ----- +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + ----- +> +> ```block +> A code block +> ``` +> +> ----- +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote_bare", + source_file_contents="""> + list +> ```block +> A code block +> ``` +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + list +> +> ```block +> A code block +> ``` +> +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote_with_previous_inner_list", + source_file_contents="""> + list 1 +> + list 2 +> list 3 +> ------ +> ```block +> A code block +> ``` +> ------ +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + list 1 +> + list 2 +> list 3 +> ------ +> +> ```block +> A code block +> ``` +> +> ------ +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_block_quote_with_previous_inner_list_and_para_continue", + source_file_contents="""> + list 1 +> + list 2 +> list 3 +> ------ +> ```block +> A code block +> ``` +> ------ +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:7:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + list 1 +> + list 2 +> list 3 +> ------ +> +> ```block +> A code block +> ``` +> +> ------ +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list_in_block_quote", + source_file_contents="""> + > ----- +> > ```block +> > A code block +> > ``` +> > ----- +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + > ----- +> > +> > ```block +> > A code block +> > ``` +> > +> > ----- +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list_in_block_quote", + source_file_contents="""> + + ----- +> ```block +> A code block +> ``` +> ----- +> + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""> + + ----- +> +> ```block +> A code block +> ``` +> +> ----- +> + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list", + source_file_contents="""+ + ----- + ```block + A code block + ``` + ----- + + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ + ----- - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) + ```block + A code block + ``` - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ----- + + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list_bare", + source_file_contents="""+ + list + ```block + A code block + ``` + more text +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ + list + ```block + A code block + ``` -@pytest.mark.rules -def test_md031_bad_fenced_block_in_block_quote(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block surrounded by block quotes. - """ + more text +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list_with_previous_inner_list", + source_file_contents="""+ + list 1 ++ + + list 2.1 + list 2.2 + ```block + A code block + ``` + + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:6:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ + list 1 ++ + + list 2.1 + list 2.2 - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "bad_fenced_block_in_block_quote.md" - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)\n" - + f"{source_path}:4:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" + ```block + A code block + ``` - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) + + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list_with_previous_inner_list_and_para_continue", + source_file_contents="""+ + list 1 ++ + + list 2.1 + list 2.2 + ```block + A code block + ``` + + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:4:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:6:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + use_debug=True, + fix_expected_file_contents="""+ + list 1 ++ + + list 2.1 + list 2.2 - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ```block + A code block + ``` + + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_block_quote_in_list_in_list", + source_file_contents="""+ + > ----- + > ```block + > A code block + > ``` + > ----- + + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ + > ----- + > + > ```block + > A code block + > ``` + > + > ----- + + another list +""", + ), + pluginRuleTest( + "bad_fenced_block_in_list_in_list_in_list", + source_file_contents="""+ + + ----- + ```block + A code block + ``` + ----- + + another list +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:2:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +{temp_source_path}:4:7: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md032", + fix_expected_file_contents="""+ + + ----- -@pytest.mark.rules -def test_md031_bad_fenced_block_in_list(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block surrounded by lists. - """ + ```block + A code block + ``` - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", "resources", "rules", "md031", "bad_fenced_block_in_list.md" - ) - supplied_arguments = [ - "--disable-rules", - "md032", - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)\n" - + f"{source_path}:4:1: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" + ----- + + another list +""", + ), + pluginRuleTest( + "issue_626", + source_file_contents="""# Steps - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) +1. First - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) + ```yaml + --- + apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 + ``` +2. Create -@pytest.mark.rules -def test_md031_bad_fenced_block_in_block_quote_in_list(): - """ - Test to make sure this rule does trigger with a document that - contains a fenced code block surrounded by block quotes within a list item. - """ + ```yaml + --- + resources: + - ../../base/git-common + ``` +""", + ), + pluginRuleTest( + "in_block_quotes_fall_off_after_fenced_open", + source_file_contents="""> this is text +> +> ```text + this is not a tab in a code block + ``` +""", + scan_expected_return_code=1, + scan_expected_output="""{temp_source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences) +""", + disable_rules="md010,md040", + fix_expected_file_contents="""> this is text +> +> ```text + this is not a tab in a code block - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_in_block_quote_in_list.md", - ) - supplied_arguments = [ - "--disable-rules", - "md032", - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:4: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)\n" - + f"{source_path}:4:4: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" + ``` +""", + ), +] - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) +fixTests = [] +for i in scanTests: + if i.fix_expected_file_contents is not None: + fixTests.append(i) -@pytest.mark.rules -def test_md031_bad_fenced_block_in_list_in_block_quote(): +@pytest.mark.parametrize("test", scanTests, ids=id_test_plug_rule_fn) +def test_md031_scan(test: pluginRuleTest) -> None: """ - Test to make sure this rule does trigger with a document that - contains a fenced code block surrounded by list item within a block quote. + Execute a parameterized scan test for plugin md001. """ + execute_scan_test(test, "md031") - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_fenced_block_in_list_in_block_quote.md", - ) - supplied_arguments = [ - "--disable-rules", - "md032", - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:2:3: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)\n" - + f"{source_path}:4:3: " - + "MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - -@pytest.mark.rules -def test_md031_issue_626(): +@pytest.mark.parametrize("test", fixTests, ids=id_test_plug_rule_fn) +def test_md031_fix(test: pluginRuleTest) -> None: """ - Addressing an issue reported in https://github.com/jackdewinter/pymarkdown/issues/626 . + Execute a parameterized fix test for plugin md001. """ + execute_fix_test(test) - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_issue_626.md", - ) - supplied_arguments = [ - "scan", - source_path, - ] - - expected_return_code = 0 - expected_output = "" - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code - ) - - -@pytest.mark.rules -def test_md031_in_block_quotes_fall_off_after_fenced_open(): +@pytest.mark.parametrize("test", configTests, ids=id_test_plug_rule_fn) +def test_md031_config(test: pluginRuleTest) -> None: """ - Test to make sure this rule + Execute a parameterized fix test for plugin md001. """ - - # Arrange - scanner = MarkdownScanner() - source_path = os.path.join( - "test", - "resources", - "rules", - "md031", - "bad_block_quote_fall_off_after_fenced_open.md", - ) - supplied_arguments = [ - "-d", - "md010,md041,md040", - "scan", - source_path, - ] - - expected_return_code = 1 - expected_output = ( - f"{source_path}:3:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)\n" - + f"{source_path}:5:3: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)" - ) - expected_error = "" - - # Act - execute_results = scanner.invoke_main(arguments=supplied_arguments) - - # Assert - execute_results.assert_results( - expected_output, expected_error, expected_return_code + execute_configuration_test( + test, + file_contents="""this is a paragraph without any capitalization errors +""", ) diff --git a/test/rules/test_plugin_manager.py b/test/rules/test_plugin_manager.py index 61a068e4d..3012550b9 100644 --- a/test/rules/test_plugin_manager.py +++ b/test/rules/test_plugin_manager.py @@ -1313,7 +1313,7 @@ def test_markdown_with_plugins_list_only(): md028 no-blanks-blockquote True True 0.5.0 No md029 ol-prefix True True 0.6.0 Yes md030 list-marker-space True True 0.6.0 Yes - md031 blanks-around-fences True True 0.6.0 No + md031 blanks-around-fences True True 0.7.0 Yes md032 blanks-around-lists True True 0.5.0 No md033 no-inline-html True True 0.6.0 No md034 no-bare-urls True True 0.5.0 No @@ -1397,7 +1397,7 @@ def test_markdown_with_plugins_list_only_all(): md028 no-blanks-blockquote True True 0.5.0 No md029 ol-prefix True True 0.6.0 Yes md030 list-marker-space True True 0.6.0 Yes - md031 blanks-around-fences True True 0.6.0 No + md031 blanks-around-fences True True 0.7.0 Yes md032 blanks-around-lists True True 0.5.0 No md033 no-inline-html True True 0.6.0 No md034 no-bare-urls True True 0.5.0 No diff --git a/test/test_markdown_extra.py b/test/test_markdown_extra.py index e09c700f5..47be53f26 100644 --- a/test/test_markdown_extra.py +++ b/test/test_markdown_extra.py @@ -6047,65 +6047,6 @@ def test_extra_043a(): # Act & Assert act_and_assert(source_markdown, expected_gfm, expected_tokens) -@pytest.mark.gfm -def test_extra_044c(): - """ - TBD - """ - - # Arrange - source_markdown = """> + list 1 -> + list 2 -> list 3 -> ------ -> ```block -> A code block -> ``` -> ------ -> + another list -""" - expected_tokens = [ - "[block-quote(1,1)::> \n> \n> \n> \n> \n> \n> \n> \n> ]", - "[ulist(1,3):+::4:: \n \n \n \n \n]", - "[para(1,5):]", - "[text(1,5):list 1:]", - "[end-para:::True]", - "[ulist(2,5):+::6: : \n ]", - "[para(2,7):\n]", - "[text(2,7):list 2\nlist 3::\n]", - "[end-para:::False]", - "[end-ulist:::True]", - "[tbreak(4,5):-::------]", - "[fcode-block(5,5):`:3:block:::::]", - "[text(6,3):A code block:]", - "[end-fcode-block:::3:False]", - "[tbreak(8,5):-::------]", - "[li(9,3):4::]", - "[para(9,5):]", - "[text(9,5):another list:]", - "[end-para:::True]", - "[BLANK(10,1):]", - "[end-ulist:::True]", - "[end-block-quote:::True]", - ] - expected_gfm = """
- -
""" - - # Act & Assert - act_and_assert(source_markdown, expected_gfm, expected_tokens) @pytest.mark.gfm def test_extra_044x(): @@ -6440,6 +6381,7 @@ def test_extra_044e(): # Act & Assert act_and_assert(source_markdown, expected_gfm, expected_tokens) + @pytest.mark.gfm def test_extra_044fx(): """ @@ -6456,19 +6398,25 @@ def test_extra_044fx(): > + another list """ expected_tokens = [ - '[block-quote(1,1)::> \n> ]', - '[ulist(1,3):+::4::\n\n\n\n\n\n\n]', - '[block-quote(1,5)::> \n> > \n> > \n> > \n> > \n> > ]', - '[tbreak(1,7):-::-----]', - '[para(2,7):]', '[text(2,7):abc:]', '[end-para:::False]', - '[fcode-block(3,7):`:3:block:::::]', '[text(4,7):A code block:]', '[end-fcode-block:::3:False]', - '[tbreak(6,7):-::-----]', - '[end-block-quote:::True]', - '[li(7,3):4::]', - '[para(7,5):]', '[text(7,5):another list:]', '[end-para:::True]', - '[BLANK(8,1):]', - '[end-ulist:::True]', - '[end-block-quote:::True]' + "[block-quote(1,1)::> \n> ]", + "[ulist(1,3):+::4::\n\n\n\n\n\n\n]", + "[block-quote(1,5)::> \n> > \n> > \n> > \n> > \n> > ]", + "[tbreak(1,7):-::-----]", + "[para(2,7):]", + "[text(2,7):abc:]", + "[end-para:::False]", + "[fcode-block(3,7):`:3:block:::::]", + "[text(4,7):A code block:]", + "[end-fcode-block:::3:False]", + "[tbreak(6,7):-::-----]", + "[end-block-quote:::True]", + "[li(7,3):4::]", + "[para(7,5):]", + "[text(7,5):another list:]", + "[end-para:::True]", + "[BLANK(8,1):]", + "[end-ulist:::True]", + "[end-block-quote:::True]", ] expected_gfm = """