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

Fix null encoding in new document in move type action #74623

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings.MoveType;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
Expand Down Expand Up @@ -4251,6 +4252,90 @@ static void F()
EndDebuggingSession(debuggingSession);
}

/// <summary>
/// Scenario:
/// - F5
/// - edit source and apply code fix to move type to file XYZ.cs
/// - continue execution
/// </summary>
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72984")]
public async Task EditAndContinueAfterApplyingMoveTypeToFileCodeFix()
{
const string movedType = "AnotherClass";

const string source = $$"""
class {{movedType}}
{
public const bool True = true;
}

class Test
{
static bool B() => {{movedType}}.True;
static void G() { while (B()); }

static void F()
{
G();
}
}
""";

const string modifiedSource = $$"""
class Test
{
static bool A() => {{movedType}}.True;
static bool B() => A();
static void G() { while (B()); }

static void F()
{
H();
}

static void H()
{
G();
}
}
""";

var moduleId = EmitAndLoadLibraryToDebuggee(source);

using var workspace = CreateWorkspace(out var solution, out var service);
(solution, var document) = AddDefaultTestProject(solution, source);
var documentId = document.Id;
var oldProject = document.Project;

var debuggingSession = await StartDebuggingSessionAsync(service, solution);

// Apply code fix: Move type to AnotherClass.cs

var moveTypeService = document.GetLanguageService<IMoveTypeService>();
var root = await document.GetSyntaxRootAsync();
var span = root.DescendantTokens()
.Where(s => s.Text is movedType)
.FirstOrDefault()
.Span;
var modifiedSolution = await moveTypeService.GetModifiedSolutionAsync(document, span, MoveTypeOperationKind.MoveType, cancellationToken: default);

// Apply edit on remaining document: source after code fix -> modifiedSource

modifiedSolution = modifiedSolution.WithDocumentText(document.Id, CreateText(modifiedSource));

var newProject = modifiedSolution.GetProject(oldProject.Id);
Assert.Equal(1, oldProject.DocumentIds.Count);
Assert.Equal(2, newProject.DocumentIds.Count);

var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, modifiedSolution);
Assert.Empty(emitDiagnostics);
Assert.False(updates.Updates.IsEmpty);
Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);

CommitSolutionUpdate(debuggingSession);
EndDebuggingSession(debuggingSession);
}

[Fact]
public async Task MultiSession()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public async Task AddClickHandler()
Assert.Contains(@"this.SomeButton.Click += new System.EventHandler(this.ExecuteWhenButtonClicked);", designerActualText);
await TestServices.SolutionExplorer.OpenFileAsync(project, "Form1.cs", HangMitigatingCancellationToken);
var codeFileActualText = await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken);
Assert.Contains(@" public partial class Form1 : Form
Assert.Contains(@" public partial class Form1: Form
{
public Form1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,23 +495,8 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode)
var newTextVersion = GetNewerVersion();
var newTreeVersion = GetNewTreeVersionForUpdatedTree(newRoot, newTextVersion, mode);

// determine encoding
Encoding? encoding;

if (TryGetSyntaxTree(out var priorTree))
{
// this is most likely available since UpdateTree is normally called after modifying the existing tree.
encoding = priorTree.Encoding;
}
else if (TryGetText(out var priorText))
{
encoding = priorText.Encoding;
}
else
{
// the existing encoding was never observed so is unknown.
encoding = null;
}
// use the encoding that we get from the new root
var encoding = newRoot.SyntaxTree.Encoding;
Copy link
Member

Choose a reason for hiding this comment

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

@jasonmalinowski I don't have context on the previous logic. Should it ask be removed (like the current pr does it), it should we just fall back to getting the encoding from the tree if the other checks fail?

Copy link
Contributor Author

@Rekkonnect Rekkonnect Dec 2, 2024

Choose a reason for hiding this comment

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

Note the previous comment from tmat: #74623 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Ok. Seems reasonable. Merging in. If you have any concerns @jason let us know


var syntaxTreeFactory = LanguageServices.GetRequiredService<ISyntaxTreeFactoryService>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2893,14 +2893,13 @@ class C { }";
Assert.Equal(encoding.EncodingName, text.Encoding.EncodingName);
Assert.Equal(fileContent, text.ToString());

// update root blindly again, after observing encoding, see that now encoding is known
// update root blindly again, after observing encoding, see that encoding is overridden to null
var doc3 = document.WithSyntaxRoot(gen.CompilationUnit()); // empty CU
var doc3text = await doc3.GetTextAsync();
Assert.NotNull(doc3text.Encoding);
Assert.Equal(encoding.EncodingName, doc3text.Encoding.EncodingName);
Assert.Null(doc3text.Encoding);
var doc3tree = await doc3.GetSyntaxTreeAsync();
Assert.Equal(doc3text.Encoding, doc3tree.GetText().Encoding);
Assert.Equal(doc3text.Encoding, doc3tree.Encoding);
Assert.Null(doc3tree.Encoding);
Assert.Null(doc3tree.GetText().Encoding);

// change doc to have no encoding, still succeeds at writing to disk with old encoding
var root = await document.GetSyntaxRootAsync();
Expand Down
Loading