diff --git a/Directory.Build.props b/Directory.Build.props
index d561ba9..734ffac 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,10 +7,15 @@
latest
true
false
- Copyright (c) 2018-2021 dotnet-campus
+ MIT
+ Copyright (c) 2018-2023 dotnet-campus
https://github.com/dotnet-campus/SourceFusion
https://github.com/dotnet-campus/SourceFusion.git
git
source;dotnet;nuget;msbuild;compile
+
+
+
+
\ No newline at end of file
diff --git a/SourceFusion.sln b/SourceFusion.sln
index cd7d7f7..ca55f44 100644
--- a/SourceFusion.sln
+++ b/SourceFusion.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28606.126
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33723.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceFusion.Tool", "src\SourceFusion.Tool\SourceFusion.Tool.csproj", "{EFDCE47F-3FCE-4428-9F63-340806876FE5}"
EndProject
@@ -37,6 +37,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Telescope.NuGe
{556913DF-6532-4532-B514-2D1184ADC75B} = {556913DF-6532-4532-B514-2D1184ADC75B}
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerator", "SourceGenerator", "{A9879CB9-D164-4A0F-9F7A-D4B86C66BD68}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Telescope.SourceGeneratorAnalyzers", "src\TelescopeSourceGenerator\Analyzers\dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj", "{C8BB3F7F-344C-40B4-BF00-005575C8C91A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo", "src\TelescopeSourceGenerator\Demo\TelescopeSourceGeneratorDemo\dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.csproj", "{350C2DAF-7AD8-4285-9C8F-737D4D078B65}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.Telescope.SourceGeneratorAnalyzers.NuGet", "src\TelescopeSourceGenerator\NuGet\dotnetCampus.Telescope.SourceGeneratorAnalyzers.NuGet.csproj", "{F1E2D63D-A05A-4647-ADB3-750A9F651D22}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Actions", "{5A85E9ED-415C-4720-BF26-8CF8A97CB832}"
+ ProjectSection(SolutionItems) = preProject
+ ..\Ipc\.github\workflows\dotnet-core.yml = ..\Ipc\.github\workflows\dotnet-core.yml
+ ..\Ipc\.github\workflows\dotnet-format.yml = ..\Ipc\.github\workflows\dotnet-format.yml
+ ..\Ipc\.github\workflows\nuget-tag-publish.yml = ..\Ipc\.github\workflows\nuget-tag-publish.yml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -71,6 +86,18 @@ Global
{84DA7EE9-B511-407E-A510-DF390C742F0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84DA7EE9-B511-407E-A510-DF390C742F0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84DA7EE9-B511-407E-A510-DF390C742F0D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C8BB3F7F-344C-40B4-BF00-005575C8C91A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C8BB3F7F-344C-40B4-BF00-005575C8C91A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C8BB3F7F-344C-40B4-BF00-005575C8C91A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C8BB3F7F-344C-40B4-BF00-005575C8C91A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {350C2DAF-7AD8-4285-9C8F-737D4D078B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {350C2DAF-7AD8-4285-9C8F-737D4D078B65}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {350C2DAF-7AD8-4285-9C8F-737D4D078B65}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {350C2DAF-7AD8-4285-9C8F-737D4D078B65}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F1E2D63D-A05A-4647-ADB3-750A9F651D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F1E2D63D-A05A-4647-ADB3-750A9F651D22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F1E2D63D-A05A-4647-ADB3-750A9F651D22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F1E2D63D-A05A-4647-ADB3-750A9F651D22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -78,6 +105,9 @@ Global
GlobalSection(NestedProjects) = preSolution
{5978AAB0-C4C1-44AF-AB92-27F8ECBF6F69} = {02D03DE5-1A19-4002-92BD-0E3DC61A53C4}
{50AE25A2-DF1B-4DE9-A7BE-C406F6497376} = {78213588-C3B8-4E86-A19B-73DECD1ABA7B}
+ {C8BB3F7F-344C-40B4-BF00-005575C8C91A} = {A9879CB9-D164-4A0F-9F7A-D4B86C66BD68}
+ {350C2DAF-7AD8-4285-9C8F-737D4D078B65} = {A9879CB9-D164-4A0F-9F7A-D4B86C66BD68}
+ {F1E2D63D-A05A-4647-ADB3-750A9F651D22} = {A9879CB9-D164-4A0F-9F7A-D4B86C66BD68}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {22702A10-22E1-4670-8FCA-9581202E6CAB}
diff --git a/src/TelescopeSourceGenerator/Analyzers/Context_/AssemblyCandidateClassParseResult.cs b/src/TelescopeSourceGenerator/Analyzers/Context_/AssemblyCandidateClassParseResult.cs
new file mode 100644
index 0000000..1268948
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Context_/AssemblyCandidateClassParseResult.cs
@@ -0,0 +1,50 @@
+using System.Collections.Immutable;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers;
+
+///
+/// 程序集里可能被标记导出的类型
+///
+readonly struct AssemblyCandidateClassParseResult
+{
+ public AssemblyCandidateClassParseResult(INamedTypeSymbol exportedTypeSymbol, ImmutableArray attributes, GeneratorSyntaxContext generatorSyntaxContext)
+ {
+ ExportedTypeSymbol = exportedTypeSymbol;
+ Attributes = attributes;
+ GeneratorSyntaxContext = generatorSyntaxContext;
+ Success = true;
+ }
+
+ public AssemblyCandidateClassParseResult()
+ {
+ Success = false;
+ ExportedTypeSymbol = default!; // 这个瞬间就被过滤掉了,先不考虑可空的复杂写法了
+ Attributes = default;
+ GeneratorSyntaxContext = default;
+ }
+
+ ///
+ /// 导出类型的语义符号
+ ///
+ public INamedTypeSymbol ExportedTypeSymbol { get; }
+
+ ///
+ /// 导出类型的语法符号
+ ///
+ public ClassDeclarationSyntax ExportedTypeClassDeclarationSyntax => (ClassDeclarationSyntax) GeneratorSyntaxContext.Node;
+
+ ///
+ /// 类型标记的特性
+ ///
+ public ImmutableArray Attributes { get; }
+
+ public GeneratorSyntaxContext GeneratorSyntaxContext { get; }
+
+ ///
+ /// 是否成功,用于过滤掉不满足条件的对象
+ ///
+ public bool Success { get; }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/Context_/MarkClassParseResult.cs b/src/TelescopeSourceGenerator/Analyzers/Context_/MarkClassParseResult.cs
new file mode 100644
index 0000000..1d1c0a6
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Context_/MarkClassParseResult.cs
@@ -0,0 +1,47 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers;
+
+///
+/// 被标记的类型的转换结果,约等于生成代码前的最终结果
+///
+readonly struct MarkClassParseResult
+{
+ public MarkClassParseResult(INamedTypeSymbol exportedTypeSymbol, ClassDeclarationSyntax exportedTypeClassDeclarationSyntax,
+ AttributeData matchAssemblyMarkAttributeData, AttributeSyntax matchAssemblyMarkAttributeSyntax,
+ MarkExportAttributeParseResult markExportAttributeParseResult,
+ GeneratorSyntaxContext generatorSyntaxContext)
+ {
+ ExportedTypeSymbol = exportedTypeSymbol;
+ ExportedTypeClassDeclarationSyntax = exportedTypeClassDeclarationSyntax;
+ MatchAssemblyMarkAttributeData = matchAssemblyMarkAttributeData;
+ MatchAssemblyMarkAttributeSyntax = matchAssemblyMarkAttributeSyntax;
+ MarkExportAttributeParseResult = markExportAttributeParseResult;
+ GeneratorSyntaxContext = generatorSyntaxContext;
+ }
+
+ ///
+ /// 导出的 class 类型的语义
+ ///
+ public INamedTypeSymbol ExportedTypeSymbol { get; }
+ ///
+ /// 导出的 class 类型的语法
+ ///
+ public ClassDeclarationSyntax ExportedTypeClassDeclarationSyntax { get; }
+ ///
+ /// 类型上标记的程序集指定特性的语义
+ ///
+ public AttributeData MatchAssemblyMarkAttributeData { get; }
+ ///
+ /// 类型上标记的程序集指定特性的语法
+ ///
+ public AttributeSyntax MatchAssemblyMarkAttributeSyntax { get; }
+
+ ///
+ /// 程序集特性里面的定义结果
+ ///
+ public MarkExportAttributeParseResult MarkExportAttributeParseResult { get; }
+
+ public GeneratorSyntaxContext GeneratorSyntaxContext { get; }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/Context_/MarkExportAttributeParseResult.cs b/src/TelescopeSourceGenerator/Analyzers/Context_/MarkExportAttributeParseResult.cs
new file mode 100644
index 0000000..721d7d9
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Context_/MarkExportAttributeParseResult.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers;
+
+readonly struct MarkExportAttributeParseResult
+{
+ public MarkExportAttributeParseResult(bool success, ITypeSymbol baseClassOrInterfaceTypeInfo,
+ ITypeSymbol attributeTypeInfo)
+ {
+ Success = success;
+ BaseClassOrInterfaceTypeInfo = baseClassOrInterfaceTypeInfo;
+ AttributeTypeInfo = attributeTypeInfo;
+ }
+
+ public bool Success { get; }
+ public ITypeSymbol BaseClassOrInterfaceTypeInfo { get; }
+
+ public ITypeSymbol AttributeTypeInfo { get; }
+
+ ///
+ /// 获取表示失败的特性解析结果。
+ ///
+ public static MarkExportAttributeParseResult Failure { get; } = new MarkExportAttributeParseResult(false, default!, default!);
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/Core/AttributeCodeReWriter.cs b/src/TelescopeSourceGenerator/Analyzers/Core/AttributeCodeReWriter.cs
new file mode 100644
index 0000000..4208e8a
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Core/AttributeCodeReWriter.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers.Core;
+
+static class AttributeCodeReWriter
+{
+ ///
+ /// 从 转换为特性生成代码。从 `[Foo(xx, xxx)]` 语义转换为 `new Foo(xx, xxx)` 的生成代码
+ ///
+ ///
+ ///
+ public static string GetAttributeCreatedCode(AttributeData attributeData)
+ {
+ // 放在特性的构造函数的参数列表,例如 [Foo(1,2,3)] 将会获取到 `1` `2` `3` 三个参数
+ var constructorArgumentCodeList = new List();
+ foreach (TypedConstant constructorArgument in attributeData.ConstructorArguments)
+ {
+ var constructorArgumentCode = TypedConstantToCodeString(constructorArgument);
+
+ constructorArgumentCodeList.Add(constructorArgumentCode);
+ }
+
+ var namedArgumentCodeList = new List<(string propertyName, string valueCode)>();
+ foreach (var keyValuePair in attributeData.NamedArguments)
+ {
+ var key = keyValuePair.Key;
+
+ var typedConstant = keyValuePair.Value;
+ var argumentCode = TypedConstantToCodeString(typedConstant);
+
+ namedArgumentCodeList.Add((key, argumentCode));
+ }
+
+ return
+ $@"new {TypeSymbolHelper.TypeSymbolToFullName(attributeData.AttributeClass!)}({string.Join(",", constructorArgumentCodeList)})
+{{
+ {string.Join(@",
+ ", namedArgumentCodeList.Select(x => $"{x.propertyName} = {x.valueCode}"))}
+}}";
+
+ static string TypedConstantToCodeString(TypedConstant typedConstant)
+ {
+ var constructorArgumentType = typedConstant.Type;
+ var constructorArgumentValue = typedConstant.Value;
+
+ string constructorArgumentCode;
+ switch (typedConstant.Kind)
+ {
+ case TypedConstantKind.Enum:
+ {
+ // "(Foo.Enum1) 1"
+ constructorArgumentCode =
+ $"({TypeSymbolHelper.TypeSymbolToFullName(typedConstant.Type!)}) {typedConstant.Value}";
+ break;
+ }
+ case TypedConstantKind.Type:
+ {
+ var typeSymbol = (ITypeSymbol?)constructorArgumentValue;
+ if (typeSymbol is null)
+ {
+ constructorArgumentCode = "null";
+ }
+ else
+ {
+ constructorArgumentCode = $"typeof({TypeSymbolHelper.TypeSymbolToFullName(typeSymbol)})";
+ }
+
+ break;
+ }
+ default:
+ {
+ constructorArgumentCode = typedConstant.Value?.ToString() ?? "null";
+ break;
+ }
+ }
+
+ return constructorArgumentCode;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/Core/ExportedTypesCodeTextGenerator.cs b/src/TelescopeSourceGenerator/Analyzers/Core/ExportedTypesCodeTextGenerator.cs
new file mode 100644
index 0000000..6bd4979
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Core/ExportedTypesCodeTextGenerator.cs
@@ -0,0 +1,114 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers.Core;
+
+class ExportedTypesCodeTextGenerator
+{
+ public string Generate(ImmutableArray markClassCollection, CancellationToken token)
+ {
+ // 导出的接口
+ var exportedInterfaces = new List();
+ // 导出的方法
+ var exportedMethodCodes = new List();
+
+ foreach (var markClassGroup in markClassCollection.GroupBy(t => t.MarkExportAttributeParseResult))
+ {
+ token.ThrowIfCancellationRequested();
+
+ var markExportAttributeParseResult = markClassGroup.Key;
+
+ var baseClassOrInterfaceName =
+ TypeSymbolHelper.TypeSymbolToFullName(markExportAttributeParseResult.BaseClassOrInterfaceTypeInfo);
+ var attributeName = TypeSymbolHelper.TypeSymbolToFullName(markExportAttributeParseResult.AttributeTypeInfo);
+
+ var exportedItemList = new List();
+
+ foreach (var markClassParseResult in markClassGroup)
+ {
+ var typeName = TypeSymbolHelper.TypeSymbolToFullName(markClassParseResult.ExportedTypeSymbol);
+
+ var attributeCreatedCode =
+ AttributeCodeReWriter.GetAttributeCreatedCode(markClassParseResult.MatchAssemblyMarkAttributeData);
+
+ var itemCode =
+ @$"new AttributedTypeMetadata<{baseClassOrInterfaceName}, {attributeName}>(typeof({typeName}), {attributeCreatedCode}, () => new {typeName}())";
+ exportedItemList.Add(itemCode);
+ }
+
+ var arrayExpression = $@"new AttributedTypeMetadata<{baseClassOrInterfaceName}, {attributeName}>[]
+ {{
+ {string.Join(@",
+ ", exportedItemList)}
+ }}";
+
+ var methodCode =
+ $@"AttributedTypeMetadata<{baseClassOrInterfaceName}, {attributeName}>[] ICompileTimeAttributedTypesExporter<{baseClassOrInterfaceName}, {attributeName}>.ExportAttributeTypes()
+ {{
+ return {arrayExpression};
+ }}";
+
+ exportedMethodCodes.Add(methodCode);
+
+ exportedInterfaces.Add(
+ $@"ICompileTimeAttributedTypesExporter<{baseClassOrInterfaceName}, {attributeName}>");
+ }
+
+ var code = $@"using dotnetCampus.Telescope;
+
+namespace dotnetCampus.Telescope
+{{
+ public partial class __AttributedTypesExport__ : {string.Join(", ", exportedInterfaces)}
+ {{
+ {string.Join(@"
+ ", exportedMethodCodes)}
+ }}
+}}";
+ code = FormatCode(code);
+ // 生成的代码示例:
+ /*
+using dotnetCampus.Telescope;
+
+namespace dotnetCampus.Telescope
+{
+ public partial class __AttributedTypesExport__ : ICompileTimeAttributedTypesExporter
+ {
+ AttributedTypeMetadata[] ICompileTimeAttributedTypesExporter.ExportAttributeTypes()
+ {
+ return new AttributedTypeMetadata[]
+ {
+ new AttributedTypeMetadata
+ (
+ typeof(global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.Foo),
+ new global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.FooAttribute(1, (global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.FooEnum)1, typeof(global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.Base), null)
+ {
+ Number2 = 2,
+ Type2 = typeof(global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.Foo),
+ FooEnum2 = (global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.FooEnum)0,
+ Type3 = null
+ },
+ () => new global::dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.Foo()
+ )
+ };
+ }
+ }
+}
+ */
+ return code;
+ }
+
+ ///
+ /// 格式化代码。
+ ///
+ /// 未格式化的源代码。
+ /// 格式化的源代码。
+ private static string FormatCode(string sourceCode)
+ {
+ var rootSyntaxNode = CSharpSyntaxTree.ParseText(sourceCode).GetRoot();
+ return rootSyntaxNode.NormalizeWhitespace().ToFullString();
+ }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/Core/TypeSymbolHelper.cs b/src/TelescopeSourceGenerator/Analyzers/Core/TypeSymbolHelper.cs
new file mode 100644
index 0000000..0efa05d
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/Core/TypeSymbolHelper.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers.Core;
+
+static class TypeSymbolHelper
+{
+ ///
+ /// 输出类型的完全限定名
+ ///
+ public static string TypeSymbolToFullName(ITypeSymbol typeSymbol)
+ {
+ // 带上 global 格式的输出 FullName 内容
+ var symbolDisplayFormat = new SymbolDisplayFormat
+ (
+ // 带上命名空间和类型名
+ SymbolDisplayGlobalNamespaceStyle.Included,
+ // 命名空间之前加上 global 防止冲突
+ SymbolDisplayTypeQualificationStyle
+ .NameAndContainingTypesAndNamespaces
+ );
+
+ return typeSymbol.ToDisplayString(symbolDisplayFormat);
+ }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/TelescopeIncrementalGenerator.cs b/src/TelescopeSourceGenerator/Analyzers/TelescopeIncrementalGenerator.cs
new file mode 100644
index 0000000..dd26f0a
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/TelescopeIncrementalGenerator.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using dotnetCampus.Telescope.SourceGeneratorAnalyzers.Core;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers;
+
+[Generator(LanguageNames.CSharp)]
+public class TelescopeIncrementalGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+#if DEBUG
+ Debugger.Launch();
+#endif
+
+ // 先读取程序集特性,接着遍历整个程序集的所有代码文件,看看哪些是符合需求的,收集起来
+ // 读取程序集特性
+
+ var assemblyAttributeSyntaxContextIncrementalValuesProvider =
+ context.SyntaxProvider.CreateSyntaxProvider
+ (
+ // 语法分析,过滤只有是程序集特性
+ (syntaxNode, cancellationToken) =>
+ {
+ // 预先判断是 assembly 的特性再继续
+ // [assembly: MarkExport(typeof(Base), typeof(FooAttribute))]
+ return syntaxNode.IsKind(SyntaxKind.AttributeList) && syntaxNode.ChildNodes()
+ .Any(subNode => subNode.IsKind(SyntaxKind.AttributeTargetSpecifier));
+ },
+ // 获取只有是属于程序集标记的特性才使用
+ ParseMarkExportAttribute
+ )
+ .Where(t => t.Success)
+ .Collect();
+
+ // 遍历整个程序集的所有代码文件
+ // 获取出所有标记了特性的类型,用来在下一步判断是否属于导出的类型
+ var assemblyClassIncrementalValuesProvider =
+ context.SyntaxProvider.CreateSyntaxProvider
+ (
+ // 语法分析,只有是 class 类型定义的才可能满足需求
+ (syntaxNode, cancellationToken) => syntaxNode.IsKind(SyntaxKind.ClassDeclaration),
+
+ // 加上语义分析,了解当前的类型是否有添加任何标记
+ (generatorSyntaxContext, cancellationToken) =>
+ {
+ var classDeclarationSyntax = (ClassDeclarationSyntax) generatorSyntaxContext.Node;
+
+ // 从语法转换为语义,用于后续判断是否标记了特性
+ INamedTypeSymbol? namedTypeSymbol =
+ generatorSyntaxContext.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
+
+ // 如果可以获取到语义的类型,则尝试获取其标记的特性
+ if (namedTypeSymbol is not null
+ // 抽象类不应该被加入创建
+ && !namedTypeSymbol.IsAbstract)
+ {
+ var attributes = namedTypeSymbol.GetAttributes();
+
+ if (attributes.Length > 0)
+ {
+ return new AssemblyCandidateClassParseResult(namedTypeSymbol, attributes,
+ generatorSyntaxContext);
+ }
+ else
+ {
+ // 只有标记了特性的,才是可能候选的类型
+ }
+ }
+
+ return new AssemblyCandidateClassParseResult();
+ }
+ )
+ // 过滤掉不符合条件的类型
+ .Where(t => t.Success);
+
+ // 将程序集特性和类型组合一起,看看哪些类型符合程序集特性的要求,将其拼装到一起
+ IncrementalValueProvider> collectionClass =
+ assemblyClassIncrementalValuesProvider
+ .Combine(assemblyAttributeSyntaxContextIncrementalValuesProvider)
+ .Select((tuple, token) => { return ParseMarkClassList(tuple.Left, tuple.Right); })
+ .SelectMany((list, _) => list)
+ .Collect();
+
+ // 参考 AttributedTypesExportFileGenerator 逻辑生成代码
+ IncrementalValueProvider generatedCodeProvider = collectionClass.Select((markClassCollection, token) =>
+ {
+ var attributedTypesExportGenerator = new ExportedTypesCodeTextGenerator();
+ string generatedCode = attributedTypesExportGenerator.Generate(markClassCollection, token);
+ return generatedCode;
+ });
+
+ // 注册到输出
+ context.RegisterSourceOutput(generatedCodeProvider,
+ (sourceProductionContext, generatedCode) =>
+ {
+ sourceProductionContext.AddSource("__AttributedTypesExport__", generatedCode);
+ });
+ }
+
+ ///
+ /// 转换被标记的类型的信息
+ ///
+ /// 程序集里面的类型
+ /// 程序集里面的各个标记
+ ///
+ private List ParseMarkClassList(AssemblyCandidateClassParseResult classParseResult,
+ ImmutableArray markExportAttributeParseResultList)
+ {
+ var list = new List();
+ foreach (MarkExportAttributeParseResult markExportAttributeParseResult in markExportAttributeParseResultList)
+ {
+ var result = ParseMarkClass(classParseResult, markExportAttributeParseResult);
+ if (result != null)
+ {
+ list.Add(result.Value);
+ }
+ }
+
+ return list;
+ }
+
+ ///
+ /// 转换被标记的类型的信息,判断当前的类型是否满足当前所选的程序集特性
+ ///
+ ///
+ ///
+ ///
+ private MarkClassParseResult? ParseMarkClass(AssemblyCandidateClassParseResult classParseResult,
+ MarkExportAttributeParseResult markExportAttributeParseResult)
+ {
+ // 先判断满足的类型
+ var matchAssemblyMarkAttributeData = classParseResult.Attributes.FirstOrDefault(t =>
+ SymbolEqualityComparer.Default.Equals(t.AttributeClass,
+ markExportAttributeParseResult.AttributeTypeInfo));
+
+ if (matchAssemblyMarkAttributeData?.ApplicationSyntaxReference is null)
+ {
+ // 找不到匹配的特性,表示这个类型不应该被收集
+ return default;
+ }
+
+ // 同时获取其语法
+ AttributeSyntax? markAttributeSyntax = classParseResult
+ .ExportedTypeClassDeclarationSyntax
+ .AttributeLists
+ .SelectMany(t => t.Attributes)
+ // 理论上 Span 是相同的,这里用 Contains 或 == 都应该是相同的结果
+ .FirstOrDefault(t => t.Span.Contains(matchAssemblyMarkAttributeData.ApplicationSyntaxReference.Span));
+
+ if (markAttributeSyntax is null)
+ {
+ // 理论上不可能是空,因为已找到其特性
+ return default;
+ }
+
+ // 再判断继承类型
+ var requiredBaseClassOrInterfaceType = markExportAttributeParseResult.BaseClassOrInterfaceTypeInfo;
+
+ if (IsInherit(classParseResult.ExportedTypeSymbol, requiredBaseClassOrInterfaceType))
+ {
+ return new MarkClassParseResult(classParseResult.ExportedTypeSymbol,
+ classParseResult.ExportedTypeClassDeclarationSyntax, matchAssemblyMarkAttributeData, markAttributeSyntax,
+ markExportAttributeParseResult, classParseResult.GeneratorSyntaxContext);
+ }
+
+ return null;
+
+ // 判断类型继承关系
+ static bool IsInherit(ITypeSymbol currentType, ITypeSymbol requiredType)
+ {
+ var baseType = currentType.BaseType;
+ while (baseType is not null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(baseType, requiredType))
+ {
+ // 如果基类型是的话
+ return true;
+ }
+
+ // 否则继续找基类型
+ baseType = baseType.BaseType;
+ }
+
+ foreach (var currentInheritInterfaceType in currentType.AllInterfaces)
+ {
+ if (SymbolEqualityComparer.Default.Equals(currentInheritInterfaceType, requiredType))
+ {
+ // 如果继承的类型是的话
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// 解析出定义在程序集里面的特性
+ ///
+ ///
+ ///
+ ///
+ private MarkExportAttributeParseResult ParseMarkExportAttribute(GeneratorSyntaxContext generatorSyntaxContext,
+ CancellationToken cancellationToken)
+ {
+ if (generatorSyntaxContext.Node is not AttributeListSyntax attributeListSyntax)
+ {
+ return MarkExportAttributeParseResult.Failure;
+ }
+
+ foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
+ {
+ // [assembly: MarkExport(typeof(Base), typeof(FooAttribute))]
+ // attributeSyntax:拿到 MarkExport 符号
+ // 由于只是拿到 MarkExport 符号,不等于是 `dotnetCampus.Telescope.MarkExportAttribute` 特性,需要走语义分析
+ var typeInfo = generatorSyntaxContext.SemanticModel.GetTypeInfo(attributeSyntax);
+ if (typeInfo.Type is { } attributeType && attributeSyntax.ArgumentList is not null)
+ {
+ // 带上 global 格式的输出 FullName 内容
+ var symbolDisplayFormat = new SymbolDisplayFormat(
+ // 带上命名空间和类型名
+ SymbolDisplayGlobalNamespaceStyle.Included,
+ // 命名空间之前加上 global 防止冲突
+ SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
+ var fullName = attributeType.ToDisplayString(symbolDisplayFormat);
+
+ if (fullName == "global::dotnetCampus.Telescope.MarkExportAttribute")
+ {
+ // 这个是符合预期的
+ var attributeArgumentSyntaxList = attributeSyntax.ArgumentList.Arguments;
+ if (attributeArgumentSyntaxList.Count == 2)
+ {
+ var baseClassOrInterfaceTypeSyntax = attributeArgumentSyntaxList[0];
+ var attributeTypeSyntax = attributeArgumentSyntaxList[1];
+
+ // 原本采用的是 GuessTypeNameByTypeOfSyntax 方式获取的,现在可以通过语义获取
+ var baseClassOrInterfaceTypeInfo = GetTypeInfoFromArgumentTypeOfSyntax(baseClassOrInterfaceTypeSyntax);
+ var attributeTypeInfo = GetTypeInfoFromArgumentTypeOfSyntax(attributeTypeSyntax);
+
+ if (baseClassOrInterfaceTypeInfo?.Type is not null && attributeTypeInfo?.Type is not null)
+ {
+ return new MarkExportAttributeParseResult(true, baseClassOrInterfaceTypeInfo.Value.Type,
+ attributeTypeInfo.Value.Type);
+ }
+
+ TypeInfo? GetTypeInfoFromArgumentTypeOfSyntax(AttributeArgumentSyntax attributeArgumentSyntax)
+ {
+ if (attributeArgumentSyntax.Expression is TypeOfExpressionSyntax typeOfExpressionSyntax)
+ {
+ var typeSyntax = typeOfExpressionSyntax.Type;
+ var typeOfType = generatorSyntaxContext.SemanticModel.GetTypeInfo(typeSyntax);
+ return typeOfType;
+ }
+
+ return null;
+ }
+ }
+ }
+ }
+ }
+
+ return MarkExportAttributeParseResult.Failure;
+ }
+}
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj b/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj
new file mode 100644
index 0000000..e05efec
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.0
+ false
+
+ true
+
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj.DotSettings b/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj.DotSettings
new file mode 100644
index 0000000..8616cc0
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Analyzers/dotnetCampus.Telescope.SourceGeneratorAnalyzers.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/AssemblyInfo.cs b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/AssemblyInfo.cs
new file mode 100644
index 0000000..419ea61
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using dotnetCampus.Telescope;
+using dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo;
+
+[assembly: MarkExport(typeof(Base), typeof(FooAttribute))]
diff --git a/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/Program.cs b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/Program.cs
new file mode 100644
index 0000000..31eda76
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/Program.cs
@@ -0,0 +1,58 @@
+namespace dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo;
+
+internal class Program
+{
+ static void Main(string[] args)
+ {
+ var attributedTypesExport = new __AttributedTypesExport__();
+ ICompileTimeAttributedTypesExporter exporter = attributedTypesExport;
+ foreach (var exportedTypeMetadata in exporter.ExportAttributeTypes())
+ {
+ // 输出导出的类型
+ Console.WriteLine(exportedTypeMetadata.RealType.FullName);
+ }
+ }
+}
+
+[Foo(0, FooEnum.N1, typeof(Foo), null)]
+abstract class F1 : Base
+{
+}
+
+[Foo(1ul, FooEnum.N2, typeof(Base), null, Number2 = 2L, Type2 = typeof(Foo), FooEnum2 = FooEnum.N1, Type3 = null)]
+class Foo : Base
+{
+}
+
+class Base
+{
+}
+
+class FooAttribute : Attribute
+{
+ public FooAttribute(ulong number1, FooEnum fooEnum, Type? type1, Type? type3)
+ {
+ Number1 = number1;
+ FooEnum1 = fooEnum;
+ Type1 = type1;
+ }
+
+ public ulong Number1 { get; set; }
+ public long Number2 { get; set; }
+
+ public FooEnum FooEnum1 { get; set; }
+ public FooEnum FooEnum2 { get; set; }
+ public FooEnum FooEnum3 { get; set; }
+
+ public Type? Type1 { get; set; }
+ public Type? Type2 { get; set; }
+ public Type? Type3 { get; set; }
+}
+
+public enum FooEnum
+{
+ N1,
+ N2,
+ N3,
+}
+
diff --git a/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.csproj b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.csproj
new file mode 100644
index 0000000..122fd0f
--- /dev/null
+++ b/src/TelescopeSourceGenerator/Demo/TelescopeSourceGeneratorDemo/dotnetCampus.Telescope.SourceGeneratorAnalyzers.Demo.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
diff --git a/src/TelescopeSourceGenerator/NuGet/dotnetCampus.Telescope.SourceGeneratorAnalyzers.NuGet.csproj b/src/TelescopeSourceGenerator/NuGet/dotnetCampus.Telescope.SourceGeneratorAnalyzers.NuGet.csproj
new file mode 100644
index 0000000..072d909
--- /dev/null
+++ b/src/TelescopeSourceGenerator/NuGet/dotnetCampus.Telescope.SourceGeneratorAnalyzers.NuGet.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net6.0;net5.0;netcoreapp3.1;netstandard2.0;net45
+ dotnetCampus.Telescope.SourceGeneratorAnalyzers
+ $(PackageId)
+ false
+ ..\dotnetCampus.TelescopeTask\bin\$(Configuration)\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dotnetCampus.Telescope.Sample/dotnetCampus.Telescope.Sample.csproj b/src/dotnetCampus.Telescope.Sample/dotnetCampus.Telescope.Sample.csproj
index c95ae7d..7398b79 100644
--- a/src/dotnetCampus.Telescope.Sample/dotnetCampus.Telescope.Sample.csproj
+++ b/src/dotnetCampus.Telescope.Sample/dotnetCampus.Telescope.Sample.csproj
@@ -5,6 +5,7 @@
netcoreapp3.1
latest
enable
+ false
diff --git a/src/dotnetCampus.Telescope/dotnetCampus.Telescope.csproj b/src/dotnetCampus.Telescope/dotnetCampus.Telescope.csproj
index 597cf77..37ea35d 100644
--- a/src/dotnetCampus.Telescope/dotnetCampus.Telescope.csproj
+++ b/src/dotnetCampus.Telescope/dotnetCampus.Telescope.csproj
@@ -4,6 +4,8 @@
net6.0;net5.0;netcoreapp3.1;netstandard2.0;net45
latest
enable
+
+ false