diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
index 5c501128a87f7..b31a2c3f21bf8 100644
--- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
+++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
@@ -587,4 +587,4 @@
Convert all extension methods in '{0}' to extension
-
\ No newline at end of file
+
diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs
index 13de4a97b5695..de6a871665996 100644
--- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs
+++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs
@@ -929,7 +929,7 @@ internal override bool IsDeclarationWithInitializer(SyntaxNode declaration)
}
internal override bool IsPrimaryConstructorDeclaration(SyntaxNode declaration)
- => declaration.Parent is TypeDeclarationSyntax { ParameterList: var parameterList } && parameterList == declaration;
+ => declaration.Parent is TypeDeclarationSyntax { ParameterList: var parameterList } and not ExtensionBlockDeclarationSyntax && parameterList == declaration;
internal override bool IsConstructorWithMemberInitializers(ISymbol symbol, CancellationToken cancellationToken)
{
@@ -1643,6 +1643,10 @@ private static bool GroupBySignatureEquivalent(IMethodSymbol? oldMethod, IMethod
return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword,
typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier);
+ case SyntaxKind.ExtensionBlockDeclaration:
+ var extensionBlockDeclaration = (ExtensionBlockDeclarationSyntax)node;
+ return extensionBlockDeclaration.Keyword.Span;
+
case SyntaxKind.BaseList:
var baseList = (BaseListSyntax)node;
return baseList.Types.Span;
@@ -2248,6 +2252,9 @@ internal override string GetDisplayName(IMethodSymbol symbol)
return CSharpFeaturesResources.local_variable_declaration;
+ case SyntaxKind.ExtensionBlockDeclaration:
+ return FeaturesResources.extension_block;
+
default:
return null;
}
@@ -2395,6 +2402,11 @@ private void ClassifyInsert(SyntaxNode node)
{
switch (node.Kind())
{
+ case SyntaxKind.ExtensionBlockDeclaration:
+ // https://github.com/dotnet/roslyn/issues/78959 Disallowed for now
+ ReportError(RudeEditKind.Insert);
+ return;
+
case SyntaxKind.ExternAliasDirective:
ReportError(RudeEditKind.Insert);
return;
@@ -2425,6 +2437,11 @@ private void ClassifyDelete(SyntaxNode oldNode)
ReportError(RudeEditKind.Delete);
return;
+ case SyntaxKind.ExtensionBlockDeclaration:
+ // https://github.com/dotnet/roslyn/issues/78959 Disallowed for now
+ ReportError(RudeEditKind.Delete);
+ return;
+
case SyntaxKind.AttributeList:
case SyntaxKind.Attribute:
// To allow removal of attributes we need to check if the removed attribute
diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs
index 11bb390f9acfc..6001081d45308 100644
--- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs
+++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs
@@ -59,6 +59,7 @@ internal enum Label
TypeDeclaration,
EnumDeclaration,
+ ExtensionBlockDeclaration, // tied to parent
BaseList, // tied to parent
PrimaryConstructorBase, // tied to parent
DelegateDeclaration,
@@ -195,6 +196,7 @@ private static int TiedToAncestor(Label label)
case Label.Parameter:
case Label.AttributeList:
case Label.Attribute:
+ case Label.ExtensionBlockDeclaration:
return 1;
// Statement syntax
@@ -681,6 +683,9 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bo
}
break;
+
+ case SyntaxKind.ExtensionBlockDeclaration:
+ return Label.ExtensionBlockDeclaration;
}
// If we got this far, its an unlabelled node. For top
@@ -917,6 +922,10 @@ protected override bool TryComputeWeightedDistance(SyntaxNode leftNode, SyntaxNo
distance = ComputeDistance((AttributeSyntax)leftNode, (AttributeSyntax)rightNode);
return true;
+ case SyntaxKind.ExtensionBlockDeclaration:
+ distance = ComputeDistance((ExtensionBlockDeclarationSyntax)leftNode, (ExtensionBlockDeclarationSyntax)rightNode);
+ return true;
+
default:
var leftName = TryGetName(leftNode);
var rightName = TryGetName(rightNode);
@@ -1435,6 +1444,9 @@ private static double CombineOptional(
case SyntaxKind.DelegateDeclaration:
return ((DelegateDeclarationSyntax)node).Identifier;
+ case SyntaxKind.ExtensionBlockDeclaration:
+ return null;
+
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
case SyntaxKind.VariableDeclaration:
diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs
index a425fe95b93df..4e659c1ff7669 100644
--- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs
+++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs
@@ -56,7 +56,7 @@ CompilationUnitSyntax unit when unit.ContainsGlobalStatements()
when !fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)
=> new FieldWithInitializerDeclarationBody(variableDeclarator),
- ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration }
+ ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration and not ExtensionBlockDeclarationSyntax }
=> typeDeclaration is { BaseList.Types: [PrimaryConstructorBaseTypeSyntax { }, ..] }
? new PrimaryConstructorWithExplicitInitializerDeclarationBody(typeDeclaration)
: new PrimaryConstructorWithImplicitInitializerDeclarationBody(typeDeclaration),
diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs
index eadc7ab944ea9..1623ae5b97aaf 100644
--- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs
+++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs
@@ -105,6 +105,7 @@ public static string GetResource(string keyword)
"top-level statement" => CSharpFeaturesResources.top_level_statement,
"top-level code" => CSharpFeaturesResources.top_level_code,
"class with explicit or sequential layout" => string.Format(FeaturesResources.class_with_explicit_or_sequential_layout),
+ "extension block" => FeaturesResources.extension_block,
_ => null
};
diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs
index 1a96a3566c8bd..8fa4682670cb4 100644
--- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs
+++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs
@@ -6247,6 +6247,2104 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndInsertVirtual()
#endregion
+ #region Extensions
+
+ [Fact]
+ public void Extension_AddedBlock_NoMethod()
+ {
+ var src1 = """
+ static class Ext
+ {
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("""
+ Insert [extension(object)
+ {
+ }]@25
+ """,
+ "Insert [(object)]@34",
+ "Insert [object]@35");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "object", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_AddedSecondBlock()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object)
+ {
+ }
+ extension(string)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("""
+ Insert [extension(string)
+ {
+ }]@62
+ """,
+ "Insert [(string)]@71",
+ "Insert [string]@72");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "string", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_AddMethod()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Insert [void M() {}]@61",
+ "Insert [()]@67"
+ );
+
+ edits.VerifySemanticDiagnostics(capabilities: EditAndContinueCapabilities.AddMethodToExistingType,
+ diagnostics: Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_RemoveMethod()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Delete [void M() {}]@61",
+ "Delete [()]@67"
+ );
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_AddName()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object x)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object]@35 -> [object x]@35");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object x", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_RemoveName()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object x)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object x]@35 -> [object]@35");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_Rename()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object x)
+ {
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object o]@35 -> [object x]@35");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object x", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_Type_ChangeNullability()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object? o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object o]@35 -> [object? o]@35");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object? o", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_Type_ChangeType()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(string o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object o]@35 -> [string o]@35");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "string o", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_AddAttribute()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension([A]object o)
+ {
+ void M() {}
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [object o]@155 -> [[A]object o]@155");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_DeleteAttribute()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension([A]object o)
+ {
+ void M() {}
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [[A]object o]@155 -> [object o]@155");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_UpdateAttributeArgument()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension([System.Obsolete("a")]object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension([System.Obsolete("b")]object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("""Update [[System.Obsolete("a")]object o]@35 -> [[System.Obsolete("b")]object o]@35""");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_AddModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(ref int i)
+ {
+ void M(int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Update [int i]@35 -> [ref int i]@35",
+ "Update [int x]@65 -> [int y]@69"
+ );
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "ref int i", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_RemoveModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(ref int i)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Update [ref int i]@35 -> [int i]@35",
+ "Update [int x]@69 -> [int y]@65");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int i", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_ExtensionParameter_ChangeModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(ref int i)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(in int i)
+ {
+ void M(int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Update [ref int i]@35 -> [in int i]@35",
+ "Update [int x]@69 -> [int y]@68"
+ );
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "in int i", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Add()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(T t) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Insert []@34",
+ "Insert [T]@35",
+ "Insert [T t]@71"
+ );
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "T", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "T", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "T t", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "T t", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Delete()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(T t) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Delete []@34",
+ "Delete [T]@35",
+ "Delete [T t]@71"
+ );
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Rename()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(T o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(U o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Update [T]@35 -> [U]@35",
+ "Update [T o]@38 -> [U o]@38");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "U", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "U", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "U o", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Constraint_Add()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(T o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(T o) where T : class
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Insert [where T : class]@43");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "where T : class", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "T", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Constraint_Delete()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(T o) where T : class
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(T o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Delete [where T : class]@43");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_TypeParameter_Constraint_Update()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(T o) where T : class
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(T o) where T : struct
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [where T : class]@43 -> [where T : struct]@43");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "where T : struct", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "where T : struct", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Rename()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void N() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [void M() {}]@61 -> [void N() {}]@61");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "void N()", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Method_ChangeModifier_Static()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ static void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [void M() {}]@61 -> [static void M() {}]@61");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "static void M()", GetResource("extension block")));
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Extension_Method_ChangeModifier_Visibility(
+ [CombinatorialValues("public ", "internal ", "protected ", "private ", "protected internal ", "private protected ", "")]
+ string oldVisibility,
+ [CombinatorialValues("public ", "internal ", "protected ", "private ", "protected internal ", "private protected ", "")]
+ string newVisibility)
+ {
+ if (oldVisibility == newVisibility)
+ return;
+
+ var src1 = $$"""
+ static class Ext
+ {
+ extension(object o)
+ {
+ {{oldVisibility}}void M() {}
+ }
+ }
+ """;
+
+ var src2 = $$"""
+ static class Ext
+ {
+ extension(object o)
+ {
+ {{newVisibility}}void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits($$"""Update [{{oldVisibility}}void M() {}]@61 -> [{{newVisibility}}void M() {}]@61""");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, $"{newVisibility}void M()", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_Insert()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Insert [int x]@68");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int x", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int x", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_Delete()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Delete [int x]@68");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_Rename()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M(int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [int x]@68 -> [int y]@68");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_AddModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(ref int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [int x]@65 -> [ref int y]@65");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "ref int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "ref int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_RemoveModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(ref int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [ref int x]@65 -> [int y]@65");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_Parameter_ChangeModifier()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(ref int x) {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(int i)
+ {
+ void M(in int y) {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [ref int x]@65 -> [in int y]@65");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "in int y", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "in int y", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Method_BodyUpdate()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int M() => 1;
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int M() => 2;
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("Update [int M() => 1;]@61 -> [int M() => 2;]@61");
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "int M()", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Method_AddSecondInBlock()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ void N() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ "Insert [void N() {}]@82",
+ "Insert [()]@88"
+ );
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "void N()", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Block_Reorder()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ extension(string s)
+ {
+ void N() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(string s)
+ {
+ void N() {}
+ }
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits("""
+ Reorder [extension(string s)
+ {
+ void N() {}
+ }]@85 -> @25
+ """);
+
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Block_ChangeParent()
+ {
+ var src1 = """
+ static class Ext1
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+
+ static class Ext2
+ {
+ }
+ """;
+
+ var src2 = """
+ static class Ext1
+ {
+ }
+
+ static class Ext2
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ """
+ Insert [extension(object o)
+ {
+ void M() {}
+ }]@53
+ """,
+ "Insert [(object o)]@62",
+ "Insert [void M() {}]@89",
+ "Insert [object o]@63",
+ "Insert [()]@95",
+ """
+ Delete [extension(object o)
+ {
+ void M() {}
+ }]@26
+ """,
+ "Delete [(object o)]@35",
+ "Delete [object o]@36",
+ "Delete [void M() {}]@62",
+ "Delete [()]@68");
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Delete, "static class Ext1", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Delete, "static class Ext1", "extension block 'extension(object)'"),
+ Diagnostic(RudeEditKind.Update, "static class Ext1", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static class Ext1", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Block_DeleteSecondBlock()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ extension(string s)
+ {
+ void N() {}
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+
+ edits.VerifyEdits(
+ """
+ Delete [extension(string s)
+ {
+ void N() {}
+ }]@85
+ """,
+ "Delete [(string s)]@94",
+ "Delete [string s]@95",
+ "Delete [void N() {}]@121",
+ "Delete [()]@127"
+ );
+
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Delete, "static class Ext", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Delete, "static class Ext", DeletedSymbolDisplay(FeaturesResources.extension_block, "extension(string)")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void ExtensionBlock_SurroundWithRemoveBlock()
+ {
+ var src1 = """
+ static class Ext
+ {
+ static void M() {}
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ static void M() {}
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Insert [extension(object o)
+ {
+ static void M() {}
+ }]@25
+ """,
+ "Insert [(object o)]@34",
+ "Insert [static void M() {}]@61",
+ "Insert [object o]@35",
+ "Insert [()]@74",
+ "Delete [static void M() {}]@29",
+ "Delete [()]@42"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static void M()", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block"))
+ );
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits(
+ "Insert [static void M() {}]@29",
+ "Insert [()]@42",
+ """
+ Delete [extension(object o)
+ {
+ static void M() {}
+ }]@25
+ """,
+ "Delete [(object o)]@34",
+ "Delete [object o]@35",
+ "Delete [static void M() {}]@61",
+ "Delete [()]@74"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Delete, "static class Ext", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Delete, "static class Ext", DeletedSymbolDisplay(FeaturesResources.extension_block, "extension(object)")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_AddRemove()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ "Insert [int P { get => 1; }]@61",
+ "Insert [{ get => 1; }]@67",
+ "Insert [get => 1;]@69"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "get", GetResource("extension block"))
+ );
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits(
+ "Delete [int P { get => 1; }]@61",
+ "Delete [{ get => 1; }]@67",
+ "Delete [get => 1;]@69"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Rename()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int Q { get => 1; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [int P { get => 1; }]@61 -> [int Q { get => 1; }]@61");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int Q", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "int Q", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Accessor_Add_Set()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Insert [set { }]@79");
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_Accessor_Remove_Set()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Delete [set { }]@79");
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_Accessor_Add_Get()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { set { } }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Insert [get => 1;]@69");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "{", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Accessor_Remove_Get()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { set { } }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Delete [get => 1;]@69");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "{", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_ReturnType_Change()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ string P { get => ""; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Update [int P { get => 1; }]@61 -> [string P { get => ""; }]@61
+ """,
+ """
+ Update [get => 1;]@69 -> [get => "";]@72
+ """
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "string P", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "string P", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Attribute_Add()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ [A]int P { get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [int P { get => 1; }]@181 -> [[A]int P { get => 1; }]@181");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "1", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Attribute_Remove()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ [A]int P { get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [[A]int P { get => 1; }]@181 -> [int P { get => 1; }]@181");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "1", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Property_Attribute_Update()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ [System.Obsolete("a")]int P { get => 1; }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ [System.Obsolete("b")]int P { get => 1; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Update [[System.Obsolete("a")]int P { get => 1; }]@61 -> [[System.Obsolete("b")]int P { get => 1; }]@61
+ """
+ );
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_AccessorAttribute_Add_Remove()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { [A]get => 1; }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits1 = GetTopEdits(src1, src2);
+ edits1.VerifyEdits("Update [get => 1;]@189 -> [[A]get => 1;]@189");
+ edits1.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")));
+
+ var edits2 = GetTopEdits(src2, src1);
+ edits2.VerifyEdits("Update [[A]get => 1;]@189 -> [get => 1;]@189");
+ edits2.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_AccessorAttribute_Add_Remove_Set()
+ {
+ var src1 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var src2 = s_attributeSource + """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; [A]set { } }
+ }
+ }
+
+ class A : System.Attribute {}
+ """;
+
+ var edits1 = GetTopEdits(src1, src2);
+ edits1.VerifyEdits("Update [set { }]@199 -> [[A]set { }]@199");
+ edits1.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block")));
+
+ var edits2 = GetTopEdits(src2, src1);
+ edits2.VerifyEdits("Update [[A]set { }]@199 -> [set { }]@199");
+ edits2.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_Getter_BodyUpdate()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 2; }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [get => 1;]@69 -> [get => 2;]@69");
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Property_Setter_BodyUpdate()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { } }
+ }
+ }
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(object o)
+ {
+ int P { get => 1; set { var _ = 1; } }
+ }
+ }
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [set { }]@79 -> [set { var _ = 1; }]@79");
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Operator_AddRemove()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ }
+ }
+
+ class C {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ "Insert [public static int operator +(C a, C b) => 1;]@56",
+ "Insert [(C a, C b)]@84",
+ "Insert [C a]@85",
+ "Insert [C b]@90"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.InsertOperator, "public static int operator +(C a, C b)", "operator"),
+ Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C b", GetResource("extension block"))
+ );
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits(
+ "Delete [public static int operator +(C a, C b) => 1;]@56",
+ "Delete [(C a, C b)]@84",
+ "Delete [C a]@85",
+ "Delete [C b]@90"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeOperator()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator -(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [public static int operator +(C a, C b) => 1;]@56 -> [public static int operator -(C a, C b) => 1;]@56");
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator -(C a, C b)", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Operator_AddRemoveAttribute()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ [A]
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Update [public static int operator +(C a, C b) => 1;]@56 -> [[A]
+ public static int operator +(C a, C b) => 1;]@56
+ """
+ );
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block")));
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits(
+ """
+ Update [[A]
+ public static int operator +(C a, C b) => 1;]@56 -> [public static int operator +(C a, C b) => 1;]@56
+ """
+ );
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeAttribute()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ [System.Obsolete("")]
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ [System.Obsolete("ERR")]
+ public static int operator -(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Update [[System.Obsolete("")]
+ public static int operator +(C a, C b) => 1;]@56 -> [[System.Obsolete("ERR")]
+ public static int operator -(C a, C b) => 1;]@56
+ """
+ );
+ edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator -(C a, C b)", GetResource("extension block")));
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeParameter_Type()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, object b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [C b]@90 -> [object b]@90");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "object b", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "object b", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeParameter_Name()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C c) => 1;
+ }
+ }
+
+ class C {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [C b]@90 -> [C c]@90");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeParameter_AddRemoveAttribute()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +(C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +([A] C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits("Update [C a]@85 -> [[A] C a]@85");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block"))
+ );
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits("Update [[A] C a]@85 -> [C a]@85");
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block"))
+ );
+ }
+
+ [Fact]
+ public void Extension_Operator_ChangeParameter_ChangeAttribute()
+ {
+ var src1 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +([System.Obsolete("")] C a, C c) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var src2 = """
+ static class Ext
+ {
+ extension(C c)
+ {
+ public static int operator +([System.Obsolete("ERR")] C a, C b) => 1;
+ }
+ }
+
+ class C {}
+ class A : System.Attribute {}
+ """;
+
+ var edits = GetTopEdits(src1, src2);
+ edits.VerifyEdits(
+ """
+ Update [[System.Obsolete("")] C a]@85 -> [[System.Obsolete("ERR")] C a]@85
+ """,
+ "Update [C c]@112 -> [C b]@115"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C b", GetResource("extension block"))
+ );
+
+ edits = GetTopEdits(src2, src1);
+ edits.VerifyEdits(
+ """
+ Update [[System.Obsolete("ERR")] C a]@85 -> [[System.Obsolete("")] C a]@85
+ """,
+ "Update [C b]@115 -> [C c]@112"
+ );
+ edits.VerifySemanticDiagnostics(
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")),
+ Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block"))
+ );
+ }
+
+ #endregion
+
#region Namespaces
[Fact]
diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs
index e96503cc24680..51f6544991a11 100644
--- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs
+++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs
@@ -2914,6 +2914,17 @@ newSymbol is IPropertySymbol newProperty &&
continue;
}
+ if (oldSymbol is { ContainingType.IsExtension: true })
+ {
+ // This is inside a new extension declaration, and not currently supported.
+ // https://github.com/dotnet/roslyn/issues/78959
+ diagnosticContext.Report(RudeEditKind.Update,
+ oldDeclaration,
+ cancellationToken,
+ [FeaturesResources.extension_block]);
+ continue;
+ }
+
ReportDeletedMemberActiveStatementsRudeEdits();
var rudeEditKind = RudeEditKind.Delete;
@@ -3069,6 +3080,18 @@ newSymbol is IPropertySymbol newProperty &&
ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null);
}
+ if (newContainingType.IsExtension)
+ {
+ // This is a new extension declaration, and not currently supported.
+ // https://github.com/dotnet/roslyn/issues/78959
+ diagnosticContext.Report(RudeEditKind.Update,
+ cancellationToken,
+ GetDiagnosticSpan(newDeclaration, EditKind.Insert),
+ [FeaturesResources.extension_block]);
+
+ continue;
+ }
+
if (oldContainingType == null)
{
// If a type parameter is inserted into the parameter list of a type declaration, the symbol key won't be resolved (because the arities do not match).
@@ -4225,6 +4248,14 @@ private void ReportUpdatedSymbolDeclarationRudeEdits(
hasGeneratedAttributeChange = false;
hasGeneratedReturnTypeAttributeChange = false;
+ if (IsOrIsContainedInNewExtension(oldSymbol) || IsOrIsContainedInNewExtension(newSymbol))
+ {
+ // https://github.com/dotnet/roslyn/issues/78959
+ // Currently not supported
+ diagnosticContext.Report(RudeEditKind.Update, cancellationToken, arguments: [FeaturesResources.extension_block]);
+ return;
+ }
+
if (oldSymbol.Kind != newSymbol.Kind)
{
rudeEdit = (oldSymbol.Kind == SymbolKind.Field || newSymbol.Kind == SymbolKind.Field) ? RudeEditKind.FieldKindUpdate : RudeEditKind.Update;
@@ -4529,6 +4560,23 @@ oldSymbol is not INamedTypeSymbol and not ITypeParameterSymbol and not IParamete
{
diagnosticContext.Report(rudeEdit, cancellationToken);
}
+
+ bool IsOrIsContainedInNewExtension(ISymbol symbol)
+ {
+ var current = symbol;
+ do
+ {
+ if (current is INamedTypeSymbol { IsExtension: true })
+ {
+ return true;
+ }
+
+ current = current.ContainingType;
+ }
+ while (current != null);
+
+ return false;
+ }
}
private static bool GeneratesParameterAttribute(RefKind kind)
diff --git a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs
index 676b9fd568208..47a045f1d73ef 100644
--- a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs
+++ b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs
@@ -9,6 +9,13 @@
namespace Microsoft.CodeAnalysis.EditAndContinue;
+///
+/// Represents a declaration of a member with executable code directly inside.
+///
+///
+/// Executable code here is code that a user might set breakpoints in. For example, a normal type with no primary constructor would not have any directly-nested
+/// executable code, but a record with a primary constructor might.
+///
internal abstract class MemberBody : DeclarationBody
{
///
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index 3240ff770f63c..91de537d5bf3a 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -3223,4 +3223,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced).
-
\ No newline at end of file
+
+ extension block
+
+
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
index 9a4e8402f7179..105af68d8d63b 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
@@ -3613,6 +3613,11 @@ Pokud se specifikátor formátu d použije bez dalších specifikátorů vlastn
zapuštěná
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
external
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
index e7d7d0bd2c05c..d575007a71a90 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
@@ -3613,6 +3613,11 @@ Bei Verwendung des Formatbezeichners "d" ohne weitere benutzerdefinierte Formatb
Eingebettet
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
Extern
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
index 5fc2e000e34c4..4f18ee9667fbb 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
@@ -3613,6 +3613,11 @@ Si el especificador de formato "d" se usa sin otros especificadores de formato p
Incrustado
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
externo
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
index 2353fc2298c4e..9d79c5d12966c 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
@@ -3613,6 +3613,11 @@ Si le spécificateur de format "d" est utilisé sans autres spécificateurs de f
Rapport
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
externe
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
index e06d22570cba3..58bb39b99dffe 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
@@ -3613,6 +3613,11 @@ Se l'identificatore di formato "d" viene usato senza altri identificatori di for
Incorporata
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
external
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
index 84e7882b5134e..d7bd998089a59 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
埋め込み
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
外部
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
index 2f09541302e10..406c90dcc7a60 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
포함됨
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
외부
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
index e4e3f1f225dab..4fee08df99525 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
@@ -3613,6 +3613,11 @@ Jeśli specyfikator formatu „d” zostanie użyty bez innych indywidualnych sp
osadzone
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
zewnętrzny
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
index 8467f2031fb38..e6454321315c2 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
@@ -3613,6 +3613,11 @@ Se o especificador de formato "d" for usado sem outros especificadores de format
embutido
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
externo
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
index 9a89486aff856..cf2e1a81c1bd9 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
внедренный
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
внешний
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
index f0f503ab8de65..fd8404764d817 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
gömülü
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
dış
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
index 6487b76873dde..a1b991b4c986e 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
嵌入源
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
外部源
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
index 117f52c28b7cd..f1e1ffd6315d3 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
@@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's
內嵌
Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB
+
+ extension block
+ extension block
+
+
external
外部
diff --git a/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs b/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs
index ddb568447c334..206142cd8b2f0 100644
--- a/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs
+++ b/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs
@@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Differencing;
+using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Test.Utilities;
namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests;
@@ -29,9 +30,39 @@ public void VerifyEdits()
=> VerifyEdits(Array.Empty());
public void VerifyEdits(params string[] expected)
- => AssertEx.Equal(expected, Edits.Select(e => e.GetDebuggerDisplay()), itemSeparator: ",\r\n", itemInspector: s => $"""
- "{s}"
- """);
+ => AssertEx.Equal(expected, Edits.Select(e => e.GetDebuggerDisplay()), itemSeparator: ",\r\n", itemInspector: static s =>
+ {
+ var maxQuoteRun = 0;
+ var currentRun = 0;
+ foreach (var c in s)
+ {
+ if (c == '"')
+ {
+ currentRun++;
+ maxQuoteRun = Math.Max(maxQuoteRun, currentRun);
+ }
+ else
+ {
+ currentRun = 0;
+ }
+ }
+
+ if (maxQuoteRun >= 1 || s.ContainsLineBreak())
+ {
+ var quoteBlock = new string('"', Math.Max(maxQuoteRun + 1, 3));
+ return $"""
+ {quoteBlock}
+ {s}
+ {quoteBlock}
+ """;
+ }
+ else
+ {
+ return $"""
+ "{s}"
+ """;
+ }
+ });
public void VerifyEdits(params EditKind[] expected)
=> AssertEx.Equal(expected, Edits.Select(e => e.Kind));