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