Skip to content

Commit

Permalink
提供添加文件头部信息的分析器和修复器 #8
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Sep 6, 2024
1 parent a77d17d commit 45320e6
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 5 deletions.
54 changes: 54 additions & 0 deletions AutoClassGenTest/AddFileHeaderCodeFixProviderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Biwen.AutoClassGen.DiagnosticAnalyzers;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Biwen.AutoClassGen.CodeFixProviders;

namespace AutoClassGenTest;

public class MyVerifier : DefaultVerifier
{
public override IVerifier PushContext(string context)
{
return new MyVerifier();
}

public override void Equal<T>(T expected, T actual, string? message = null)
{
//0,1:返回成功
if (expected is 0 && actual is 1) return;

base.Equal(expected, actual, message);
}
}

public class AddFileHeaderCodeFixProviderTest :
CSharpCodeFixVerifier<FileHeaderAnalyzer, AddFileHeaderCodeFixProvider, MyVerifier>
{

[Fact]
public async Task Test()
{
var @code = "namespace Biwen { public interface IRequest {} }";

var fixedCode = """
// Licensed to the {Product} under one or more agreements.
// The {Product} licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Biwen { public interface IRequest {} }
""";

Diagnostic(FileHeaderAnalyzer.DiagnosticId)
.WithSpan(1, 1, 1, 1)
.WithLocation(1, 1);
;

//设置修复器的路径:




await VerifyAnalyzerAsync(@code);
await VerifyCodeFixAsync(@code, fixedCode);

}
}
41 changes: 41 additions & 0 deletions AutoClassGenTest/AutoClassGenTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>

<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0-1.final" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0-1.final" PrivateAssets="all" />

<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Biwen.AutoClassGen.Attributes\Biwen.AutoClassGen.Attributes.csproj" />
<ProjectReference Include="..\Biwen.AutoClassGen.Gen\Biwen.AutoClassGen.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<PackageVersion>1.3.6</PackageVersion>
<PackageProjectUrl>https://github.com/vipwan/Biwen.AutoClassGen</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Biwen.AutoClassGen.DiagnosticAnalyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Immutable;
using System.Composition;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace Biwen.AutoClassGen.CodeFixProviders;

/// <summary>
/// 自动给文件添加头部注释
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddFileHeaderCodeFixProvider))]
[Shared]
public class AddFileHeaderCodeFixProvider : CodeFixProvider
{
private const string Title = "添加文件头部信息";
private const string ConfigFileName = "Biwen.AutoClassGen.Comment";

private const string DefaultComment = """
// Licensed to the {Product} under one or more agreements.
// The {Product} licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
""";

public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return [FileHeaderAnalyzer.DiagnosticId]; }
}

public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics[0];
var diagnosticSpan = diagnostic.Location.SourceSpan;

context.RegisterCodeFix(
CodeAction.Create(
title: Title,
createChangedDocument: c => FixDocumentAsync(context.Document, diagnosticSpan, c),
equivalenceKey: Title),
diagnostic);

return Task.CompletedTask;
}

private static async Task<Document> FixDocumentAsync(Document document, TextSpan span, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

//从项目配置中获取文件头部信息
var projFilePath = document.Project.FilePath ?? "C:\\test.csproj";//单元测试时没有文件路径,因此使用默认路径

var projectDirectory = Path.GetDirectoryName(projFilePath);
var configFilePath = Path.Combine(projectDirectory, ConfigFileName);

var comment = DefaultComment;

string? copyright = "MIT";
string? author = Environment.UserName;
string? title = document.Project.Name;
string? version = document.Project.Version.ToString();
string? product = document.Project.AssemblyName;
string? file = Path.GetFileName(document.FilePath);
#pragma warning disable CA1305 // 指定 IFormatProvider
string? date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
#pragma warning restore CA1305 // 指定 IFormatProvider

if (File.Exists(configFilePath))
{
comment = File.ReadAllText(configFilePath, System.Text.Encoding.UTF8);
//使用正则表达式替换
comment = Regex.Replace(comment, @"\{(?<key>[^}]+)\}", m =>
{
var key = m.Groups["key"].Value;
return key switch
{
"Product" => product,
"Title" => title,
"Version" => version,
"Date" => date,
"Author" => author,
"Copyright" => copyright,
"File" => file,
_ => m.Value,
};
});
}

var headerComment = SyntaxFactory.Comment(comment + Environment.NewLine);
var newRoot = root?.WithLeadingTrivia(headerComment);
if (newRoot == null)
{
return document;
}
var newDocument = document.WithSyntaxRoot(newRoot);

return newDocument;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// <copyright file="SourceGenCodeFixProvider.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Biwen.AutoClassGen
namespace Biwen.AutoClassGen.CodeFixProviders
{
using System.Collections.Immutable;
using System.Composition;
Expand Down
51 changes: 51 additions & 0 deletions Biwen.AutoClassGen.Gen/DiagnosticAnalyzers/FileHeaderAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;

namespace Biwen.AutoClassGen.DiagnosticAnalyzers;

/// <summary>
/// 文件头部分析器
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class FileHeaderAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "GEN050";
private static readonly LocalizableString Title = "文件缺少头部信息";
private static readonly LocalizableString MessageFormat = "文件缺少头部信息";
private static readonly LocalizableString Description = "每个文件应包含头部信息.";
private const string Category = "Documentation";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];

public override void Initialize(AnalysisContext context)
{
if (context is null)
return;

context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxTreeAction(AnalyzeSyntaxTree);
}

private static void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
{
var root = context.Tree.GetRoot(context.CancellationToken);
var firstToken = root.GetFirstToken();

// 检查文件是否以注释开头
var hasHeaderComment = firstToken.LeadingTrivia.Any(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineCommentTrivia));

if (!hasHeaderComment)
{
var diagnostic = Diagnostic.Create(Rule, Location.Create(context.Tree, TextSpan.FromBounds(0, 0)));
context.ReportDiagnostic(diagnostic);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Biwen.AutoClassGen
namespace Biwen.AutoClassGen.DiagnosticAnalyzers
{
using System;
using System.Collections.Immutable;
Expand Down
6 changes: 6 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.Comment
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Licensed to the {Product} under one or more agreements.
// The {Product} licenses this file to you under the {Copyright} license.
// See the LICENSE file in the project root for more information.13456789
// 1234 {Product} {File}
// {Date} {Title} {Author}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>This is a test console App</Description>
<Copyright>MIT0</Copyright>
<Company>万雅虎</Company>
<Authors>万雅虎</Authors>
</PropertyGroup>

<Import Project="../Version.props"/>
Expand All @@ -13,11 +17,10 @@
<PackageReference Include="Biwen.AutoClassGen.Attributes" Version="1.3.2.3" PrivateAssets="contentfiles;analyzers" />
</ItemGroup>-->


<ItemGroup Condition="'$(Configuration)'=='Debug'">
<ProjectReference Include="..\Biwen.AutoClassGen.Attributes\Biwen.AutoClassGen.Attributes.csproj" />
<ProjectReference Include="..\Biwen.AutoClassGen.Gen\Biwen.AutoClassGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>


</Project>
</Project>
11 changes: 11 additions & 0 deletions Biwen.AutoClassGen.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.props = version.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{631F8A4D-FE17-4730-BFAC-50520BA349D7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoClassGenTest", "AutoClassGenTest\AutoClassGenTest.csproj", "{62156C0C-B590-4AC1-927D-207960AD505C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -46,10 +50,17 @@ Global
{EC2EEC4B-FB43-40B2-AFEC-7885E01C3A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC2EEC4B-FB43-40B2-AFEC-7885E01C3A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC2EEC4B-FB43-40B2-AFEC-7885E01C3A7C}.Release|Any CPU.Build.0 = Release|Any CPU
{62156C0C-B590-4AC1-927D-207960AD505C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62156C0C-B590-4AC1-927D-207960AD505C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62156C0C-B590-4AC1-927D-207960AD505C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62156C0C-B590-4AC1-927D-207960AD505C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{62156C0C-B590-4AC1-927D-207960AD505C} = {631F8A4D-FE17-4730-BFAC-50520BA349D7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F116E20A-3544-4AF0-90BA-3657B2B98CE6}
EndGlobalSection
Expand Down

0 comments on commit 45320e6

Please sign in to comment.