Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

https://github.com/jackdewinter/pymarkdown/issues/1247 #1248

Merged
merged 13 commits into from
Oct 28, 2024
Merged

Conversation

jackdewinter
Copy link
Owner

@jackdewinter jackdewinter commented Oct 27, 2024

closes #1247

Summary by Sourcery

Add new tests for complex nested Markdown structures and fix indentation issues in nested lists without block quotes. Refactor token handling logic to improve prefix determination and update the changelog with these changes.

New Features:

  • Introduce new tests for handling complex nested list and block quote scenarios in Markdown parsing.

Bug Fixes:

  • Fix issues with incorrect indentation calculation in nested list scenarios without block quotes, ensuring proper rehydration of fixed text.

Enhancements:

  • Refactor the __abcd_final_list function to improve handling of removed tokens and container stacks, enhancing the logic for prefix determination.

Documentation:

  • Update changelog to include new tests and fixes related to complex nested Markdown structures.

Tests:

  • Add comprehensive tests for scenarios involving multiple levels of containers and their interactions, including cases with list and block quote combinations.

Copy link
Contributor

sourcery-ai bot commented Oct 27, 2024

Reviewer's Guide by Sourcery

This PR fixes an issue with list indentation calculation in cases where there are 3 or more nested lists without block quotes. The changes primarily focus on improving the handling of leading spaces and container adjustments in the markdown parser.

Class diagram for TransformContainers and RuleMd031 changes

classDiagram
    class TransformContainers {
        +__abcd_final_list_second_half(removed_tokens: List<MarkdownToken>, removed_token_indices: List<int>, container_stack: List<MarkdownToken>, possible_prefix: Optional<str>) Optional<str>
        +__abcd_final_list(removed_tokens: List<MarkdownToken>, removed_token_indices: List<int>, container_stack: List<MarkdownToken>) Optional<str>
    }
    class RuleMd031 {
        +__fix_spacing_one_container(context: PluginScanContext, token: MarkdownToken) bool
        +__fix_spacing_removed_container(context: PluginScanContext, token: MarkdownToken) void
        +next_token(context: PluginScanContext, token: MarkdownToken) void
        +__last_end_container_tokens: List<MarkdownToken>
    }
    TransformContainers --> RuleMd031 : uses
    note for TransformContainers "Handles container adjustments and leading spaces"
    note for RuleMd031 "Manages spacing fixes for markdown tokens"
Loading

File-Level Changes

Change Details Files
Added support for handling leading spaces in list indentation calculations
  • Split list indentation handling into separate methods for better organization
  • Added handling for cases where leading_spaces is None
  • Fixed indent calculation for 3+ nested lists without block quotes
pymarkdown/transform_markdown/transform_containers.py
Improved container token handling in MD031 rule
  • Added tracking of last end container tokens
  • Split container handling logic into separate methods
  • Fixed spacing adjustments for removed containers
pymarkdown/plugins/rule_md_031.py
Added comprehensive test coverage for nested list scenarios
  • Added tests for multiple levels of container drops
  • Added test cases for list indentation edge cases
  • Updated test skip flags for better test organization
test/test_markdown_extra.py
test/rules/test_md031.py
Added boundary check for list indentation in block quotes
  • Added check for indent level against line length
  • Fixed potential index out of bounds error
pymarkdown/block_quotes/block_quote_count_helper.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1247 Fix incorrect indentation calculation in 3+ drop cases with only lists (no block quotes)

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jackdewinter - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟡 Testing: 3 issues found
  • 🟡 Complexity: 2 issues found
  • 🟡 Documentation: 3 issues found

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 9046 to 9047
@pytest.mark.skip
def test_extra_050a0():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Test case is marked as skipped without explanation

This test case is marked with @pytest.mark.skip but there's no comment explaining why it's skipped. Consider adding a comment explaining the reason for skipping or remove the skip marker if the test should be running.

Comment on lines 9048 to 9049
"""
TBD
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Test docstring is incomplete

Multiple test cases have 'TBD' as their docstring. The docstring should describe what aspect of the functionality is being tested. Consider updating these with proper descriptions that explain the test's purpose.

Comment on lines 33 to 34
skip_fix_bad_markdown = True
skip_fix_asserts = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Global test skip flags without documentation

These global flags control test skipping behavior but lack documentation explaining their purpose and when they should be used. Consider adding comments explaining what these flags control and why they exist.

Comment on lines 8 to 15
- None
- [Issue 1233](https://github.com/jackdewinter/pymarkdown/issues/1233)
- [Issue 1234](https://github.com/jackdewinter/pymarkdown/issues/1234)
- [Issue 1235](https://github.com/jackdewinter/pymarkdown/issues/1235)
- Adding more comprehensive "drop X" tests where multiple levels of
containers are involved, and then dropping one or more of those
containers in a single line.

<!--- pyml disable-next-line no-duplicate-heading-->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (documentation): Issues 1233-1235 appear in both Added and Fixed sections

Consider clarifying why these issues appear in both sections, or consolidate them into the most appropriate section to avoid confusion.

- [Issue 1233](https://github.com/jackdewinter/pymarkdown/issues/1233)
- [Issue 1234](https://github.com/jackdewinter/pymarkdown/issues/1234)
- [Issue 1235](https://github.com/jackdewinter/pymarkdown/issues/1235)
- Adding more comprehensive "drop X" tests where multiple levels of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (documentation): The term "drop X" could benefit from more context

Consider explaining what "drop X" refers to, as this might not be immediately clear to all readers.

Suggested change
- Adding more comprehensive "drop X" tests where multiple levels of
- Adding more comprehensive database column dropping tests where multiple levels of schema dependencies are involved

- [Issue 1233](https://github.com/jackdewinter/pymarkdown/issues/1233)
- [Issue 1234](https://github.com/jackdewinter/pymarkdown/issues/1234)
- [Issue 1235](https://github.com/jackdewinter/pymarkdown/issues/1235)
- Adding new "drop_x" tests and resolve any scan issues with them.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (documentation): Inconsistent terminology between "drop X" and "drop_x"

Consider using consistent terminology throughout the changelog to avoid confusion.

@@ -426,9 +426,55 @@ def __abcd(

# pylint: enable=too-many-arguments, too-many-boolean-expressions

@staticmethod
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider consolidating the indent calculation logic into a single method without stack manipulation.

The split into __abcd_final_list_second_half has introduced unnecessary complexity through stack manipulation and branching. Consider simplifying by focusing on the indent calculation:

@staticmethod
def __abcd_final_list(
    removed_tokens: List[MarkdownToken],
    removed_token_indices: List[int],
    container_stack: List[MarkdownToken],
) -> Optional[str]:
    # Handle list token prefix
    removed_list_token = cast(ListStartMarkdownToken, removed_tokens[0])
    assert removed_list_token.leading_spaces is not None
    split_leading_spaces = removed_list_token.leading_spaces.split("\n")
    possible_prefix = None
    if removed_token_indices[0] < len(split_leading_spaces):
        possible_prefix = split_leading_spaces[removed_token_indices[0]]
    del removed_tokens[0]
    del removed_token_indices[0]

    # Calculate indent if only list tokens
    if not any(t.is_block_quote_start for t in container_stack + removed_tokens):
        current_indent_level = (container_stack[-1].indent_level 
                              if container_stack else 0)
        return (" " * current_indent_level if possible_prefix is None 
                else possible_prefix)

    # Handle block quote case
    if removed_tokens[0].is_block_quote_start:
        bq_token = cast(BlockQuoteMarkdownToken, removed_tokens[0])
        assert bq_token.bleading_spaces is not None
        split_spaces = bq_token.bleading_spaces.split("\n")
        assert removed_token_indices[0] < len(split_spaces)
        prefix = "" if possible_prefix is None else possible_prefix
        prefix = f"{split_spaces[removed_token_indices[0]]}{prefix}"
        del removed_tokens[0]
        del removed_token_indices[0]
        return prefix

    return None

This approach:

  1. Eliminates the need for stack copying and reversal
  2. Simplifies the branching logic
  3. Keeps the indent calculation separate from block quote handling
  4. Maintains all functionality while reducing state manipulation

@@ -56,6 +56,7 @@ def __init__(self) -> None:
self.__removed_container_adjustments: Optional[
List[PendingContainerAdjustment]
] = None
self.__last_end_container_tokens: List[MarkdownToken] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring container end token handling to use a recursive traversal approach instead of maintaining state

The current implementation maintains ongoing state via __last_end_container_tokens and uses complex index manipulation to process them. This can be simplified while keeping the enhanced multi-container functionality:

def __collect_preceding_container_ends(self, token: MarkdownToken) -> List[MarkdownToken]:
    """Collect container end tokens that immediately precede this token."""
    container_ends = []
    current = self.__last_token
    while current and (current.is_list_end or current.is_block_quote_end):
        container_ends.insert(0, current)
        current = self.__second_last_token
    return container_ends

# In the fenced code block handling section:
if token.is_fenced_code_block and self.__last_token.is_list_end:
    container_ends = self.__collect_preceding_container_ends(token)
    replacement_tokens = [
        BlankLineMarkdownToken(
            extracted_whitespace="",
            position_marker=PositionMarker(new_token.line_number - 1, 0, ""),
            column_delta=1,
        )
    ]
    replacement_tokens.extend(container_ends)
    replacement_tokens.append(new_token)
    self.register_replace_tokens_request(
        context, container_ends[0], token, replacement_tokens
    )

This approach:

  1. Eliminates the need for ongoing token list maintenance
  2. Computes container ends only when needed
  3. Removes complex index manipulation
  4. Keeps the enhanced multi-container functionality


new_token = copy.deepcopy(token)
self.__fix_count += 1
new_token.adjust_line_number(context, self.__fix_count)
assert self.__last_token is not None
if token.is_fenced_code_block and self.__last_token.is_list_end:
end_container_index = len(self.__last_end_container_tokens) - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Extract code out into method (extract-method)

Copy link

codecov bot commented Oct 27, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.98%. Comparing base (cee1f3f) to head (6721ca2).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1248      +/-   ##
==========================================
- Coverage   99.99%   99.98%   -0.01%     
==========================================
  Files         191      191              
  Lines       21094    21123      +29     
  Branches     2696     2701       +5     
==========================================
+ Hits        21092    21120      +28     
  Misses          1        1              
- Partials        1        2       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jackdewinter jackdewinter merged commit 50a1e94 into main Oct 28, 2024
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Md031 Fix - Multiple drop with only lists causes bad fix.
1 participant