Skip to content

SourceText.GetTextChanges produces invalid edits after successive changes #41413

@ajaybhargavb

Description

@ajaybhargavb

When working on formatting for .razor files, I noticed something odd. Under certain conditions, GetTextChanges would return invalid edits. I'm not sure exactly under what conditions. I was able to come up with a (somewhat) minimal repro.

Version Used:
Microsoft.CodeAnalysis 3.4.0
dotnet 3.1.101

Steps to Reproduce:

  1. dotnet new console -o SourceTextBug
  2. cd SourceTextBug
  3. Replace Program.cs content with the following,
Program.cs

using System;
using Microsoft.CodeAnalysis.Text;

namespace SourceTextBug
{
    class Program
    {
        static void Main(string[] args)
        {
            var content = @"@functions{
    public class Foo
    {
void Method()
{
    
}
    }
}";
            Console.WriteLine("Initial:");
            Console.WriteLine(content);
            Console.WriteLine();

            var text = SourceText.From(content);
            var edits1 = new TextChange[]
            {
                new TextChange(new TextSpan(39, 0), "    "),
                new TextChange(new TextSpan(42, 0), "            "),
                new TextChange(new TextSpan(57, 0), "            "),
                new TextChange(new TextSpan(58, 0), "\r\n"),
                new TextChange(new TextSpan(64, 2), "        "),
                new TextChange(new TextSpan(69, 0), "    "),
            };
            var changedText = text.WithChanges(edits1);

            Console.WriteLine("After first set of edits:");
            Console.WriteLine(changedText.ToString());
            Console.WriteLine();

            var edits2 = new TextChange[]
            {
                new TextChange(new TextSpan(35, 4), string.Empty),
                new TextChange(new TextSpan(46, 4), string.Empty),
                new TextChange(new TextSpan(73, 4), string.Empty),
                new TextChange(new TextSpan(88, 0), "    "),
                new TextChange(new TextSpan(90, 4), string.Empty),
                new TextChange(new TextSpan(105, 4), string.Empty),
            };
            var changedText2 = changedText.WithChanges(edits2);

            Console.WriteLine("After second set of edits:");
            Console.WriteLine(changedText2.ToString());
            Console.WriteLine();

            Console.WriteLine("Diff of final text vs original:");
            var changes = changedText2.GetTextChanges(text);
            foreach (var change in changes)
            {
                Console.WriteLine(change);
            }
        }
    }
}

4. Add the following in the csproj,
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.4.0" />`
  1. dotnet run

Expected Behavior:
The set of TextChanges produced should be valid and not overlap.

Actual Behavior:
Overlapping changes are produced.

Output

Initial:
@functions{
    public class Foo
    {
void Method()
{

}
    }
}

After first set of edits:
@functions{
    public class Foo
        {
            void Method()
            {

            }
        }
}

After second set of edits:
@functions{
    public class Foo
    {
        void Method()
        {

        }
    }
}

Diff of final text vs original:
TextChange: { [35..39), "    " }
TextChange: { [42..42), "        " }
TextChange: { [57..57), "        " }
TextChange: { [58..58), "
" }
TextChange: { [64..66), "        }
 " }
TextChange: { [62..66), "" }
TextChange: { [69..69), "    " }
TextChange: { [73..77), "" }

Notice the two overlapping edits,
[64..66) and [62..66)

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions