diff --git a/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs index ade0311f695c..d42de6fa6e5d 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/GenerateType/GenerateTypeTests.cs @@ -5901,4 +5901,26 @@ internal class Test public int B { get; set; } } """, index: 1); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/49649")] + public Task TestInTopLevelProgram() + => TestInRegularAndScriptAsync( + """ + var student = new [|Student|]("Youssef"); + Console.WriteLine(student.Name); + """, + """ + var student = new Student("Youssef"); + Console.WriteLine(student.Name); + + internal class Student + { + private string v; + + public Student(string v) + { + this.v = v; + } + } + """, index: 1); } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs b/src/EditorFeatures/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs index 5b4225b20c93..9ea1e21c7bfc 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs @@ -2,23 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable +#if CODE_STYLE +extern alias CODESTYLE_UTILITIES; +#endif using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Remote.Testing; using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; -public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest : AbstractDiagnosticProviderBasedUserDiagnosticTest -{ - protected AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest(ITestOutputHelper logger) - : base(logger) - { - } +#if CODE_STYLE +using OptionsCollectionAlias = CODESTYLE_UTILITIES::Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.OptionsCollection; +#else +using OptionsCollectionAlias = OptionsCollection; +#endif +public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest(ITestOutputHelper? logger) + : AbstractDiagnosticProviderBasedUserDiagnosticTest(logger) +{ protected override ParseOptions GetScriptOptions() => Options.Script; protected internal override string GetLanguage() => LanguageNames.CSharp; @@ -158,8 +164,24 @@ public interface IAsyncEnumerator : IAsyncDisposable [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup, int index = 0, - TestParameters parameters = null) + TestParameters? parameters = null) { return base.TestInRegularAndScript1Async(initialMarkup, expectedMarkup, index, parameters); } + + internal new Task TestInRegularAndScriptAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup, + int index = 0, + CodeActionPriority? priority = null, + CompilationOptions? compilationOptions = null, + OptionsCollectionAlias? options = null, + object? fixProviderData = null, + ParseOptions? parseOptions = null, + string? title = null, + TestHost testHost = TestHost.OutOfProcess) + { + return base.TestInRegularAndScriptAsync( + initialMarkup, expectedMarkup, index, priority, compilationOptions, options, fixProviderData, parseOptions, title, testHost); + } } diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs index 0ebac83ad0f2..2309c81eb50e 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; @@ -100,7 +101,7 @@ private ImmutableArray GetActions( State state, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; var generateNewTypeInDialog = false; if (state.NamespaceToGenerateInOpt != null) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs index 75d972a22f05..12421618da5c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationHelpers.cs @@ -172,14 +172,15 @@ public static SyntaxList Insert( CSharpCodeGenerationContextInfo info, IList? availableIndices, Func, TDeclaration?>? after = null, - Func, TDeclaration?>? before = null) + Func, TDeclaration?>? before = null, + Func, int, bool>? canPlaceAtIndex = null) where TDeclaration : SyntaxNode { var index = GetInsertionIndex( declarationList, declaration, info, availableIndices, CSharpDeclarationComparer.WithoutNamesInstance, CSharpDeclarationComparer.WithNamesInstance, - after, before); + after, before, canPlaceAtIndex); availableIndices?.Insert(index, true); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs index 31c4d36df888..a97d4b64fd1a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs @@ -59,7 +59,11 @@ public static CompilationUnitSyntax AddNamedTypeTo( CancellationToken cancellationToken) { var declaration = GenerateNamedTypeDeclaration(service, namedType, CodeGenerationDestination.CompilationUnit, info, cancellationToken); - var members = Insert(destination.Members, declaration, info, availableIndices); + var members = Insert( + destination.Members, declaration, info, availableIndices, + // We're adding a named type to a compilation unit. If there are any global statements, we must place it after them. + after: static members => members.LastOrDefault(m => m is GlobalStatementSyntax), + canPlaceAtIndex: static (members, index) => index >= members.Count || members[index] is not GlobalStatementSyntax); return destination.WithMembers(members); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs index 476a0c100117..1e653b7a55ca 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs @@ -193,11 +193,14 @@ public static int GetInsertionIndex( IComparer comparerWithoutNameCheck, IComparer comparerWithNameCheck, Func, TDeclaration?>? after = null, - Func, TDeclaration?>? before = null) + Func, TDeclaration?>? before = null, + Func, int, bool>? canPlaceAtIndex = null) where TDeclaration : SyntaxNode { Contract.ThrowIfTrue(availableIndices != null && availableIndices.Count != declarationList.Count + 1); + canPlaceAtIndex ??= static (_, _) => true; + // Try to strictly obey the after option by inserting immediately after the member containing the location if (info.Context.AfterThisLocation?.SourceTree is { } afterSourceTree && afterSourceTree.FilePath == declarationList.FirstOrDefault()?.SyntaxTree.FilePath) @@ -207,10 +210,8 @@ public static int GetInsertionIndex( { var index = declarationList.IndexOf(afterMember); index = GetPreferredIndex(index + 1, availableIndices, forward: true); - if (index != -1) - { + if (index != -1 && canPlaceAtIndex(declarationList, index)) return index; - } } }