Skip to content

Commit c1794aa

Browse files
Ensure generated types come after top level statements (#79378)
2 parents f935ca8 + 4060745 commit c1794aa

File tree

6 files changed

+67
-16
lines changed

6 files changed

+67
-16
lines changed

src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5901,4 +5901,26 @@ internal class Test
59015901
public int B { get; set; }
59025902
}
59035903
""", index: 1);
5904+
5905+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/49649")]
5906+
public Task TestInTopLevelProgram()
5907+
=> TestInRegularAndScriptAsync(
5908+
"""
5909+
var student = new [|Student|]("Youssef");
5910+
Console.WriteLine(student.Name);
5911+
""",
5912+
"""
5913+
var student = new Student("Youssef");
5914+
Console.WriteLine(student.Name);
5915+
5916+
internal class Student
5917+
{
5918+
private string v;
5919+
5920+
public Student(string v)
5921+
{
5922+
this.v = v;
5923+
}
5924+
}
5925+
""", index: 1);
59045926
}

src/EditorFeatures/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
#nullable disable
5+
#if CODE_STYLE
6+
extern alias CODESTYLE_UTILITIES;
7+
#endif
68

79
using System.Diagnostics.CodeAnalysis;
810
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis.CodeActions;
912
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
1013
using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics;
14+
using Microsoft.CodeAnalysis.Remote.Testing;
1115
using Xunit.Abstractions;
1216

1317
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
1418

15-
public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest : AbstractDiagnosticProviderBasedUserDiagnosticTest
16-
{
17-
protected AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest(ITestOutputHelper logger)
18-
: base(logger)
19-
{
20-
}
19+
#if CODE_STYLE
20+
using OptionsCollectionAlias = CODESTYLE_UTILITIES::Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.OptionsCollection;
21+
#else
22+
using OptionsCollectionAlias = OptionsCollection;
23+
#endif
2124

25+
public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest(ITestOutputHelper? logger)
26+
: AbstractDiagnosticProviderBasedUserDiagnosticTest(logger)
27+
{
2228
protected override ParseOptions GetScriptOptions() => Options.Script;
2329

2430
protected internal override string GetLanguage() => LanguageNames.CSharp;
@@ -158,8 +164,24 @@ public interface IAsyncEnumerator<out T> : IAsyncDisposable
158164
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
159165
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup,
160166
int index = 0,
161-
TestParameters parameters = null)
167+
TestParameters? parameters = null)
162168
{
163169
return base.TestInRegularAndScript1Async(initialMarkup, expectedMarkup, index, parameters);
164170
}
171+
172+
internal new Task TestInRegularAndScriptAsync(
173+
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
174+
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup,
175+
int index = 0,
176+
CodeActionPriority? priority = null,
177+
CompilationOptions? compilationOptions = null,
178+
OptionsCollectionAlias? options = null,
179+
object? fixProviderData = null,
180+
ParseOptions? parseOptions = null,
181+
string? title = null,
182+
TestHost testHost = TestHost.OutOfProcess)
183+
{
184+
return base.TestInRegularAndScriptAsync(
185+
initialMarkup, expectedMarkup, index, priority, compilationOptions, options, fixProviderData, parseOptions, title, testHost);
186+
}
165187
}

src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.CodeAnalysis;
1414
using Microsoft.CodeAnalysis.CodeActions;
1515
using Microsoft.CodeAnalysis.CodeGeneration;
16+
using Microsoft.CodeAnalysis.Collections;
1617
using Microsoft.CodeAnalysis.Internal.Log;
1718
using Microsoft.CodeAnalysis.LanguageService;
1819
using Microsoft.CodeAnalysis.PooledObjects;
@@ -100,7 +101,7 @@ private ImmutableArray<CodeAction> GetActions(
100101
State state,
101102
CancellationToken cancellationToken)
102103
{
103-
using var _ = ArrayBuilder<CodeAction>.GetInstance(out var result);
104+
using var result = TemporaryArray<CodeAction>.Empty;
104105

105106
var generateNewTypeInDialog = false;
106107
if (state.NamespaceToGenerateInOpt != null)

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,15 @@ public static SyntaxList<TDeclaration> Insert<TDeclaration>(
172172
CSharpCodeGenerationContextInfo info,
173173
IList<bool>? availableIndices,
174174
Func<SyntaxList<TDeclaration>, TDeclaration?>? after = null,
175-
Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null)
175+
Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null,
176+
Func<SyntaxList<TDeclaration>, int, bool>? canPlaceAtIndex = null)
176177
where TDeclaration : SyntaxNode
177178
{
178179
var index = GetInsertionIndex(
179180
declarationList, declaration, info, availableIndices,
180181
CSharpDeclarationComparer.WithoutNamesInstance,
181182
CSharpDeclarationComparer.WithNamesInstance,
182-
after, before);
183+
after, before, canPlaceAtIndex);
183184

184185
availableIndices?.Insert(index, true);
185186

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ public static CompilationUnitSyntax AddNamedTypeTo(
5959
CancellationToken cancellationToken)
6060
{
6161
var declaration = GenerateNamedTypeDeclaration(service, namedType, CodeGenerationDestination.CompilationUnit, info, cancellationToken);
62-
var members = Insert(destination.Members, declaration, info, availableIndices);
62+
var members = Insert(
63+
destination.Members, declaration, info, availableIndices,
64+
// We're adding a named type to a compilation unit. If there are any global statements, we must place it after them.
65+
after: static members => members.LastOrDefault(m => m is GlobalStatementSyntax),
66+
canPlaceAtIndex: static (members, index) => index >= members.Count || members[index] is not GlobalStatementSyntax);
6367
return destination.WithMembers(members);
6468
}
6569

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,14 @@ public static int GetInsertionIndex<TDeclaration>(
193193
IComparer<TDeclaration> comparerWithoutNameCheck,
194194
IComparer<TDeclaration> comparerWithNameCheck,
195195
Func<SyntaxList<TDeclaration>, TDeclaration?>? after = null,
196-
Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null)
196+
Func<SyntaxList<TDeclaration>, TDeclaration?>? before = null,
197+
Func<SyntaxList<TDeclaration>, int, bool>? canPlaceAtIndex = null)
197198
where TDeclaration : SyntaxNode
198199
{
199200
Contract.ThrowIfTrue(availableIndices != null && availableIndices.Count != declarationList.Count + 1);
200201

202+
canPlaceAtIndex ??= static (_, _) => true;
203+
201204
// Try to strictly obey the after option by inserting immediately after the member containing the location
202205
if (info.Context.AfterThisLocation?.SourceTree is { } afterSourceTree &&
203206
afterSourceTree.FilePath == declarationList.FirstOrDefault()?.SyntaxTree.FilePath)
@@ -207,10 +210,8 @@ public static int GetInsertionIndex<TDeclaration>(
207210
{
208211
var index = declarationList.IndexOf(afterMember);
209212
index = GetPreferredIndex(index + 1, availableIndices, forward: true);
210-
if (index != -1)
211-
{
213+
if (index != -1 && canPlaceAtIndex(declarationList, index))
212214
return index;
213-
}
214215
}
215216
}
216217

0 commit comments

Comments
 (0)