Skip to content

Commit 3d5292e

Browse files
authored
Merge pull request #1 from dotnet-campus/t/walterlv/first
添加第一批源生成器辅助方法(那些有争议的暂时还没放进来,慢慢再说吧,先用起来)
2 parents 08ce705 + a2da9f0 commit 3d5292e

25 files changed

+2887
-0
lines changed

Directory.Build.props

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<Project>
2+
3+
<!-- 这里是 DotNetCampus.CodeAnalysisUtils 库的打包版本号 -->
4+
<Import Project="build\Version.props" />
5+
6+
<!-- 框架和语言信息 -->
7+
<PropertyGroup>
8+
<!-- 语言 -->
9+
<LangVersion>latest</LangVersion>
10+
<Nullable>enable</Nullable>
11+
<ImplicitUsings>true</ImplicitUsings>
12+
<!-- 构建 -->
13+
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts\</ArtifactsPath>
14+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
15+
<!--
16+
Adding NoWarn to remove build warnings
17+
NU1507: Warning when there are multiple package sources when using CPM with no source mapping
18+
NU5128: 在 nuspec 和 lib/ref 文件夹的依赖项组中声明的某些目标框架在其他位置没有完全匹配项。请参阅以下操作列表:
19+
-为以下组件添加库或引用程序集 netstandard2.0 目标框架
20+
-->
21+
<NoWarn>$(NoWarn);NU1507</NoWarn>
22+
<!--
23+
CA1416: 平台兼容性警告
24+
-->
25+
<WarningsAsErrors>$(WarningAsErrors);CA1416</WarningsAsErrors>
26+
</PropertyGroup>
27+
28+
<!-- 自定义属性 -->
29+
<PropertyGroup>
30+
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
31+
<ThisYear>$([System.DateTime]::Now.ToString(`yyyy`))</ThisYear>
32+
</PropertyGroup>
33+
34+
<!--库信息 -->
35+
<PropertyGroup>
36+
<Description>为 DotNetCampus 组织下项目内附带的源分析器/源生成器提供公共方法。</Description>
37+
<Authors>dotnet-campus</Authors>
38+
<Company>dotnet campus(.NET 职业技术学院)</Company>
39+
<Copyright>Copyright &#169; 2024-$(ThisYear) dotnet campus, All Rights Reserved.</Copyright>
40+
<RepositoryType>git</RepositoryType>
41+
<RepositoryUrl>https://github.com/dotnet-campus/DotNetCampus.CodeAnalysisUtils</RepositoryUrl>
42+
<PackageProjectUrl>https://github.com/dotnet-campus/DotNetCampus.CodeAnalysisUtils</PackageProjectUrl>
43+
</PropertyGroup>
44+
45+
</Project>

Directory.Packages.props

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageVersion Include="DotNetCampus.LatestCSharpFeatures" Version="13.0.0" />
4+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
5+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
6+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" />
7+
</ItemGroup>
8+
</Project>

DotNetCampus.CodeAnalysisUtils.sln

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
Microsoft Visual Studio Solution File, Format Version 12.00
2+
# Visual Studio Version 17
3+
VisualStudioVersion = 17.10.34916.146
4+
MinimumVisualStudioVersion = 10.0.40219.1
5+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCampus.CodeAnalysisUtils", "src\DotNetCampus.CodeAnalysisUtils\DotNetCampus.CodeAnalysisUtils.csproj", "{96D6B633-B848-484B-B6C5-50CDB69338E6}"
6+
EndProject
7+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6E7697B2-AB98-4292-859F-2DF130506A83}"
8+
ProjectSection(SolutionItems) = preProject
9+
.gitattributes = .gitattributes
10+
.gitignore = .gitignore
11+
build\Version.props = build\Version.props
12+
Directory.Build.props = Directory.Build.props
13+
README.md = README.md
14+
Directory.Packages.props = Directory.Packages.props
15+
EndProjectSection
16+
EndProject
17+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A6089B7E-9528-4B78-9B8E-C9C173D9D3B4}"
18+
EndProject
19+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetCampus.CodeAnalysis.Tests", "tests\DotNetCampus.CodeAnalysis.Tests\DotNetCampus.CodeAnalysis.Tests.csproj", "{287E7364-300A-47EE-9EBD-51B99CB7C7E7}"
20+
EndProject
21+
Global
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {74F16CF5-45E1-4E2C-93E6-B48A39A66B46}
24+
EndGlobalSection
25+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
26+
Debug|Any CPU = Debug|Any CPU
27+
Release|Any CPU = Release|Any CPU
28+
EndGlobalSection
29+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
30+
{96D6B633-B848-484B-B6C5-50CDB69338E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{96D6B633-B848-484B-B6C5-50CDB69338E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{96D6B633-B848-484B-B6C5-50CDB69338E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{96D6B633-B848-484B-B6C5-50CDB69338E6}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{287E7364-300A-47EE-9EBD-51B99CB7C7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{287E7364-300A-47EE-9EBD-51B99CB7C7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{287E7364-300A-47EE-9EBD-51B99CB7C7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{287E7364-300A-47EE-9EBD-51B99CB7C7E7}.Release|Any CPU.Build.0 = Release|Any CPU
38+
EndGlobalSection
39+
GlobalSection(NestedProjects) = preSolution
40+
{287E7364-300A-47EE-9EBD-51B99CB7C7E7} = {A6089B7E-9528-4B78-9B8E-C9C173D9D3B4}
41+
EndGlobalSection
42+
GlobalSection(SolutionProperties) = preSolution
43+
HideSolutionNode = FALSE
44+
EndGlobalSection
45+
EndGlobal

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# DotNetCampus.CodeAnalysisUtils
2+
3+
| Build | NuGet |
4+
|--|--|
5+
|![](https://github.com/dotnet-campus/DotNetCampus.CodeAnalysisUtils/workflows/.NET%20Core/badge.svg)|[![](https://img.shields.io/nuget/v/DotNetCampus.CodeAnalysisUtils.svg)](https://www.nuget.org/packages/DotNetCampus.CodeAnalysisUtils)|

build/Version.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Project>
2+
<PropertyGroup>
3+
<Version>0.1.0-alpha01</Version>
4+
</PropertyGroup>
5+
</Project>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Immutable;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
6+
namespace DotNetCampus.CodeAnalysis.Utils.CodeAnalysis;
7+
8+
/// <summary>
9+
/// 辅助源生成器获取 MSBuild 属性值。
10+
/// </summary>
11+
internal static class AnalyzerConfigOptionsExtensions
12+
{
13+
/// <summary>
14+
/// 获取 build_property.{key} 的值。
15+
/// </summary>
16+
/// <param name="options">源生成器配置选项。</param>
17+
/// <param name="key">要获取的 MSBuild 属性名。</param>
18+
/// <param name="value">属性值。</param>
19+
/// <typeparam name="T">属性类型。(只支持 <see langword="string"/> 和 <see langword="bool"/>。)</typeparam>
20+
/// <returns>获取到的值的结果。可放到 <see langword="if"/> 条件中。</returns>
21+
public static AnalyzerConfigOptionResult TryGetValue<T>(
22+
this AnalyzerConfigOptions options,
23+
string key,
24+
out T value)
25+
where T : notnull
26+
{
27+
if (options.TryGetValue($"build_property.{key}", out var stringValue) && !string.IsNullOrWhiteSpace(stringValue))
28+
{
29+
value = ConvertFromString<T>(stringValue);
30+
return new AnalyzerConfigOptionResult(options, true)
31+
{
32+
UnsetPropertyNames = [],
33+
};
34+
}
35+
36+
value = default!;
37+
return new AnalyzerConfigOptionResult(options, false)
38+
{
39+
UnsetPropertyNames = [key],
40+
};
41+
}
42+
43+
/// <summary>
44+
/// 连续获取 build_property.{key} 的值。
45+
/// </summary>
46+
/// <param name="builder">使用链式方式获取多个 MSBuild 属性的值。</param>
47+
/// <param name="key">要获取的 MSBuild 属性名。</param>
48+
/// <param name="value">属性值。</param>
49+
/// <typeparam name="T">属性类型。(只支持 <see langword="string"/> 和 <see langword="bool"/>。)</typeparam>
50+
/// <returns>获取到的值的结果。可放到 <see langword="if"/> 条件中,只有能连续获取到的所有属性值时,才满足条件。</returns>
51+
public static AnalyzerConfigOptionResult TryGetValue<T>(
52+
this AnalyzerConfigOptionResult builder,
53+
string key,
54+
out T value)
55+
where T : notnull
56+
{
57+
var options = builder.Options;
58+
59+
if (options.TryGetValue($"build_property.{key}", out var stringValue) && !string.IsNullOrWhiteSpace(stringValue))
60+
{
61+
value = ConvertFromString<T>(stringValue);
62+
return builder.Link(true, key);
63+
}
64+
65+
value = default!;
66+
return builder.Link(false, key);
67+
}
68+
69+
/// <summary>
70+
/// 连续获取 build_property.{key} 的值,在未获取到的时候获取另外由 <paramref name="fallbackKeys"/> 指定的属性值。
71+
/// </summary>
72+
/// <param name="options">源生成器配置选项。</param>
73+
/// <param name="key">要获取的 MSBuild 属性名。</param>
74+
/// <param name="fallbackKeys">当获取不到前一个属性名的值时,依次往后获取属性名的值。</param>
75+
/// <returns>第一个能获取到的属性的值。如果全获取不到,返回 <see langword="null"/>。</returns>
76+
public static string? TryGetValueWithFallback(this AnalyzerConfigOptions options, string key, params string[] fallbackKeys)
77+
{
78+
if (options.TryGetValue($"build_property.{key}", out var stringValue) && !string.IsNullOrWhiteSpace(stringValue))
79+
{
80+
return stringValue;
81+
}
82+
83+
foreach (var fallbackKey in fallbackKeys)
84+
{
85+
if (options.TryGetValue($"build_property.{fallbackKey}", out stringValue) && !string.IsNullOrWhiteSpace(stringValue))
86+
{
87+
return stringValue;
88+
}
89+
}
90+
91+
return null;
92+
}
93+
94+
private static T ConvertFromString<T>(string value)
95+
{
96+
if (typeof(T) == typeof(string))
97+
{
98+
return (T)(object)value;
99+
}
100+
if (typeof(T) == typeof(bool))
101+
{
102+
return (T)(object)value.Equals("true", StringComparison.OrdinalIgnoreCase);
103+
}
104+
return default!;
105+
}
106+
107+
public static string EnsureGetValueWithFallback(this AnalyzerConfigOptions options, string key, params string[] fallbackKeys)
108+
{
109+
return TryGetValueWithFallback(options, key, fallbackKeys)
110+
?? throw new InvalidOperationException($"No value found for key '{key}' or any of the fallback keys.");
111+
}
112+
}
113+
114+
/// <summary>
115+
/// 用于连续获取 MSBuild 属性值的结果。
116+
/// </summary>
117+
/// <param name="Options">源生成器配置选项。</param>
118+
/// <param name="GotValue">在此之前如果获取到了所有的属性值,则为 <see langword="true"/>。</param>
119+
public readonly record struct AnalyzerConfigOptionResult(AnalyzerConfigOptions Options, bool GotValue)
120+
{
121+
/// <summary>
122+
/// 未能获取到值的属性名列表。
123+
/// </summary>
124+
public required ImmutableList<string> UnsetPropertyNames { get; init; }
125+
126+
/// <summary>
127+
/// 将当前结果与新的获取结果链接起来。只有所有的值都获取到了,才返回 <see langword="true"/>;否则返回 <see langword="false"/> 并记录未获取到值的属性名。
128+
/// </summary>
129+
/// <param name="result">新的获取结果。</param>
130+
/// <param name="propertyName">新的属性名。</param>
131+
/// <returns>链接后的结果。</returns>
132+
public AnalyzerConfigOptionResult Link(bool result, string propertyName)
133+
{
134+
if (result)
135+
{
136+
return this;
137+
}
138+
139+
if (propertyName is null)
140+
{
141+
throw new ArgumentNullException(nameof(propertyName), @"The property name must be specified if the result is false.");
142+
}
143+
144+
return this with
145+
{
146+
GotValue = false,
147+
UnsetPropertyNames = UnsetPropertyNames.Add(propertyName),
148+
};
149+
}
150+
151+
/// <summary>
152+
/// 允许将结果直接用在 <see langword="if"/> 条件中。<see langword="true"/> 表示所有属性都成功获取到了。
153+
/// </summary>
154+
/// <param name="result">获取结果。</param>
155+
/// <returns>如果所有属性都成功获取到了,返回 <see langword="true"/>;否则返回 <see langword="false"/>。</returns>
156+
public static implicit operator bool(AnalyzerConfigOptionResult result) => result.GotValue;
157+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#nullable enable
2+
using System.Text.RegularExpressions;
3+
4+
namespace DotNetCampus.CodeAnalysis.Utils.CodeAnalysis;
5+
6+
/// <summary>
7+
/// 为通过模板生成的源代码提供正则表达式。
8+
/// </summary>
9+
public static class TemplateRegexes
10+
{
11+
private static Regex? _namespaceRegex;
12+
private static Regex? _flagRegex;
13+
private static Regex? _flag1Regex;
14+
private static Regex? _flag2Regex;
15+
private static Regex? _flag3Regex;
16+
private static Regex? _flag4Regex;
17+
private static Regex NamespaceRegex => _namespaceRegex ??= new Regex(@"\bnamespace\s+([\w\.]+)(?=\s*[;\{])", RegexOptions.Compiled | RegexOptions.Singleline);
18+
private static Regex FlagRegex => _flagRegex ??= new Regex(@"(?<=\n)\s+// <FLAG>.+?</FLAG>", RegexOptions.Compiled | RegexOptions.Singleline);
19+
private static Regex Flag1Regex => _flag1Regex ??= new Regex(@"(?<=\n)\s+// <FLAG1>.+?</FLAG1>", RegexOptions.Compiled | RegexOptions.Singleline);
20+
private static Regex Flag2Regex => _flag2Regex ??= new Regex(@"(?<=\n)\s+// <FLAG2>.+?</FLAG2>", RegexOptions.Compiled | RegexOptions.Singleline);
21+
private static Regex Flag3Regex => _flag3Regex ??= new Regex(@"(?<=\n)\s+// <FLAG3>.+?</FLAG3>", RegexOptions.Compiled | RegexOptions.Singleline);
22+
private static Regex Flag4Regex => _flag4Regex ??= new Regex(@"(?<=\n)\s+// <FLAG4>.+?</FLAG4>", RegexOptions.Compiled | RegexOptions.Singleline);
23+
24+
/// <summary>
25+
/// 替换代码中的命名空间声明。
26+
/// </summary>
27+
/// <param name="content">包含要替换命名空间代码的字符串。</param>
28+
/// <param name="newNamespace">新的命名空间。</param>
29+
/// <returns>替换了命名空间的新代码。</returns>
30+
public static string ReplaceNamespace(this string content, string newNamespace)
31+
{
32+
return NamespaceRegex.Replace(content, $"namespace {newNamespace}");
33+
}
34+
35+
/// <summary>
36+
/// 替换代码中的 // <FLAG>...</FLAG> 注释,将其替换为指定的内容。
37+
/// </summary>
38+
/// <param name="content">包含要替换的代码的字符串。</param>
39+
/// <param name="flagContent">要替换的内容。</param>
40+
/// <returns>替换后的字符串。</returns>
41+
public static string FlagReplace(this string content, string flagContent)
42+
{
43+
return FlagRegex.Replace(content, flagContent);
44+
}
45+
46+
/// <summary>
47+
/// 替换代码中的 // <FLAG1>...</FLAG1> 注释,将其替换为指定的内容。
48+
/// </summary>
49+
/// <param name="content">包含要替换的代码的字符串。</param>
50+
/// <param name="flagContent">要替换的内容。</param>
51+
/// <returns>替换后的字符串。</returns>
52+
public static string Flag1Replace(this string content, string flagContent)
53+
{
54+
return Flag1Regex.Replace(content, flagContent);
55+
}
56+
57+
/// <summary>
58+
/// 替换代码中的 // <FLAG2>...</FLAG2> 注释,将其替换为指定的内容。
59+
/// </summary>
60+
/// <param name="content">包含要替换的代码的字符串。</param>
61+
/// <param name="flagContent">要替换的内容。</param>
62+
/// <returns>替换后的字符串。</returns>
63+
public static string Flag2Replace(this string content, string flagContent)
64+
{
65+
return Flag2Regex.Replace(content, flagContent);
66+
}
67+
68+
/// <summary>
69+
/// 替换代码中的 // <FLAG3>...</FLAG3> 注释,将其替换为指定的内容。
70+
/// </summary>
71+
/// <param name="content">包含要替换的代码的字符串。</param>
72+
/// <param name="flagContent">要替换的内容。</param>
73+
/// <returns>替换后的字符串。</returns>
74+
public static string Flag3Replace(this string content, string flagContent)
75+
{
76+
return Flag3Regex.Replace(content, flagContent);
77+
}
78+
79+
/// <summary>
80+
/// 替换代码中的 // <FLAG4>...</FLAG4> 注释,将其替换为指定的内容。
81+
/// </summary>
82+
/// <param name="content">包含要替换的代码的字符串。</param>
83+
/// <param name="flagContent">要替换的内容。</param>
84+
/// <returns>替换后的字符串。</returns>
85+
public static string Flag4Replace(this string content, string flagContent)
86+
{
87+
return Flag4Regex.Replace(content, flagContent);
88+
}
89+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<ImplicitUsings>false</ImplicitUsings>
6+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
7+
<IsRoslynComponent>true</IsRoslynComponent>
8+
<RootNamespace>DotNetCampus.CodeAnalysis.Utils</RootNamespace>
9+
<PackageId>DotNetCampus.CodeAnalysisUtils</PackageId>
10+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
11+
<IncludeBuildOutput>false</IncludeBuildOutput>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="DotNetCampus.LatestCSharpFeatures" PrivateAssets="all" />
16+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
17+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
18+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<EmbeddedResource Include="**\*.g.cs" LinkBase="." />
23+
</ItemGroup>
24+
25+
<Target Name="_IncludeAllDependencies" BeforeTargets="_GetPackageFiles">
26+
<ItemGroup>
27+
<None Include="Package\_._" Pack="true" PackagePath="lib\netstandard2.0\" />
28+
<None Include="$(RepositoryRoot)README.md" Pack="true" PackagePath="\" />
29+
<None Include="$(ArtifactsPath)bin\DotNetCampus.CodeAnalysisUtils\$(Configuration)\**\*.dll" Pack="true" PackagePath="analyzers\dotnet\cs" />
30+
</ItemGroup>
31+
</Target>
32+
33+
</Project>

0 commit comments

Comments
 (0)