Skip to content

Fix ManageScript delimiter checking for C# string variants#745

Merged
dsarno merged 4 commits intobetafrom
fix/manage-script-delimiter-checking
Feb 13, 2026
Merged

Fix ManageScript delimiter checking for C# string variants#745
dsarno merged 4 commits intobetafrom
fix/manage-script-delimiter-checking

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Feb 13, 2026

Summary

  • Extract shared CSharpLexer struct in C# and _iter_csharp_tokens generator in Python, replacing 7 duplicated inline lexer copies that all had the same bugs
  • Fix delimiter checking for verbatim (@""), interpolated ($""), verbatim-interpolated ($@""/@$""), raw ("""), and interpolated raw ($""", $$""") string literals — braces/delimiters inside these are no longer miscounted
  • Fix IndexOfClassToken to skip matches inside strings and comments
  • Remove CheckScopedBalance and ValidateBasicStructure — redundant with the now-correct CheckBalancedDelimiters
  • Make anchor_delete and anchor_replace use FindBestAnchorMatch — consistent with anchor_insert instead of using first-match
  • Rewrite _find_best_closing_brace_match in Python to use brace-depth analysis instead of indentation heuristics

Test plan

  • 26 new Python tests (test_script_apply_edits_local.py) — all pass
  • 666/666 Python tests pass (uv run pytest tests/ -v)
  • 16 new C# tests (ManageScriptDelimiterTests.cs) for CheckBalancedDelimiters and IndexOfClassToken
  • Manual torture tests via MCP: created scripts with $"", @"", $@"", $""", $$""" patterns — zero false positive validation errors
  • Structured edits (insert_method, replace_method, anchor_insert, anchor_replace, anchor_delete) all work correctly on scripts containing tricky string patterns
  • Run Unity EditMode tests in TestProjects/UnityMCPTests

🤖 Generated with Claude Code

Summary by Sourcery

Improve C# script editing robustness by using a shared lexer for delimiter-aware parsing, tightening delimiter validation and class detection, and making anchor-based edits brace-depth aware across C# and Python tooling.

New Features:

  • Introduce a reusable C# lexer and corresponding Python token iterator that understand modern C# string and comment forms for structural analysis.

Bug Fixes:

  • Fix delimiter balancing to ignore braces and delimiters inside verbatim, interpolated, raw, and combined C# string literals and comments.
  • Ensure IndexOfClassToken skips class keyword matches that appear inside strings or comments.
  • Prevent anchor matching and closing-brace selection from choosing braces that are inside strings or comments, preferring the correct class-level brace instead.

Enhancements:

  • Simplify validation logic by removing scoped/basic structure checks in favor of the enhanced CheckBalancedDelimiters implementation.
  • Unify anchor_insert, anchor_delete, and anchor_replace to all use a best-match selection strategy based on structural context rather than first regex match.

Tests:

  • Add extensive C# EditMode tests covering delimiter balancing and class token detection across complex C# string/comment scenarios.
  • Add Python integration tests for local script edit application, string-context detection, and brace-aware anchor/closing-brace matching.

Summary by CodeRabbit

  • Bug Fixes

    • Consistent delimiter/balance validation before and after edits to prevent corrupt edits.
    • More reliable anchor selection that prefers class-level/outer braces and avoids matches inside strings/comments.
    • Safer edit application with no-op detection, duplicate-insert protection, and clearer error messages.
    • Improved code-vs-non-code detection to avoid editing inside strings, chars, or comments.
  • Tests

    • Added extensive tests for string-context detection, brace/anchor selection, and local edit operations across C# string variants.
    • Updated unit tests for delimiter validation and class-token indexing; removed obsolete scoped-balance tests.

dsarno and others added 3 commits February 12, 2026 18:37
…iants

The ManageScript tool had 7 copy-pasted inline lexers that did not handle
verbatim strings (@""), interpolated strings ($""), or raw string literals,
causing false unbalanced braces errors on valid C# code like $"Score: {score}".

Extract a shared CSharpLexer struct that correctly handles all C# string/comment
types, rewrite all callers to use it, and remove redundant CheckScopedBalance
and ValidateBasicStructure methods. Fix IndexOfClassToken to skip matches inside
strings and comments. Fix anchor_insert to use depth-based best-match selection
instead of picking the first regex match (which selected method-level braces
over class-level ones).

On the Python side, add _is_in_string_context and _brace_depth_at_positions
helpers, and rewrite _find_best_closing_brace_match with depth-based scoring
that filters out matches inside strings/comments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arp_tokens generator

_is_in_string_context and _brace_depth_at_positions duplicated ~150 lines
of the same C# string/comment lexer. Extract a shared generator and rewrite
both as thin wrappers (-59 net lines, same behavior).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Add """...""" and $"""...""" (C# 11 interpolated raw string)
   support to both C# CSharpLexer and Python _iter_csharp_tokens.
   Dollar count determines interpolation threshold (N dollars = N
   consecutive braces to open an interpolation hole).

2. Make anchor_delete and anchor_replace use FindBestAnchorMatch
   (same as anchor_insert) instead of rx.Match for consistent
   target selection when multiple anchors match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 13, 2026

Reviewer's Guide

Refactors C# and Python script editing logic to use a shared C# lexer/tokenizer that correctly skips all C# string/comment forms when checking delimiters and anchors, replaces heuristic brace matching with brace-depth analysis, and aligns anchor-based edit operations and validation on the new, more accurate infrastructure with accompanying tests.

Class diagram for CSharpLexer-based delimiter and anchor utilities

classDiagram
    class ManageScript {
        +ApplyTextEdits(original, spans, validateMode) object
        +CheckBalancedDelimiters(text, line, expected) bool
        +TryComputeClassSpanBalanced(source, className, start, length, why) bool
        +TryComputeMethodSpan(source, methodName, start, length, why) bool
        +TryFindClassInsertionPoint(source, classStart, insertAt, why) bool
        +FindBestAnchorMatch(matches, text, pattern) Match
        +IndexOfClassToken(s, className) int
        +ValidateScriptSyntax(contents, level, errors) bool
    }

    class CSharpLexer {
        -string _text
        -int _pos
        -int _end
        -int _line
        -bool _inSingleComment
        -bool _inMultiComment
        +bool InNonCode
        +int Position
        +int Line
        +CSharpLexer(text, start, end)
        +Advance(c) bool
        -SkipInterpolatedStringBody(isVerbatim) void
        -SkipInterpolatedRawStringBody(dollarCount, quoteCount) void
    }

    ManageScript *-- CSharpLexer : uses

    class ScriptApplyEditsPy {
        +_iter_csharp_tokens(text) generator
        +_is_in_string_context(text, position) bool
        +_brace_depth_at_positions(text, positions) dict
        +_find_best_anchor_match(pattern, text, flags, prefer_last)
        +_find_best_closing_brace_match(matches, text)
        +_apply_edits_locally(original_text, edits) str
    }

    ScriptApplyEditsPy ..> ManageScript : mirrored_lexing_logic
Loading

File-Level Changes

Change Details Files
Replace ad-hoc delimiter scanning with a reusable CSharpLexer and updated CheckBalancedDelimiters implementation that correctly ignores all C# string and comment variants and returns precise error locations.
  • Introduce a CSharpLexer struct that advances through source while tracking whether the current position is in code vs. strings/comments, including verbatim, interpolated, interpolated-verbatim, raw, interpolated raw strings, and char literals.
  • Rewrite CheckBalancedDelimiters to drive off CSharpLexer tokens instead of manual flags, pushing/popping brace/paren/bracket stacks only when InNonCode is false and using lexer.Line for error reporting.
  • Remove scoped-only balance checking in ApplyTextEdits and rely solely on the final CheckBalancedDelimiters result for structural validation, simplifying relaxed vs. non-relaxed paths.
MCPForUnity/Editor/Tools/ManageScript.cs
Make class/method span computations and class token search string-aware by reusing CSharpLexer and avoiding matches inside strings/comments.
  • Update TryComputeClassSpanBalanced, TryComputeMethodSpan, and TryFindClassInsertionPoint to iterate with CSharpLexer instead of duplicating string/comment state machines, ensuring brace depth is only updated in real code.
  • Change IndexOfClassToken to enforce word boundaries around "class " and confirm the match is not within a string or comment by scanning with CSharpLexer up to the candidate index.
  • Adjust span calculations to use lexer.Position rather than raw indices when determining closing brace locations.
MCPForUnity/Editor/Tools/ManageScript.cs
Unify anchor-based edit operations on a brace-depth-aware FindBestAnchorMatch that prefers outer class-level braces and skips matches in strings/comments.
  • Introduce FindBestAnchorMatch that, for closing-brace patterns, computes brace depth at each candidate '}' using CSharpLexer and selects the shallowest (outermost) match, falling back to the last match otherwise.
  • Modify anchor_insert, anchor_delete, and anchor_replace to gather all regex matches, run them through FindBestAnchorMatch, and handle filtered-not-found cases distinctly from no matches.
  • Keep anchor_insert’s existing behavior of preferring the last match for non-brace patterns while reusing the same selection logic across operations.
MCPForUnity/Editor/Tools/ManageScript.cs
Replace Python-side indentation heuristics for finding the best closing brace with a C#-syntax-aware brace-depth analysis built on a shared C# token iterator.
  • Introduce _iter_csharp_tokens in script_apply_edits.py, a generator that lexes C# text into (position, char, is_code, interp_depth) while correctly handling all C# string and comment forms and interpolated raw strings.
  • Implement _brace_depth_at_positions to scan tokens, track brace depth in real code, and record the depth before each relevant '}' character.
  • Rewrite _find_best_closing_brace_match to filter matches inside strings/comments using _is_in_string_context, map each regex match to the actual '}' position, compute depths via _brace_depth_at_positions, and pick the shallowest, latest closing brace instead of using indentation-based scoring.
Server/src/services/tools/script_apply_edits.py
Add string-aware helpers on the Python side to avoid using anchors inside strings/comments and to regress-test local edit behavior.
  • Add _is_in_string_context using _iter_csharp_tokens to report whether a position lies within a string or comment and use it to filter out non-code matches in _find_best_closing_brace_match.
  • Ensure _find_best_anchor_match (Python) continues to prefer last vs. first matches but now cooperates with the improved closing-brace logic for structural anchors.
  • Extend _apply_edits_locally test coverage to include edits around interpolated strings, verifying that regex_replace and line-based edits behave correctly with complex string patterns.
Server/src/services/tools/script_apply_edits.py
Server/tests/integration/test_script_apply_edits_local.py
Remove obsolete scoped/structural validation helpers and update tests to target the new delimiter logic and class token search behavior.
  • Delete CheckScopedBalance and ValidateBasicStructure from ManageScript, as their responsibilities are covered by the corrected CheckBalancedDelimiters.
  • Update ValidateScriptSyntax to use CheckBalancedDelimiters directly and emit a structured error message with offending line and expected delimiter.
  • Remove CheckScopedBalance-related tests from ManageScriptValidationTests and add a new ManageScriptDelimiterTests suite that exercises verbatim, interpolated, raw, and mixed string forms plus IndexOfClassToken behavior in comments/strings.
MCPForUnity/Editor/Tools/ManageScript.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptDelimiterTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptDelimiterTests.cs.meta

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Refactors delimiter and anchor handling across C# editor tooling and Python backend: adds token-aware lexers/tokenizers, brace-depth-aware anchor resolution, unconditional balance validation around edits, reworks edit flows and error handling, and adds comprehensive tests for string/brace edge cases.

Changes

Cohort / File(s) Summary
C# Core Implementation
MCPForUnity/Editor/Tools/ManageScript.cs
Large refactor: removes per-edit scoped relaxed flow, enforces unconditional final balance checks, introduces CSharpLexer for code vs non-code scanning, adds FindBestAnchorMatch, reworks anchor_insert/delete/replace, strengthens preflight/postflight validation and error messaging, expands structured-edit handling (class/method ops).
Python Backend Enhancement
Server/src/services/tools/script_apply_edits.py
Adds _iter_csharp_tokens tokenizer, _is_in_string_context, _brace_depth_at_positions, and refactors closing-brace/anchor selection to prefer outer (shallowest) brace matches using tokenized context. Improves filtering of matches inside strings/comments.
Integration Tests (Python)
Server/tests/integration/test_script_apply_edits_local.py
New pytest module exercising tokenizer, string-context detection, brace-depth selection, anchor matching, and local edit application across C# string variants (verbatim, interpolated, raw, multi-dollar).
Unity EditMode Tests
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptDelimiterTests.cs
New Unity edit-mode tests covering delimiter balancing and class-token indexing over verbatim, interpolated, raw, multi-line strings, comments, and real-world script scenarios. Adds many focused test cases.
Unity Test Metadata
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptDelimiterTests.cs.meta
Adds .meta file for the new Unity test asset.
Test Cleanup
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs
Removes tests and reflection helper for deleted CheckScopedBalance; retains CheckBalancedDelimiters tests and fallback logic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • msanatan

Poem

🐰 A tiny lexer hops through code and string,
Skipping quotes and comments on a spring,
Finding anchors, counting braces neat,
So edits land tidy — no mismatched beat,
Hooray for tests that make the change complete!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (5 files):

⚔️ MCPForUnity/Editor/Helpers/AssetPathUtility.cs (content)
⚔️ MCPForUnity/Editor/Tools/ManageScript.cs (content)
⚔️ MCPForUnity/package.json (content)
⚔️ Server/src/services/tools/script_apply_edits.py (content)
⚔️ TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs (content)

These conflicts must be resolved before merging into beta.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing delimiter checking for C# string variants (verbatim, interpolated, raw, etc.), which is the core objective and primary focus of the pull request.
Description check ✅ Passed The PR description comprehensively covers all template sections including summary of changes, type of change (bug fix/refactoring), specific modifications, test plan with results, and related information.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/manage-script-delimiter-checking
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/manage-script-delimiter-checking
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
Server/src/services/tools/script_apply_edits.py (1)

367-374: _is_in_string_context re-scans from position 0 on every call — redundant with _brace_depth_at_positions.

_find_best_closing_brace_match calls _is_in_string_context per candidate (line 548), then calls _brace_depth_at_positions (line 555) which does another full scan. The latter already skips non-code characters — positions inside strings/comments simply won't appear in the returned dict, so depths.get(pos, float('inf')) naturally ranks them last (effectively filtering them out).

You could drop the _is_in_string_context filtering in _find_best_closing_brace_match entirely, avoiding O(N × len(text)) redundant work for N candidate matches.

♻️ Simplify _find_best_closing_brace_match by removing redundant filtering
     brace_positions: dict[int, object] = {}  # brace_pos → match
     for m in matches:
         for offset in range(m.start(), m.end()):
             if offset < len(text) and text[offset] == '}':
-                if not _is_in_string_context(text, offset):
-                    brace_positions[offset] = m
+                brace_positions[offset] = m
                 break

     if not brace_positions:
         return None

     depths = _brace_depth_at_positions(text, set(brace_positions.keys()))

+    # Positions inside strings/comments won't appear in depths (they're skipped
+    # by the lexer), so depths.get(pos, float('inf')) naturally ranks them last.
     # Score: prefer shallowest depth (outermost brace), then latest position
MCPForUnity/Editor/Tools/ManageScript.cs (2)

1019-1079: SkipInterpolatedStringBody: nested string handling inside interpolation holes is limited to regular strings.

Lines 1036–1047 handle a nested "..." string inside an interpolation hole, but only with \\ escape handling. If the interpolation hole contains a verbatim string (@"..."), another interpolated string ($"..."), or a raw string literal, those would be misparsed. This mirrors the same limitation in the Python-side _iter_csharp_tokens.

For most practical C# code this is fine, but deeply nested interpolation patterns like $"outer {$"inner {x}"}" could cause incorrect brace/delimiter tracking.


2133-2170: IndexOfClassToken string/comment filtering is correct but O(n²) in pathological cases.

For each candidate occurrence of "class ClassName", the code creates a new CSharpLexer from position 0 to idx + 1 (line 2158) and scans forward to determine if the position is inside a string or comment. If the source contains many false matches (e.g., "class Foo" appearing repeatedly in string literals), each creates a fresh scan from position 0.

For typical Unity scripts this is fine. If this ever becomes a bottleneck on very large generated files, a single-pass approach (scanning once and recording all "class ClassName" positions that are in code) would be more efficient.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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 - I've left some high level feedback:

  • IndexOfClassToken currently instantiates a new CSharpLexer and scans from the start of the file up to each candidate index on every loop iteration; if this is used on larger files, consider refactoring to either precompute non-code ranges once or reuse a single lexer pass to avoid the O(N^2) worst-case behavior.
  • The heuristic in FindBestAnchorMatch for detecting "closing brace" patterns (pattern.Contains("}") && (pattern.Contains("$") || pattern.EndsWith(@"\s*"))) is fairly brittle; it may be worth either making the caller explicitly indicate when a pattern is intended to target closing braces, or tightening this detection to avoid misclassifying unrelated patterns that happen to match those string conditions.
  • In the Python path, _find_best_closing_brace_match calls _is_in_string_context for each regex match, and _is_in_string_context re-iterates from the start of the file each time; if you expect many candidate matches in large files, consider caching tokenization results or computing brace depths and string/comment ranges in a single pass to reduce repeated work.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- IndexOfClassToken currently instantiates a new CSharpLexer and scans from the start of the file up to each candidate index on every loop iteration; if this is used on larger files, consider refactoring to either precompute non-code ranges once or reuse a single lexer pass to avoid the O(N^2) worst-case behavior.
- The heuristic in FindBestAnchorMatch for detecting "closing brace" patterns (`pattern.Contains("}") && (pattern.Contains("$") || pattern.EndsWith(@"\s*"))`) is fairly brittle; it may be worth either making the caller explicitly indicate when a pattern is intended to target closing braces, or tightening this detection to avoid misclassifying unrelated patterns that happen to match those string conditions.
- In the Python path, _find_best_closing_brace_match calls _is_in_string_context for each regex match, and _is_in_string_context re-iterates from the start of the file each time; if you expect many candidate matches in large files, consider caching tokenization results or computing brace depths and string/comment ranges in a single pass to reduce repeated work.

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Tools/ManageScript.cs`:
- Line 1052: The comment points out that when skipping C-style /* ... */
comments the code unconditionally does `_pos += 2` after the loop which can
overshoot if the closing `*/` was not found; update the logic in the
comment-skipping sites (the inline comment branch and the analogous place inside
SkipInterpolatedRawStringBody) to only advance by 2 when `_pos + 1 < _end` and
`_text[_pos] == '*' && _text[_pos + 1] == '/'` (i.e., detect the actual
terminator) — otherwise do not increment past the end and just exit so Position
remains within range. Ensure both occurrences use the same guarded check.
🧹 Nitpick comments (5)
Server/src/services/tools/script_apply_edits.py (2)

367-374: _is_in_string_context performs a full-text scan from position 0 each invocation.

In _find_best_closing_brace_match, this is called once per candidate match (line 543), and then _brace_depth_at_positions does yet another full pass. For k matches in a file of length n, that's O(k·n + n) total tokenizer work. This is acceptable when k is small (typically single-digit), but for adversarial or very long files it could be noticeable.

A single-pass approach (scanning once and collecting both "is-code" status at match starts and brace depths) would reduce this to O(n), but is only worth doing if profiling shows a bottleneck.


523-571: _find_best_closing_brace_match — string-context filter checks m.start(), not the } position.

Line 543 filters matches where m.start() is inside a string/comment. However, for a regex like ^\s*}\s*$, m.start() is the beginning of the line (whitespace), while the } is later in the match. If a line starts in code but a } appears inside a string on that same line, the filter could miss it.

In practice this is very unlikely because ^\s*}\s*$ requires the line to contain only whitespace and a }, so quote characters can't be present. Still, a more robust approach would filter based on the actual } position.

As a secondary defence, unrecognized } positions fall back to float('inf') depth (line 565), so they're naturally de-prioritised — the impact is limited.

Suggested: filter on the actual brace position instead of match start
-    # Filter out matches inside strings/comments
-    matches = [m for m in matches if not _is_in_string_context(text, m.start())]
-    if not matches:
-        return None
-
     # Find the position of the '}' character within each match
     brace_positions: dict[int, object] = {}  # brace_pos → match
     for m in matches:
-        # The match may contain whitespace around '}'; find the actual '}'
         for offset in range(m.start(), m.end()):
             if offset < len(text) and text[offset] == '}':
-                brace_positions[offset] = m
+                if not _is_in_string_context(text, offset):
+                    brace_positions[offset] = m
                 break
 
     if not brace_positions:
-        return matches[-1]  # fallback
+        return None
Server/tests/integration/test_script_apply_edits_local.py (1)

213-258: Async test setup is correct, but consider adding a negative test for _apply_edits_locally.

The current tests verify happy-path behavior. A test that expects RuntimeError for an unknown op or out-of-bounds replace_range would strengthen regression coverage for error paths.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptDelimiterTests.cs (2)

171-201: IndexOfClassToken tests cover the key scenarios — consider adding an edge-case test.

The existing tests verify normal code, comment skipping, string skipping, and fallthrough to later matches. One missing scenario worth adding: a class token that appears immediately after a closing string quote (e.g., string s = "text";class Foo { }), which exercises the boundary detection discussed in the IndexOfClassToken review for ManageScript.cs.


205-228: Reflection helpers duplicate those in ManageScriptValidationTests.cs.

CallCheckBalancedDelimiters and CallIndexOfClassToken appear in both test files. Consider extracting them into a shared test utility class to avoid drift if the private method signatures change.

Suggested shared helper class
// In a shared file like TestHelpers/ManageScriptReflectionHelpers.cs
internal static class ManageScriptReflectionHelpers
{
    public static bool CallCheckBalancedDelimiters(string text, out int line, out char expected)
    {
        line = 0;
        expected = '\0';
        var method = typeof(ManageScript).GetMethod("CheckBalancedDelimiters",
            BindingFlags.NonPublic | BindingFlags.Static);
        var parameters = new object[] { text, 0, '\0' };
        var result = (bool)method.Invoke(null, parameters);
        line = (int)parameters[1];
        expected = (char)parameters[2];
        return result;
    }

    public static int CallIndexOfClassToken(string source, string className)
    {
        var method = typeof(ManageScript).GetMethod("IndexOfClassToken",
            BindingFlags.NonPublic | BindingFlags.Static);
        return (int)method.Invoke(null, new object[] { source, className });
    }
}

1. Guard _pos += 2 after /* comment scanning in both
   SkipInterpolatedStringBody and SkipInterpolatedRawStringBody —
   only advance past */ when the terminator was actually found,
   preventing overshoot past _end on unterminated comments.

2. Filter _find_best_closing_brace_match on the actual }
   position rather than m.start() (which points at leading
   whitespace for patterns like ^\s*}\s*$).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dsarno dsarno merged commit cda07f7 into beta Feb 13, 2026
4 checks passed
@dsarno dsarno deleted the fix/manage-script-delimiter-checking branch February 13, 2026 04:24
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.

1 participant