Skip to content

Conversation

@JayBazuzi
Copy link
Contributor

@JayBazuzi JayBazuzi commented Dec 1, 2025

Summary by Sourcery

Escape problematic control characters in inline approval outputs and stabilize CI workflows for mise-based builds.

New Features:

  • Add support for escaping a defined set of control and invisible Unicode characters in inline Python reporter outputs.

Enhancements:

  • Update inline approvals internal documentation to describe how control characters are represented in verified strings.

CI:

  • Add an explicit mise install step to GitHub Actions workflows to improve reliability of test and tidy jobs.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 1, 2025

Reviewer's Guide

Adds explicit escaping for a curated set of control and invisible Unicode characters in inline Python approval reports, documents the behavior, and hardens GitHub Actions workflows by explicitly running mise install to mitigate transient setup issues.

Sequence diagram for InlinePythonReporter create_received_file processing with control character escaping

sequenceDiagram
    actor Developer
    participant InlinePythonReporter
    participant Path
    participant handle_preceeding_whitespace
    participant escape_backslashes
    participant escape_control_characters
    participant StackFrameNamer

    Developer->>InlinePythonReporter: create_received_file(received_path, test_source_file)
    InlinePythonReporter->>Path: read_text(encoding_utf_8)
    Path-->>InlinePythonReporter: original_received_text_raw
    InlinePythonReporter->>InlinePythonReporter: original_received_text = original_received_text_raw without last char
    InlinePythonReporter->>InlinePythonReporter: received_text = original_received_text + footer
    InlinePythonReporter->>handle_preceeding_whitespace: handle_preceeding_whitespace(received_text)
    handle_preceeding_whitespace-->>InlinePythonReporter: whitespace_normalized_text
    InlinePythonReporter->>escape_backslashes: escape_backslashes(whitespace_normalized_text)
    escape_backslashes-->>InlinePythonReporter: backslashes_escaped_text
    InlinePythonReporter->>escape_control_characters: escape_control_characters(backslashes_escaped_text)
    escape_control_characters-->>InlinePythonReporter: fully_escaped_text
    InlinePythonReporter->>StackFrameNamer: get_test_frame()
    StackFrameNamer-->>InlinePythonReporter: frame_with_function_name
    InlinePythonReporter->>InlinePythonReporter: method_name = frame_with_function_name.function
    InlinePythonReporter-->>Developer: path_to_generated_test_method_file
Loading

Class diagram for InlinePythonReporter and control character escaping helpers

classDiagram
    class InlinePythonReporter {
        - footer: str
        + __init__(self, footer: str)
        + create_received_file(self, received_path: str, test_source_file: str) str
    }

    class Reporter {
        <<interface>>
        + create_received_file(received_path: str, test_source_file: str) str
    }

    class StackFrameNamer {
        + get_test_frame() frame
    }

    class inline_python_reporter_module_helpers {
        + handle_preceeding_whitespace(text: str) str
        + detect_trailing_whitespace(text: str) bool
        + escape_backslashes(text: str) str
        + escape_control_characters(text: str) str
    }

    InlinePythonReporter ..|> Reporter
    InlinePythonReporter ..> StackFrameNamer : uses
    InlinePythonReporter ..> inline_python_reporter_module_helpers : uses

    note for inline_python_reporter_module_helpers "escape_control_characters maps selected control and invisible Unicode characters (such as null, backspace, zero-width spaces, directional marks, BOM) to explicit escape sequences before embedding them in docstrings"
Loading

File-Level Changes

Change Details Files
Introduce dedicated escaping of selected control and invisible Unicode characters in inline reporter output.
  • Add escape_control_characters helper that maps specific control and invisible Unicode characters to visible backslash-escaped sequences using str.translate
  • Invoke escape_control_characters after whitespace normalization and backslash escaping when constructing the inline received text in InlinePythonReporter.create_received_file
approvaltests/namer/inline_python_reporter.py
Document how inline approvals handle control characters alongside existing whitespace and escape handling.
  • Extend inline_approvals_whitespace micro-feature documentation to list the exact control characters that are rendered explicitly and give examples (e.g., \u200f)
internal_documentation/micro_features/inline_approvals_whitespace.md
Improve robustness of CI workflows that depend on mise by adding an explicit installation step.
  • Insert a dedicated mise install step after jdx/mise-action in the main test workflow
  • Insert a dedicated mise install step after jdx/mise-action in the minimal test workflow
  • Insert a dedicated mise install step after jdx/mise-action in the tidy_code_and_update_markdown_snippets workflow
.github/workflows/test.yml
.github/workflows/test_min.yml
.github/workflows/tidy_code_and_update_markdown_snippets.yml

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. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the 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 exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

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

@JayBazuzi JayBazuzi merged commit 0de60b2 into main Dec 1, 2025
72 of 86 checks passed
Copy link

@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 there - I've reviewed your changes - here's some feedback:

  • Consider moving the translation_table out of escape_control_characters() into a module-level constant so it isn’t rebuilt on every call and is easier to inspect/extend.
  • The list of control characters in inline_approvals_whitespace.md is a bit inconsistent (e.g., \u200f appears twice and some items are formatted without backticks or with different descriptions); it would be clearer to normalize the formatting and ensure each character is listed once with a consistent label.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider moving the `translation_table` out of `escape_control_characters()` into a module-level constant so it isn’t rebuilt on every call and is easier to inspect/extend.
- The list of control characters in `inline_approvals_whitespace.md` is a bit inconsistent (e.g., `\u200f` appears twice and some items are formatted without backticks or with different descriptions); it would be clearer to normalize the formatting and ensure each character is listed once with a consistent label.

## Individual Comments

### Comment 1
<location> `approvaltests/namer/inline_python_reporter.py:98-99` </location>
<code_context>
-        # Handle preceding whitespace consistently across all lines.
         received_text = handle_preceeding_whitespace(received_text)
-        # Escape backslashes to avoid accidental escape sequences in the docstring.
         received_text = escape_backslashes(received_text)
+        received_text = escape_control_characters(received_text)
         method_name = StackFrameNamer.get_test_frame().function
         if detect_trailing_whitespace(original_received_text):
</code_context>

<issue_to_address>
**issue (bug_risk):** Consider the ordering of backslash vs control-character escaping to keep behavior consistent.

Currently, `escape_backslashes` doubles only the original backslashes; the backslashes introduced later by `escape_control_characters` (e.g. in `"\x00"`) remain single. This produces mixed single/double backslashes and may leave control-character sequences still interpretable as escapes in docstrings. To keep behavior consistent and avoid accidental escape sequences, consider running `escape_control_characters` before `escape_backslashes`, or doing a final `escape_backslashes` after both steps so all backslashes are handled uniformly.
</issue_to_address>

### Comment 2
<location> `.github/workflows/tidy_code_and_update_markdown_snippets.yml:11` </location>
<code_context>
       - uses: actions/checkout@v6
       - run: echo "${{ matrix.python-version }}" > .python-version
       - uses: jdx/mise-action@v3
+      - run: mise install  # retry for network issues
       - run: ./build_and_test.sh
         shell: bash
</code_context>

<issue_to_address>
**question:** The retry step always runs rather than conditionally on failure.

As written, this will always run `mise install` twice—once via `jdx/mise-action` and once in this step—rather than only retrying on failure. If you want an actual retry, consider gating this on a previous step’s failure with `if: ${{ failure() }}` or using a dedicated retry wrapper, so you avoid extra work when the initial install succeeds.
</issue_to_address>

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 98 to +99
received_text = escape_backslashes(received_text)
received_text = escape_control_characters(received_text)
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Consider the ordering of backslash vs control-character escaping to keep behavior consistent.

Currently, escape_backslashes doubles only the original backslashes; the backslashes introduced later by escape_control_characters (e.g. in "\x00") remain single. This produces mixed single/double backslashes and may leave control-character sequences still interpretable as escapes in docstrings. To keep behavior consistent and avoid accidental escape sequences, consider running escape_control_characters before escape_backslashes, or doing a final escape_backslashes after both steps so all backslashes are handled uniformly.

steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v3
- run: mise install # retry for network issues
Copy link

Choose a reason for hiding this comment

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

question: The retry step always runs rather than conditionally on failure.

As written, this will always run mise install twice—once via jdx/mise-action and once in this step—rather than only retrying on failure. If you want an actual retry, consider gating this on a previous step’s failure with if: ${{ failure() }} or using a dedicated retry wrapper, so you avoid extra work when the initial install succeeds.

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.

2 participants