提交 412c248b 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #21204 from CyrusNajmabadi/addFileBanner

Add feature to add missing File-Banner to a file.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.AddFileBanner;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AddFileBanner
{
public partial class AddFileBannerTests : AbstractCSharpCodeActionTest
{
protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
=> new CSharpAddFileBannerCodeRefactoringProvider();
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)]
public async Task TestBanner1()
{
await TestInRegularAndScriptAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// This is the banner
class Program2
{
}
</Document>
</Project>
</Workspace>",
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>// This is the banner
using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// This is the banner
class Program2
{
}
</Document>
</Project>
</Workspace>", ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)]
public async Task TestMultiLineBanner1()
{
await TestInRegularAndScriptAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// This is the banner
// It goes over multiple lines
class Program2
{
}
</Document>
</Project>
</Workspace>",
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>// This is the banner
// It goes over multiple lines
using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// This is the banner
// It goes over multiple lines
class Program2
{
}
</Document>
</Project>
</Workspace>", ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)]
public async Task TestMissingWhenAlreadyThere()
{
await TestMissingAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]// I already have a banner
using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// This is the banner
class Program2
{
}
</Document>
</Project>
</Workspace>");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)]
public async Task TestMissingIfOtherFileDoesNotHaveBanner()
{
await TestMissingAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]
using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>
class Program2
{
}
</Document>
</Project>
</Workspace>");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)]
public async Task TestMissingIfOtherFileIsAutoGenerated()
{
await TestMissingAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]
using System;
class Program1
{
static void Main()
{
}
}
</Document>
<Document>// &lt;autogenerated /&gt;
class Program2
{
}
</Document>
</Project>
</Workspace>");
}
}
}
......@@ -34,6 +34,7 @@ public static class Features
public const string CodeActionsUpgradeProject = "CodeActions.UpgradeProject";
public const string CodeActionsAddAccessibilityModifiers = "CodeActions.AddAccessibilityModifiers";
public const string CodeActionsAddBraces = "CodeActions.AddBraces";
public const string CodeActionsAddFileBanner = "CodeActions.AddFileBanner";
public const string CodeActionsAddImport = "CodeActions.AddImport";
public const string CodeActionsAddMissingReference = "CodeActions.AddMissingReference";
public const string CodeActionsAddParameter = "CodeActions.AddParameter";
......
' 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.Threading.Tasks
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.VisualBasic.AddFileBanner
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Imports Roslyn.Test.Utilities
Imports Xunit
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.AddFileBanner
Partial Public Class AddFileBannerTests
Inherits AbstractVisualBasicCodeActionTest
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace, parameters As TestParameters) As CodeRefactoringProvider
Return New VisualBasicAddFileBannerCodeRefactoringProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)>
Public Async Function TestBanner1() As Task
Await TestInRegularAndScriptAsync(
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' This is the banner
class Program2
end class
</Document>
</Project>
</Workspace>",
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>' This is the banner
Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' This is the banner
class Program2
end class
</Document>
</Project>
</Workspace>", ignoreTrivia:=False)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)>
Public Async Function TestMultiLineBanner1() As Task
Await TestInRegularAndScriptAsync(
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' This is the banner
' It goes over multiple lines
class Program2
end class
</Document>
</Project>
</Workspace>",
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>' This is the banner
' It goes over multiple lines
Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' This is the banner
' It goes over multiple lines
class Program2
end class
</Document>
</Project>
</Workspace>", ignoreTrivia:=False)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)>
Public Async Function TestMissingWhenAlreadyThere() As Task
Await TestMissingAsync(
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]' I already have a banner
Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' This is the banner
class Program2
end class
</Document>
</Project>
</Workspace>")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)>
Public Async Function TestMissingIfOtherFileDoesNotHaveBanner() As Task
Await TestMissingAsync(
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]
Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>
class Program2
end class
</Document>
</Project>
</Workspace>")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddFileBanner)>
Public Async Function TestMissingIfOtherFileIsAutoGenerated() As Task
Await TestMissingAsync(
"
<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>[||]
Imports System
class Program1
sub Main()
end sub
end class
</Document>
<Document>' &lt;autogenerated /&gt;
class Program2
end class
</Document>
</Project>
</Workspace>")
End Function
End Class
End Namespace
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.AddFileBanner;
using Microsoft.CodeAnalysis.CodeRefactorings;
namespace Microsoft.CodeAnalysis.CSharp.AddFileBanner
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp), Shared]
internal class CSharpAddFileBannerCodeRefactoringProvider : AbstractAddFileBannerCodeRefactoringProvider
{
protected override bool IsCommentStartCharacter(char ch)
=> ch == '/';
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.AddFileBanner
{
internal abstract class AbstractAddFileBannerCodeRefactoringProvider : CodeRefactoringProvider
{
protected abstract bool IsCommentStartCharacter(char ch);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var cancellationToken = context.CancellationToken;
var document = context.Document;
if (!context.Span.IsEmpty)
{
return;
}
var position = context.Span.Start;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var firstToken = root.GetFirstToken();
if (!firstToken.FullSpan.IntersectsWith(position))
{
return;
}
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var banner = syntaxFacts.GetFileBanner(root);
if (banner.Length > 0)
{
// Already has a banner.
return;
}
// Process the other documents in this document's project. Look at the
// ones that we can get a root from (without having to parse). Then
// look at the ones we'd need to parse.
var siblingDocumentsAndRoots =
document.Project.Documents
.Where(d => d != document)
.Select(d =>
{
d.TryGetSyntaxRoot(out var siblingRoot);
return (document: d, root: siblingRoot);
})
.OrderBy((t1, t2) => (t1.root != null) == (t2.root != null) ? 0 : t1.root != null ? -1 : 1);
foreach (var (siblingDocument, siblingRoot) in siblingDocumentsAndRoots)
{
cancellationToken.ThrowIfCancellationRequested();
var siblingBanner = await TryGetBannerAsync(siblingDocument, siblingRoot, cancellationToken).ConfigureAwait(false);
if (siblingBanner.Length > 0 && !siblingDocument.IsGeneratedCode(cancellationToken))
{
context.RegisterRefactoring(
new MyCodeAction(c => AddBannerAsync(document, root, siblingBanner, c)));
return;
}
}
}
private Task<Document> AddBannerAsync(
Document document, SyntaxNode root, ImmutableArray<SyntaxTrivia> banner, CancellationToken c)
{
var newRoot = root.WithPrependedLeadingTrivia(new SyntaxTriviaList(banner));
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}
private async Task<ImmutableArray<SyntaxTrivia>> TryGetBannerAsync(
Document document, SyntaxNode root, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
// If we have a tree already for this document, then just check to see
// if it has a banner.
if (root != null)
{
return syntaxFacts.GetFileBanner(root);
}
// Didn't have a tree. Don't want to parse the file if we can avoid it.
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
if (text.Length == 0 || !IsCommentStartCharacter(text[0]))
{
// Didn't start with a comment character, don't bother looking at
// this file.
return ImmutableArray<SyntaxTrivia>.Empty;
}
var token = syntaxFacts.ParseToken(text.ToString());
return syntaxFacts.GetFileBanner(token);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Add_file_banner, createChangedDocument)
{
}
}
}
}
......@@ -161,6 +161,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add file banner.
/// </summary>
internal static string Add_file_banner {
get {
return ResourceManager.GetString("Add_file_banner", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add missing cases.
/// </summary>
......
......@@ -1295,4 +1295,7 @@ This version used in: {2}</value>
<data name="Generate_constructor_in_0_without_fields" xml:space="preserve">
<value>Generate constructor in '{0}' (without fields)</value>
</data>
<data name="Add_file_banner" xml:space="preserve">
<value>Add file banner</value>
</data>
</root>
\ No newline at end of file
' 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.Composition
Imports Microsoft.CodeAnalysis.AddFileBanner
Imports Microsoft.CodeAnalysis.CodeRefactorings
Namespace Microsoft.CodeAnalysis.VisualBasic.AddFileBanner
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicAddFileBannerCodeRefactoringProvider
Inherits AbstractAddFileBannerCodeRefactoringProvider
Protected Overrides Function IsCommentStartCharacter(ch As Char) As Boolean
Return ch = "'"c
End Function
End Class
End Namespace
......@@ -45,6 +45,9 @@ public bool SupportsIndexingInitializer(ParseOptions options)
public bool SupportsThrowExpression(ParseOptions options)
=> ((CSharpParseOptions)options).LanguageVersion >= LanguageVersion.CSharp7;
public SyntaxToken ParseToken(string text)
=> SyntaxFactory.ParseToken(text);
public bool IsAwaitKeyword(SyntaxToken token)
{
return token.IsKind(SyntaxKind.AwaitKeyword);
......
......@@ -373,8 +373,14 @@ public ImmutableArray<SyntaxTrivia> GetLeadingBannerAndPreprocessorDirectives<TS
public ImmutableArray<SyntaxTrivia> GetFileBanner(SyntaxNode root)
{
Debug.Assert(root.FullSpan.Start == 0);
return GetFileBanner(root.GetFirstToken(includeZeroWidth: true));
}
public ImmutableArray<SyntaxTrivia> GetFileBanner(SyntaxToken firstToken)
{
Debug.Assert(firstToken.FullSpan.Start == 0);
var leadingTrivia = root.GetLeadingTrivia();
var leadingTrivia = firstToken.LeadingTrivia;
var index = 0;
_fileBannerMatcher.TryMatch(leadingTrivia.ToList(), ref index);
......
......@@ -19,6 +19,8 @@ internal interface ISyntaxFactsService : ILanguageService
bool SupportsIndexingInitializer(ParseOptions options);
bool SupportsThrowExpression(ParseOptions options);
SyntaxToken ParseToken(string text);
bool IsAwaitKeyword(SyntaxToken token);
bool IsIdentifier(SyntaxToken token);
bool IsGlobalNamespaceKeyword(SyntaxToken token);
......@@ -303,6 +305,7 @@ internal interface ISyntaxFactsService : ILanguageService
TSyntaxNode GetNodeWithoutLeadingBlankLines<TSyntaxNode>(TSyntaxNode node) where TSyntaxNode : SyntaxNode;
ImmutableArray<SyntaxTrivia> GetFileBanner(SyntaxNode root);
ImmutableArray<SyntaxTrivia> GetFileBanner(SyntaxToken firstToken);
bool ContainsInterleavedDirective(SyntaxNode node, CancellationToken cancellationToken);
bool ContainsInterleavedDirective(ImmutableArray<SyntaxNode> nodes, CancellationToken cancellationToken);
......
......@@ -66,6 +66,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return False
End Function
Public Function ParseToken(text As String) As SyntaxToken Implements ISyntaxFactsService.ParseToken
Return SyntaxFactory.ParseToken(text, startStatement:=True)
End Function
Public Function IsAwaitKeyword(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsAwaitKeyword
Return token.Kind = SyntaxKind.AwaitKeyword
End Function
......@@ -1586,6 +1590,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return GetFileBanner(root)
End Function
Private Function ISyntaxFactsService_GetFileBanner(firstToken As SyntaxToken) As ImmutableArray(Of SyntaxTrivia) Implements ISyntaxFactsService.GetFileBanner
Return GetFileBanner(firstToken)
End Function
Protected Overrides Function ContainsInterleavedDirective(span As TextSpan, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
Return token.ContainsInterleavedDirective(span, cancellationToken)
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册