diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs index 91e525517f47909fd38535bc04bbe73e045943fb..6c881c19e3e44f4c3568597c90d2b02acb3b602e 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs @@ -4782,6 +4782,83 @@ public B() }", index: 2); } + + [WorkItem(17361, "https://github.com/dotnet/roslyn/issues/17361")] + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateType)] + public async Task TestPreserveFileBanner1() + { + await TestAddDocumentInRegularAndScriptAsync( +@"// I am a banner + +class Program +{ + void Main ( ) + { + [|Foo|] f ; + } +} ", +@"// I am a banner + +internal class Foo +{ +}", +expectedContainers: ImmutableArray.Empty, +expectedDocumentName: "Foo.cs", +ignoreTrivia: false); + } + + [WorkItem(17361, "https://github.com/dotnet/roslyn/issues/17361")] + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateType)] + public async Task TestPreserveFileBanner2() + { + await TestAddDocumentInRegularAndScriptAsync( +@"/// I am a doc comment +class Program +{ + void Main ( ) + { + [|Foo|] f ; + } +} ", +@"internal class Foo +{ +}", +expectedContainers: ImmutableArray.Empty, +expectedDocumentName: "Foo.cs", +ignoreTrivia: false); + } + + [WorkItem(17361, "https://github.com/dotnet/roslyn/issues/17361")] + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateType)] + public async Task TestPreserveFileBanner3() + { + await TestAddDocumentInRegularAndScriptAsync( + @"// I am a banner +using System; + +class Program +{ + void Main (StackOverflowException e) + { + var f = new [|Foo|](e); + } +}", + @"// I am a banner +using System; + +internal class Foo +{ + private StackOverflowException e; + + public Foo(StackOverflowException e) + { + this.e = e; + } +}", + expectedContainers: ImmutableArray.Empty, + expectedDocumentName: "Foo.cs", + ignoreTrivia: false); + } } public partial class GenerateTypeWithUnboundAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateType/GenerateTypeTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateType/GenerateTypeTests.vb index bcdc0f4720c4665bc44bd40f0af624f6caea797c..5fe9ee6c9998712fb5ddfd58aba6c83d8dbfd61c 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateType/GenerateTypeTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/GenerateType/GenerateTypeTests.vb @@ -465,6 +465,75 @@ expectedContainers:=ImmutableArray.Create("Foo"), expectedDocumentName:="Bar.vb") End Function + + + Public Async Function TestPreserveBanner1() As Task + Await TestAddDocumentInRegularAndScriptAsync( +"' I am a banner! + +Class Program + Sub Main() + Call New [|Bar|]() + End Sub +End Class", +"' I am a banner! + +Friend Class Bar + Public Sub New() + End Sub +End Class +", +expectedContainers:=ImmutableArray(Of String).Empty, +expectedDocumentName:="Bar.vb", ignoreTrivia:=False) + End Function + + + + Public Async Function TestPreserveBanner2() As Task + Await TestAddDocumentInRegularAndScriptAsync( +"''' I am a doc comment! + +Class Program + Sub Main() + Call New [|Bar|]() + End Sub +End Class", +"Friend Class Bar + Public Sub New() + End Sub +End Class +", +expectedContainers:=ImmutableArray(Of String).Empty, +expectedDocumentName:="Bar.vb", ignoreTrivia:=False) + End Function + + + + Public Async Function TestPreserveBanner3() As Task + Await TestAddDocumentInRegularAndScriptAsync( +"' I am a banner! +Imports System + +Class Program + Sub Main(e As StackOverflowException) + Call New [|Bar|](e) + End Sub +End Class", +"' I am a banner! +Imports System + +Friend Class Bar + Private e As StackOverflowException + + Public Sub New(e As StackOverflowException) + Me.e = e + End Sub +End Class +", +expectedContainers:=ImmutableArray(Of String).Empty, +expectedDocumentName:="Bar.vb", ignoreTrivia:=False) + End Function + Public Async Function TestGenerateIntoGlobalNamespaceNewFile() As Task Await TestAddDocumentInRegularAndScriptAsync( diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs index a313eea960866b2672b0c5edd3188aebb5997865..f624eefe995fde3688e24cc0decb6cc6f0c552ff 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.ProjectManagement; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -272,10 +273,11 @@ private void AddFoldersToNamespaceContainers(List container, IList container, IList if triggered from Dialog // 2: containers -> if triggered not from a Dialog but from QualifiedName // 3: triggering document folder structure -> if triggered not from a Dialog and a SimpleName - var adjustedContainer = isDialog ? folders : - _state.SimpleName != _state.NameOrMemberAccessExpression ? containers.ToList() : _document.Document.Folders.ToList(); + var adjustedContainer = isDialog + ? folders + : _state.SimpleName != _state.NameOrMemberAccessExpression + ? containers.ToList() + : _document.Document.Folders.ToList(); // Now, take the code that would be generated and actually create an edit that would // produce a document with that code in it. + var newRoot = await codeGenResult.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); + + if (newDocument.Project.Language == _document.Document.Project.Language) + { + var syntaxFacts = _document.Document.GetLanguageService(); + var fileBanner = syntaxFacts.GetFileBanner(_document.Root); + + if (fileBanner.Any(syntaxFacts.IsRegularComment)) + { + newRoot = newRoot.WithPrependedLeadingTrivia(fileBanner); + } + } return await CreateAddDocumentAndUpdateUsingsOrImportsOperationsAsync( projectToBeUpdated, triggeringProject, documentName, - await codeGenResult.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false), + newRoot, _document.Document, includeUsingsOrImports, adjustedContainer, @@ -437,7 +454,7 @@ private async Task> GetGenerateIntoContainingNa return new CodeActionOperation[] { new ApplyChangesOperation(updatedSolution) }; } - private Tuple GetNamespaceContainersAndAddUsingsOrImport( + private (string[] containers, string usingOrImport) GetNamespaceContainersAndAddUsingsOrImport( bool isDialog, IList folders, bool areFoldersValidIdentifiers, @@ -467,18 +484,12 @@ private async Task> GetGenerateIntoContainingNa else { // Generated from the Dialog - List containerList = new List(); - - string rootNamespaceOfTheProjectGeneratedInto; + var containerList = new List(); - if (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange) - { - rootNamespaceOfTheProjectGeneratedInto = _service.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim(); - } - else - { - rootNamespaceOfTheProjectGeneratedInto = _targetLanguageService.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim(); - } + var rootNamespaceOfTheProjectGeneratedInto = + _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange + ? _service.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim() + : _targetLanguageService.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim(); var projectManagementService = _document.Project.Solution.Workspace.Services.GetService(); var defaultNamespace = _generateTypeOptionsResult.DefaultNamespace; @@ -526,7 +537,7 @@ private async Task> GetGenerateIntoContainingNa Contract.Assert(includeUsingsOrImports != null); } - return Tuple.Create(containers, includeUsingsOrImports); + return (containers, includeUsingsOrImports); } private async Task> GetGenerateIntoTypeOperationsAsync(INamedTypeSymbol namedType) diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs index 3aebe9c461bf7cfb0abf0f507c871d160b63f403..abffa4590d59ece806d390e8ad3b2acae8e01ad8 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; @@ -657,6 +658,17 @@ public static bool IsAnyLambdaOrAnonymousMethod(this SyntaxNode node) return node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)); } + public static ImmutableArray GetFileBanner(this SyntaxNode root) + { + Debug.Assert(root.FullSpan.Start == 0); + + var leadingTrivia = root.GetLeadingTrivia(); + var index = 0; + s_fileBannerMatcher.TryMatch(leadingTrivia.ToList(), ref index); + + return ImmutableArray.CreateRange(leadingTrivia.Take(index)); + } + public static bool IsAnyAssignExpression(this SyntaxNode node) { return SyntaxFacts.IsAssignmentExpression(node.Kind()); diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index d49e9f204a31a0b9861e8485aeedd705698404d7..c193cd0063fce3c8bab506d9c30593b044dfb3c2 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -1979,5 +1979,8 @@ public bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int pos public ImmutableArray GetSelectedMembers(SyntaxNode root, TextSpan textSpan) => ImmutableArray.CastUp(root.GetMembersInSpan(textSpan)); + + public ImmutableArray GetFileBanner(SyntaxNode root) + => root.GetFileBanner(); } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index 4b663c0a12b3893de8766dceeaa4cd75c66031cb..34d61ce365dce872bc8f973e8c5cd25c86364827 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -279,6 +279,8 @@ internal interface ISyntaxFactsService : ILanguageService out SyntaxNode newRoot, out SyntaxNode newContextNode); SyntaxNode GetNextExecutableStatement(SyntaxNode statement); + + ImmutableArray GetFileBanner(SyntaxNode root); } [Flags] diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb index 8e5caa82d91e4648616f5a49fd80af90cae9c435..50b068e3457c0a8c80ed10e701188e83e0ccfbf3 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb @@ -1,5 +1,6 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Collections.Immutable Imports System.Runtime.CompilerServices Imports System.Threading Imports Microsoft.CodeAnalysis @@ -538,6 +539,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return DirectCast(node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)), TSyntaxNode) End Function + + Public Function GetFileBanner(root As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) + Debug.Assert(root.FullSpan.Start = 0) + + Dim leadingTrivia = root.GetLeadingTrivia() + Dim index = 0 + s_fileBannerMatcher.TryMatch(leadingTrivia.ToList(), index) + + Return ImmutableArray.CreateRange(leadingTrivia.Take(index)) + End Function + ''' ''' Returns true if this is a block that can contain multiple executable statements. i.e. ''' this node is the VB equivalent of a BlockSyntax in C#. diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 628f3c29910fe2c29df8e62a2ee62b9870f7b034..6f1a06a575501e36d7987350f478db91b370c98f 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1733,5 +1733,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Function GetSelectedMembers(root As SyntaxNode, textSpan As TextSpan) As ImmutableArray(Of SyntaxNode) Implements ISyntaxFactsService.GetSelectedMembers Return ImmutableArray(Of SyntaxNode).CastUp(root.GetMembersInSpan(textSpan)) End Function + + Public Function GetFileBanner(root As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) Implements ISyntaxFactsService.GetFileBanner + Return root.GetFileBanner() + End Function End Class End Namespace \ No newline at end of file