From b523a2bb2ae272c0d9766b2f499b753ea900a597 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 28 Nov 2023 22:05:02 +0800 Subject: [PATCH] 1.8.3 package --- .gitignore | 4 +- .../Automation/AttributeGenerator.cs | 167 ----- .../Automation/CommandGenerator.cs | 98 --- .../Automation/ConstructorGenerator.cs | 193 ------ .../Automation/DependencyPropertyGenerator.cs | 130 ---- .../Automation/SaltConstantGenerator.cs | 72 -- .../CodeAnalysis/NotNullWhenAttribute.cs | 18 - .../HttpClientGenerator.cs | 145 ---- .../DependencyInjection/InjectionGenerator.cs | 145 ---- .../DependencyInjection/ServiceAnalyzer.cs | 78 --- .../Enum/LocalizedEnumGenerator.cs | 136 ---- .../GlobalSuppressions.cs | 8 - .../Identity/IdentityGenerator.cs | 202 ------ .../Snap.Hutao.SourceGeneration/JsonParser.cs | 439 ------------ .../Primitive/AttributeDataExtension.cs | 32 - .../Primitive/EnumerableExtension.cs | 41 -- .../Primitive/GeneratorSyntaxContext2.cs | 77 --- .../GeneratorSyntaxContextExtension.cs | 18 - .../Primitive/SymbolDisplayFormats.cs | 21 - .../Primitive/SyntaxExtension.cs | 20 - .../Primitive/TypeSymbolExtension.cs | 28 - .../Resx/ResxGenerator.cs | 629 ------------------ .../Resx/StringExtensions.cs | 27 - .../Snap.Hutao.SourceGeneration.csproj | 19 - .../UniversalAnalyzer.cs | 302 --------- .../Snap.Hutao.SourceGeneration/stylecop.json | 23 - src/Snap.Hutao/Snap.Hutao.sln | 28 +- .../Snap.Hutao/Package.appxmanifest | 2 +- .../Package.development.appxmanifest | 2 +- .../PublishProfiles/FolderProfile.pubxml | 19 + src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 20 +- 31 files changed, 43 insertions(+), 3100 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/ConstructorGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/ServiceAnalyzer.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContextExtension.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SymbolDisplayFormats.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SyntaxExtension.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/TypeSymbolExtension.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/StringExtensions.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json create mode 100644 src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/.gitignore b/.gitignore index 2f91f8dc..130e1dc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ desktop.ini *.csproj.user -*.pubxml *.DotSettings.user .vs/ @@ -20,4 +19,5 @@ src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/ src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/ src/Snap.Hutao/Snap.Hutao.Test/bin/ -src/Snap.Hutao/Snap.Hutao.Test/obj/ \ No newline at end of file +src/Snap.Hutao/Snap.Hutao.Test/obj/ +src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml.user diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs deleted file mode 100644 index 80e068fe..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Snap.Hutao.SourceGeneration.Automation; - -[Generator(LanguageNames.CSharp)] -internal sealed class AttributeGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterPostInitializationOutput(GenerateAllAttributes); - } - - public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationContext context) - { - string coreAnnotations = """ - using System.Diagnostics; - - namespace Snap.Hutao.Core.Annotation; - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - internal sealed class CommandAttribute : Attribute - { - public CommandAttribute(string name) - { - } - - public bool AllowConcurrentExecutions { get; set; } - } - - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - internal sealed class ConstructorGeneratedAttribute : Attribute - { - public ConstructorGeneratedAttribute() - { - } - - public bool CallBaseConstructor { get; set; } - public bool ResolveHttpClient { get; set; } - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - internal sealed class DependencyPropertyAttribute : Attribute - { - public DependencyPropertyAttribute(string name, Type type) - { - } - - public DependencyPropertyAttribute(string name, Type type, object defaultValue) - { - } - - public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName) - { - } - - public bool IsAttached { get; set; } - public Type AttachedType { get; set; } = default; - } - - [AttributeUsage(AttributeTargets.All, Inherited = false)] - [Conditional("DEBUG")] - internal sealed class HighQualityAttribute : Attribute - { - } - """; - context.AddSource("Snap.Hutao.Core.Annotation.Attributes.g.cs", coreAnnotations); - - string coreDependencyInjectionAnnotationHttpClients = """ - namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; - - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - internal sealed class HttpClientAttribute : Attribute - { - public HttpClientAttribute(HttpClientConfiguration configuration) - { - } - - public HttpClientAttribute(HttpClientConfiguration configuration, Type interfaceType) - { - } - } - - internal enum HttpClientConfiguration - { - /// - /// 默认配置 - /// - Default, - - /// - /// 米游社请求配置 - /// - XRpc, - - /// - /// 米游社登录请求配置 - /// - XRpc2, - - /// - /// Hoyolab app - /// - XRpc3, - } - - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute - { - /// - public int MaxConnectionsPerServer { get; set; } - - /// - /// - /// - public bool UseCookies { get; set; } - } - """; - context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.Attributes.g.cs", coreDependencyInjectionAnnotationHttpClients); - - string coreDependencyInjectionAnnotations = """ - namespace Snap.Hutao.Core.DependencyInjection.Annotation; - - internal enum InjectAs - { - Singleton, - Transient, - Scoped, - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - internal sealed class InjectionAttribute : Attribute - { - public InjectionAttribute(InjectAs injectAs) - { - } - - public InjectionAttribute(InjectAs injectAs, Type interfaceType) - { - } - - public object Key { get; set; } - } - """; - context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations); - - string resourceLocalization = """ - namespace Snap.Hutao.Resource.Localization; - - [AttributeUsage(AttributeTargets.Enum)] - internal sealed class LocalizationAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Field)] - internal sealed class LocalizationKeyAttribute : Attribute - { - public LocalizationKeyAttribute(string key) - { - } - } - """; - context.AddSource("Snap.Hutao.Resource.Localization.Attributes.g.cs", resourceLocalization); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs deleted file mode 100644 index 83b1d206..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.Automation; - -[Generator(LanguageNames.CSharp)] -internal sealed class CommandGenerator : IIncrementalGenerator -{ - public const string AttributeName = "Snap.Hutao.Core.Annotation.CommandAttribute"; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider>> commands = - context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedMethods, CommandMethod) - .Where(GeneratorSyntaxContext2.NotNull) - .Collect(); - - context.RegisterImplementationSourceOutput(commands, GenerateCommandImplementations); - } - - private static bool FilterAttributedMethods(SyntaxNode node, CancellationToken token) - { - return node is MethodDeclarationSyntax methodDeclarationSyntax - && methodDeclarationSyntax.Parent is ClassDeclarationSyntax classDeclarationSyntax - && classDeclarationSyntax.Modifiers.Count > 1 - && methodDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 CommandMethod(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.TryGetDeclaredSymbol(token, out IMethodSymbol? methodSymbol)) - { - ImmutableArray attributes = methodSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, methodSymbol, attributes); - } - } - - return default; - } - - private static void GenerateCommandImplementations(SourceProductionContext production, ImmutableArray> context2s) - { - foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString())) - { - GenerateCommandImplementation(production, context2); - } - } - - private static void GenerateCommandImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2) - { - INamedTypeSymbol classSymbol = context2.Symbol.ContainingType; - - AttributeData commandInfo = context2.SingleAttribute(AttributeName); - string commandName = (string)commandInfo.ConstructorArguments[0].Value!; - - string commandType = context2.Symbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task") - ? "AsyncRelayCommand" - : "RelayCommand"; - - string genericParameter = context2.Symbol.Parameters.ElementAtOrDefault(0) is IParameterSymbol parameter - ? $"<{parameter.Type.ToDisplayString(SymbolDisplayFormats.FullyQualifiedNonNullableFormat)}>" - : string.Empty; - - string concurrentExecution = commandInfo.HasNamedArgumentWith("AllowConcurrentExecutions", value => value) - ? ", AsyncRelayCommandOptions.AllowConcurrentExecutions" - : string.Empty; - - string className = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); - - string code = $$""" - using CommunityToolkit.Mvvm.Input; - - namespace {{classSymbol.ContainingNamespace}}; - - partial class {{className}} - { - private ICommand _{{commandName}}; - - public ICommand {{commandName}} - { - get => _{{commandName}} ??= new {{commandType}}{{genericParameter}}({{context2.Symbol.Name}}{{concurrentExecution}}); - } - } - """; - - string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}'); - production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/ConstructorGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/ConstructorGenerator.cs deleted file mode 100644 index 0d005188..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/ConstructorGenerator.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.Automation; - -[Generator(LanguageNames.CSharp)] -internal sealed class ConstructorGenerator : IIncrementalGenerator -{ - private const string AttributeName = "Snap.Hutao.Core.Annotation.ConstructorGeneratedAttribute"; - private const string CompilerGenerated = "System.Runtime.CompilerServices.CompilerGeneratedAttribute"; - - //private static readonly DiagnosticDescriptor genericTypeNotSupportedDescriptor = new("SH102", "Generic type is not supported to generate .ctor", "Type [{0}] is not supported", "Quality", DiagnosticSeverity.Error, true); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider> injectionClasses = - context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, ConstructorGeneratedClass) - .Where(GeneratorSyntaxContext2.NotNull) - .Collect(); - - context.RegisterSourceOutput(injectionClasses, GenerateConstructorImplementations); - } - - private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token) - { - return node is ClassDeclarationSyntax classDeclarationSyntax - && classDeclarationSyntax.Modifiers.Count > 1 - && classDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 ConstructorGeneratedClass(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol)) - { - ImmutableArray attributes = classSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, classSymbol, attributes); - } - } - - return default; - } - - private static void GenerateConstructorImplementations(SourceProductionContext production, ImmutableArray context2s) - { - foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString())) - { - GenerateConstructorImplementation(production, context2); - } - } - - private static void GenerateConstructorImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2) - { - AttributeData constructorInfo = context2.SingleAttribute(AttributeName); - - bool resolveHttpClient = constructorInfo.HasNamedArgumentWith("ResolveHttpClient", value => value); - bool callBaseConstructor = constructorInfo.HasNamedArgumentWith("CallBaseConstructor", value => value); - string httpclient = resolveHttpClient ? ", System.Net.Http.HttpClient httpClient" : string.Empty; - - FieldValueAssignmentOptions options = new(resolveHttpClient, callBaseConstructor); - - StringBuilder sourceBuilder = new StringBuilder().Append($$""" - namespace {{context2.Symbol.ContainingNamespace}}; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(ConstructorGenerator)}}", "1.0.0.0")] - partial class {{context2.Symbol.ToDisplayString(SymbolDisplayFormats.QualifiedNonNullableFormat)}} - { - public {{context2.Symbol.Name}}(System.IServiceProvider serviceProvider{{httpclient}}){{(options.CallBaseConstructor ? " : base(serviceProvider)" : string.Empty)}} - { - - """); - - FillUpWithFieldValueAssignment(sourceBuilder, context2, options); - - sourceBuilder.Append(""" - } - } - """); - - string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}'); - production.AddSource($"{normalizedClassName}.ctor.g.cs", sourceBuilder.ToString()); - } - - private static void FillUpWithFieldValueAssignment(StringBuilder builder, GeneratorSyntaxContext2 context2, FieldValueAssignmentOptions options) - { - IEnumerable fields = context2.Symbol.GetMembers() - .Where(m => m.Kind == SymbolKind.Field) - .OfType(); - - foreach (IFieldSymbol fieldSymbol in fields) - { - if (fieldSymbol.Name.AsSpan()[0] is '<') - { - continue; - } - - bool shoudSkip = false; - foreach (SyntaxReference syntaxReference in fieldSymbol.DeclaringSyntaxReferences) - { - if (syntaxReference.GetSyntax() is VariableDeclaratorSyntax declarator) - { - if (declarator.Initializer is not null) - { - // Skip field with initializer - builder.Append(" // Skip field with initializer: ").AppendLine(fieldSymbol.Name); - shoudSkip = true; - break; - } - } - } - - if (shoudSkip) - { - continue; - } - - if (fieldSymbol.IsReadOnly && !fieldSymbol.IsStatic) - { - switch (fieldSymbol.Type.ToDisplayString()) - { - case "System.IServiceProvider": - builder - .Append(" this.") - .Append(fieldSymbol.Name) - .AppendLine(" = serviceProvider;"); - break; - - case "System.Net.Http.HttpClient": - if (options.ResolveHttpClient) - { - builder - .Append(" this.") - .Append(fieldSymbol.Name) - .AppendLine(" = httpClient;"); - } - else - { - builder - .Append(" this.") - .Append(fieldSymbol.Name) - .Append(" = serviceProvider.GetRequiredService().CreateClient(nameof(") - .Append(context2.Symbol.Name) - .AppendLine("));"); - } - break; - - default: - builder - .Append(" this.") - .Append(fieldSymbol.Name) - .Append(" = serviceProvider.GetRequiredService<") - .Append(fieldSymbol.Type) - .AppendLine(">();"); - break; - } - } - } - - foreach (INamedTypeSymbol interfaceSymbol in context2.Symbol.Interfaces) - { - if (interfaceSymbol.Name == "IRecipient") - { - builder - .Append(" CommunityToolkit.Mvvm.Messaging.IMessengerExtensions.Register<") - .Append(interfaceSymbol.TypeArguments[0]) - .AppendLine(">(serviceProvider.GetRequiredService(), this);"); - } - } - } - - private readonly struct FieldValueAssignmentOptions - { - public readonly bool ResolveHttpClient; - public readonly bool CallBaseConstructor; - - public FieldValueAssignmentOptions(bool resolveHttpClient, bool callBaseConstructor) - { - ResolveHttpClient = resolveHttpClient; - CallBaseConstructor = callBaseConstructor; - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs deleted file mode 100644 index 8d0d7748..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/DependencyPropertyGenerator.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.Automation; - -[Generator(LanguageNames.CSharp)] -internal sealed class DependencyPropertyGenerator : IIncrementalGenerator -{ - private const string AttributeName = "Snap.Hutao.Core.Annotation.DependencyPropertyAttribute"; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider> commands = - context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, CommandMethod) - .Where(GeneratorSyntaxContext2.NotNull) - .Collect(); - - context.RegisterImplementationSourceOutput(commands, GenerateDependencyPropertyImplementations); - } - - private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token) - { - return node is ClassDeclarationSyntax classDeclarationSyntax - && classDeclarationSyntax.Modifiers.Count > 1 - && classDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 CommandMethod(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? methodSymbol)) - { - ImmutableArray attributes = methodSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, methodSymbol, attributes); - } - } - - return default; - } - - private static void GenerateDependencyPropertyImplementations(SourceProductionContext production, ImmutableArray context2s) - { - foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString())) - { - GenerateDependencyPropertyImplementation(production, context2); - } - } - - private static void GenerateDependencyPropertyImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2) - { - foreach (AttributeData propertyInfo in context2.Attributes.Where(attr => attr.AttributeClass!.ToDisplayString() == AttributeName)) - { - string owner = context2.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); - Dictionary namedArguments = propertyInfo.NamedArguments.ToDictionary(); - bool isAttached = namedArguments.TryGetValue("IsAttached", out TypedConstant constant) && (bool)constant.Value!; - string register = isAttached ? "RegisterAttached" : "Register"; - - ImmutableArray arguments = propertyInfo.ConstructorArguments; - - string propertyName = (string)arguments[0].Value!; - string propertyType = arguments[1].Value!.ToString(); - string defaultValue = arguments.ElementAtOrDefault(2).ToCSharpString() ?? "default"; - defaultValue = defaultValue == "null" ? "default" : defaultValue; - string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty; - - string code; - if (isAttached) - { - string objType = namedArguments.TryGetValue("AttachedType", out TypedConstant attachedType) - ? attachedType.Value!.ToString() - : "object"; - - code = $$""" - using Microsoft.UI.Xaml; - - namespace {{context2.Symbol.ContainingNamespace}}; - - partial class {{owner}} - { - private static readonly DependencyProperty {{propertyName}}Property = - DependencyProperty.RegisterAttached("{{propertyName}}", typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}})); - - public static {{propertyType}} Get{{propertyName}}({{objType}} obj) - { - return ({{propertyType}})obj?.GetValue({{propertyName}}Property); - } - - public static void Set{{propertyName}}({{objType}} obj, {{propertyType}} value) - { - obj.SetValue({{propertyName}}Property, value); - } - } - """; - } - else - { - code = $$""" - using Microsoft.UI.Xaml; - - namespace {{context2.Symbol.ContainingNamespace}}; - - partial class {{owner}} - { - private static readonly DependencyProperty {{propertyName}}Property = - DependencyProperty.Register(nameof({{propertyName}}), typeof({{propertyType}}), typeof({{owner}}), new PropertyMetadata(({{propertyType}}){{defaultValue}}{{propertyChangedCallback}})); - - public {{propertyType}} {{propertyName}} - { - get => ({{propertyType}})GetValue({{propertyName}}Property); - set => SetValue({{propertyName}}Property, value); - } - } - """; - } - - string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}'); - production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code); - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs deleted file mode 100644 index 86967884..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using System; -using System.Net.Http; -using System.Runtime.Serialization; - -namespace Snap.Hutao.SourceGeneration.Automation; - -[Generator(LanguageNames.CSharp)] -internal sealed class SaltConstantGenerator : IIncrementalGenerator -{ - private static readonly HttpClient httpClient; - private static readonly Lazy> lazySaltInfo; - - static SaltConstantGenerator() - { - httpClient = new(); - lazySaltInfo = new Lazy>(() => - { - string body = httpClient.GetStringAsync("https://internal.snapgenshin.cn/Archive/Salt/Latest").GetAwaiter().GetResult(); - return JsonParser.FromJson>(body)!; - }); - } - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterPostInitializationOutput(GenerateSaltContstants); - } - - private static void GenerateSaltContstants(IncrementalGeneratorPostInitializationContext context) - { - Response saltInfo = lazySaltInfo.Value; - string code = $$""" - namespace Snap.Hutao.Web.Hoyolab; - - internal sealed class SaltConstants - { - public const string CNVersion = "{{saltInfo.Data.CNVersion}}"; - public const string CNK2 = "{{saltInfo.Data.CNK2}}"; - public const string CNLK2 = "{{saltInfo.Data.CNLK2}}"; - - public const string OSVersion = "{{saltInfo.Data.OSVersion}}"; - public const string OSK2 = "{{saltInfo.Data.OSK2}}"; - public const string OSLK2 = "{{saltInfo.Data.OSLK2}}"; - } - """; - context.AddSource("SaltConstants.g.cs", code); - } - - private sealed class Response - { - [DataMember(Name = "data")] - public T Data { get; set; } = default!; - } - - internal sealed class SaltLatest - { - public string CNVersion { get; set; } = default!; - - public string CNK2 { get; set; } = default!; - - public string CNLK2 { get; set; } = default!; - - public string OSVersion { get; set; } = default!; - - public string OSK2 { get; set; } = default!; - - public string OSLK2 { get; set; } = default!; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs deleted file mode 100644 index 3a2eb152..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace System.Diagnostics.CodeAnalysis; - -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) - { - ReturnValue = returnValue; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs deleted file mode 100644 index 5df9b4c1..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/HttpClientGenerator.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.DependencyInjection; - -[Generator(LanguageNames.CSharp)] -internal sealed class HttpClientGenerator : IIncrementalGenerator -{ - private const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientAttribute"; - - private const string HttpClientConfiguration = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration."; - private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute"; - private const string CRLF = "\r\n"; - - private static readonly DiagnosticDescriptor injectionShouldOmitDescriptor = new("SH201", "Injection 特性可以省略", "HttpClient 特性已将 {0} 注册为 Transient 服务", "Quality", DiagnosticSeverity.Warning, true); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider> injectionClasses = context.SyntaxProvider - .CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass) - .Where(GeneratorSyntaxContext2.NotNull) - .Collect(); - - context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddHttpClientsImplementation); - } - - private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token) - { - return node is ClassDeclarationSyntax classDeclarationSyntax - && classDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol)) - { - ImmutableArray attributes = classSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, classSymbol, attributes); - } - } - - return default; - } - - private static void GenerateAddHttpClientsImplementation(SourceProductionContext context, ImmutableArray context2s) - { - StringBuilder sourceBuilder = new StringBuilder().Append($$""" - // Copyright (c) DGP Studio. All rights reserved. - // Licensed under the MIT license. - - using System.Net.Http; - - namespace Snap.Hutao.Core.DependencyInjection; - - internal static partial class IocHttpClientConfiguration - { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(HttpClientGenerator)}}", "1.0.0.0")] - public static partial IServiceCollection AddHttpClients(this IServiceCollection services) - { - """); - - FillUpWithAddHttpClient(sourceBuilder, context, context2s); - - sourceBuilder.Append(""" - - return services; - } - } - """); - - context.AddSource("IocHttpClientConfiguration.g.cs", sourceBuilder.ToString()); - } - - private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts) - { - List lines = []; - StringBuilder lineBuilder = new(); - - foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) - { - if (context.SingleOrDefaultAttribute(InjectionGenerator.AttributeName) is AttributeData injectionData) - { - if (injectionData.ConstructorArguments[0].ToCSharpString() == InjectionGenerator.InjectAsTransientName) - { - if (injectionData.ConstructorArguments.Length < 2) - { - production.ReportDiagnostic(Diagnostic.Create(injectionShouldOmitDescriptor, context.Context.Node.GetLocation(), context.Context.Node)); - } - } - } - - lineBuilder.Clear().Append(CRLF); - lineBuilder.Append(@" services.AddHttpClient<"); - - AttributeData httpClientData = context.SingleAttribute(AttributeName); - ImmutableArray arguments = httpClientData.ConstructorArguments; - - if (arguments.Length == 2) - { - lineBuilder.Append($"{arguments[1].Value}, "); - } - - lineBuilder.Append($"{context.Symbol.ToDisplayString()}>("); - lineBuilder.Append(arguments[0].ToCSharpString().Substring(HttpClientConfiguration.Length)).Append("Configuration)"); - - if (context.SingleOrDefaultAttribute(PrimaryHttpMessageHandlerAttributeName) is AttributeData handlerData) - { - ImmutableArray> properties = handlerData.NamedArguments; - lineBuilder.Append(@".ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() {"); - - foreach (KeyValuePair property in properties) - { - lineBuilder.Append(' '); - lineBuilder.Append(property.Key); - lineBuilder.Append(" = "); - lineBuilder.Append(property.Value.ToCSharpString()); - lineBuilder.Append(','); - } - - lineBuilder.Append(" })"); - } - - lineBuilder.Append(';'); - - lines.Add(lineBuilder.ToString()); - } - - foreach (string line in lines.OrderBy(x => x)) - { - sourceBuilder.Append(line); - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs deleted file mode 100644 index 7aa07a88..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/InjectionGenerator.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.DependencyInjection; - -[Generator(LanguageNames.CSharp)] -internal sealed class InjectionGenerator : IIncrementalGenerator -{ - public const string AttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectionAttribute"; - public const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton"; - public const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient"; - public const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped"; - - private static readonly DiagnosticDescriptor invalidInjectionDescriptor = new("SH101", "无效的 InjectAs 枚举值", "尚未支持生成 {0} 配置", "Quality", DiagnosticSeverity.Error, true); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider> injectionClasses = context.SyntaxProvider - .CreateSyntaxProvider(FilterAttributedClasses, HttpClientClass) - .Where(GeneratorSyntaxContext2.NotNull) - .Collect(); - - context.RegisterImplementationSourceOutput(injectionClasses, GenerateAddInjectionsImplementation); - } - - private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token) - { - return node is ClassDeclarationSyntax classDeclarationSyntax - && classDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 HttpClientClass(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? classSymbol)) - { - ImmutableArray attributes = classSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, classSymbol, attributes); - } - } - - return default; - } - - private static void GenerateAddInjectionsImplementation(SourceProductionContext context, ImmutableArray context2s) - { - StringBuilder sourceBuilder = new StringBuilder().Append($$""" - // Copyright (c) DGP Studio. All rights reserved. - // Licensed under the MIT license. - - namespace Snap.Hutao.Core.DependencyInjection; - - internal static partial class ServiceCollectionExtension - { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(InjectionGenerator)}}", "1.0.0.0")] - public static partial IServiceCollection AddInjections(this IServiceCollection services) - { - """); - - FillUpWithAddServices(sourceBuilder, context, context2s); - sourceBuilder.Append(""" - - return services; - } - } - """); - - context.AddSource("ServiceCollectionExtension.g.cs", sourceBuilder.ToString()); - } - - private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray contexts) - { - List lines = []; - StringBuilder lineBuilder = new(); - - foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString())) - { - lineBuilder.Clear().AppendLine(); - - AttributeData injectionInfo = context.SingleAttribute(AttributeName); - ImmutableArray arguments = injectionInfo.ConstructorArguments; - - string injectAsName = arguments[0].ToCSharpString(); - - bool hasKey = injectionInfo.TryGetNamedArgumentValue("Key", out TypedConstant key); - - switch (injectAsName, hasKey) - { - case (InjectAsSingletonName, false): - lineBuilder.Append(" services.AddSingleton<"); - break; - case (InjectAsSingletonName, true): - lineBuilder.Append(" services.AddKeyedSingleton<"); - break; - case (InjectAsTransientName, false): - lineBuilder.Append(" services.AddTransient<"); - break; - case (InjectAsTransientName, true): - lineBuilder.Append(" services.AddKeyedTransient<"); - break; - case (InjectAsScopedName, false): - lineBuilder.Append(" services.AddScoped<"); - break; - case (InjectAsScopedName, true): - lineBuilder.Append(" services.AddKeyedScoped<"); - break; - default: - production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName)); - break; - } - - if (arguments.Length == 2) - { - lineBuilder.Append($"{arguments[1].Value}, "); - } - - if (hasKey) - { - lineBuilder.Append($"{context.Symbol.ToDisplayString()}>({key.ToCSharpString()});"); - } - else - { - lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();"); - } - - lines.Add(lineBuilder.ToString()); - } - - foreach (string line in lines.OrderBy(x => x)) - { - sourceBuilder.Append(line); - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/ServiceAnalyzer.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/ServiceAnalyzer.cs deleted file mode 100644 index f655f151..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/DependencyInjection/ServiceAnalyzer.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Immutable; -using System.Linq; - -namespace Snap.Hutao.SourceGeneration.DependencyInjection; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -internal class ServiceAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor NonSingletonUseServiceProviderDescriptor = new("SH301", "Non Singleton service should avoid direct use of IServiceProvider", "Non Singleton service should avoid direct use of IServiceProvider", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor SingletonServiceCaptureNonSingletonServiceDescriptor = new("SH302", "Singleton service should avoid keep reference of non singleton service", "Singleton service should avoid keep reference of non singleton service", "Quality", DiagnosticSeverity.Info, true); - - public override ImmutableArray SupportedDiagnostics - { - get => new DiagnosticDescriptor[] - { - NonSingletonUseServiceProviderDescriptor, - SingletonServiceCaptureNonSingletonServiceDescriptor, - }.ToImmutableArray(); - } - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterCompilationStartAction(CompilationStart); - } - - private static void CompilationStart(CompilationStartAnalysisContext context) - { - context.RegisterSyntaxNodeAction(HandleNonSingletonUseServiceProvider, SyntaxKind.ClassDeclaration); - context.RegisterSyntaxNodeAction(HandleSingletonServiceCaptureNonSingletonService, SyntaxKind.ClassDeclaration); - } - - private static void HandleNonSingletonUseServiceProvider(SyntaxNodeAnalysisContext context) - { - ClassDeclarationSyntax classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; - if (classDeclarationSyntax.HasAttributeLists()) - { - INamedTypeSymbol? classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); - if (classSymbol is not null) - { - foreach (AttributeData attributeData in classSymbol.GetAttributes()) - { - if (attributeData.AttributeClass!.ToDisplayString() is InjectionGenerator.AttributeName) - { - string serviceType = attributeData.ConstructorArguments[0].ToCSharpString(); - if (serviceType is InjectionGenerator.InjectAsTransientName or InjectionGenerator.InjectAsScopedName) - { - HandleNonSingletonUseServiceProviderActual(context, classSymbol); - } - } - } - } - } - } - - private static void HandleNonSingletonUseServiceProviderActual(SyntaxNodeAnalysisContext context, INamedTypeSymbol classSymbol) - { - ISymbol? symbol = classSymbol.GetMembers().Where(m => m is IFieldSymbol f && f.Type.ToDisplayString() == "System.IServiceProvider").SingleOrDefault(); - - if (symbol is not null) - { - Diagnostic diagnostic = Diagnostic.Create(NonSingletonUseServiceProviderDescriptor, symbol.Locations.FirstOrDefault()); - context.ReportDiagnostic(diagnostic); - } - } - - private static void HandleSingletonServiceCaptureNonSingletonService(SyntaxNodeAnalysisContext context) - { - //classSymbol.GetMembers().Where(m => m is IFieldSymbol { IsReadOnly: true, DeclaredAccessibility: Accessibility.Private } f); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs deleted file mode 100644 index c8a4f07e..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Enum/LocalizedEnumGenerator.cs +++ /dev/null @@ -1,136 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Snap.Hutao.SourceGeneration.Enum; - -[Generator(LanguageNames.CSharp)] -internal class LocalizedEnumGenerator : IIncrementalGenerator -{ - private const string AttributeName = "Snap.Hutao.Resource.Localization.LocalizationAttribute"; - private const string LocalizationKeyName = "Snap.Hutao.Resource.Localization.LocalizationKeyAttribute"; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValuesProvider localizationEnums = context.SyntaxProvider - .CreateSyntaxProvider(FilterAttributedEnums, LocalizationEnum) - .Where(GeneratorSyntaxContext2.NotNull); - - context.RegisterSourceOutput(localizationEnums, GenerateGetLocalizedDescriptionImplementation); - } - - private static bool FilterAttributedEnums(SyntaxNode node, CancellationToken token) - { - return node is EnumDeclarationSyntax enumDeclarationSyntax - && enumDeclarationSyntax.HasAttributeLists(); - } - - private static GeneratorSyntaxContext2 LocalizationEnum(GeneratorSyntaxContext context, CancellationToken token) - { - if (context.SemanticModel.GetDeclaredSymbol(context.Node, token) is INamedTypeSymbol enumSymbol) - { - ImmutableArray attributes = enumSymbol.GetAttributes(); - if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName)) - { - return new(context, enumSymbol, attributes); - } - } - - return default; - } - - private static void GenerateGetLocalizedDescriptionImplementation(SourceProductionContext context, GeneratorSyntaxContext2 context2) - { - StringBuilder sourceBuilder = new StringBuilder().Append($$""" - // Copyright (c) DGP Studio. All rights reserved. - // Licensed under the MIT license. - - using System.Globalization; - - namespace Snap.Hutao.Resource.Localization; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(LocalizedEnumGenerator)}}", "1.0.0.0")] - internal static class {{context2.Symbol.Name}}Extension - { - /// - /// 获取本地化的描述 - /// - /// 枚举值 - /// 本地化的描述 - public static string GetLocalizedDescription(this {{context2.Symbol}} value) - { - string key = value switch - { - - """); - - FillUpWithSwitchBranches(sourceBuilder, context2); - - sourceBuilder.Append($$""" - _ => string.Empty, - }; - - if (string.IsNullOrEmpty(key)) - { - return Enum.GetName(value); - } - else - { - return SH.ResourceManager.GetString(key, CultureInfo.CurrentCulture); - } - } - - /// - /// 获取本地化的描述 - /// - /// 枚举值 - /// 本地化的描述 - [return:MaybeNull] - public static string GetLocalizedDescriptionOrDefault(this {{context2.Symbol}} value) - { - string key = value switch - { - - """); - - FillUpWithSwitchBranches(sourceBuilder, context2); - - sourceBuilder.Append($$""" - _ => string.Empty, - }; - - return SH.ResourceManager.GetString(key, CultureInfo.CurrentCulture); - } - } - """); - - context.AddSource($"{context2.Symbol.Name}Extension.g.cs", sourceBuilder.ToString()); - } - - private static void FillUpWithSwitchBranches(StringBuilder sourceBuilder, GeneratorSyntaxContext2 context) - { - IEnumerable fields = context.Symbol.GetMembers() - .Where(m => m.Kind == SymbolKind.Field) - .Cast(); - - foreach (IFieldSymbol fieldSymbol in fields) - { - AttributeData? localizationKeyInfo = fieldSymbol.GetAttributes() - .SingleOrDefault(data => data.AttributeClass!.ToDisplayString() == LocalizationKeyName); - if (localizationKeyInfo != null) - { - sourceBuilder - .Append(" ") - .Append(fieldSymbol) - .Append(" => \"") - .Append(localizationKeyInfo.ConstructorArguments[0].Value) - .AppendLine("\","); - } - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs deleted file mode 100644 index 7d458146..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("", "RS2008")] diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs deleted file mode 100644 index a3440e94..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs +++ /dev/null @@ -1,202 +0,0 @@ -using Microsoft.CodeAnalysis; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Text; - -namespace Snap.Hutao.SourceGeneration.Identity; - -[Generator(LanguageNames.CSharp)] -internal sealed class IdentityGenerator : IIncrementalGenerator -{ - private const string FileName = "IdentityStructs.json"; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider> provider = context.AdditionalTextsProvider.Where(MatchFileName).Collect(); - - context.RegisterImplementationSourceOutput(provider, GenerateIdentityStructs); - } - - private static bool MatchFileName(AdditionalText text) - { - return Path.GetFileName(text.Path) == FileName; - } - - private static void GenerateIdentityStructs(SourceProductionContext context, ImmutableArray texts) - { - AdditionalText jsonFile = texts.Single(); - - string identityJson = jsonFile.GetText(context.CancellationToken)!.ToString(); - List identities = identityJson.FromJson>()!; - - if (identities.Any()) - { - foreach (IdentityStructMetadata identityStruct in identities) - { - GenerateIdentityStruct(context, identityStruct); - } - } - } - - private static void GenerateIdentityStruct(SourceProductionContext context, IdentityStructMetadata metadata) - { - string name = metadata.Name; - - StringBuilder sourceBuilder = new StringBuilder().AppendLine($$""" - // Copyright (c) DGP Studio. All rights reserved. - // Licensed under the MIT license. - - using Snap.Hutao.Model.Primitive.Converter; - using System.Numerics; - - namespace Snap.Hutao.Model.Primitive; - - /// - /// {{metadata.Documentation}} - /// - [JsonConverter(typeof(IdentityConverter<{{name}}>))] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(IdentityGenerator)}}","1.0.0.0")] - internal readonly partial struct {{name}} - { - /// - /// 值 - /// - public readonly uint Value; - - /// - /// Initializes a new instance of the struct. - /// - /// value - public {{name}}(uint value) - { - Value = value; - } - - public static implicit operator uint({{name}} value) - { - return value.Value; - } - - public static implicit operator {{name}}(uint value) - { - return new(value); - } - - /// - public override int GetHashCode() - { - return Value.GetHashCode(); - } - - /// - public override string ToString() - { - return Value.ToString(); - } - } - """); - - if (metadata.Equatable) - { - sourceBuilder.AppendLine($$""" - - internal readonly partial struct {{name}} : IEquatable<{{name}}> - { - /// - public override bool Equals(object obj) - { - return obj is {{name}} other && Equals(other); - } - - /// - public bool Equals({{name}} other) - { - return Value == other.Value; - } - } - """); - } - - if (metadata.EqualityOperators) - { - sourceBuilder.AppendLine($$""" - - internal readonly partial struct {{name}} : IEqualityOperators<{{name}}, {{name}}, bool>, IEqualityOperators<{{name}}, uint, bool> - { - public static bool operator ==({{name}} left, {{name}} right) - { - return left.Value == right.Value; - } - - public static bool operator ==({{name}} left, uint right) - { - return left.Value == right; - } - - public static bool operator !=({{name}} left, {{name}} right) - { - return !(left == right); - } - - public static bool operator !=({{name}} left, uint right) - { - return !(left == right); - } - } - """); - } - - if (metadata.AdditionOperators) - { - sourceBuilder.AppendLine($$""" - - internal readonly partial struct {{name}} : IAdditionOperators<{{name}}, {{name}}, {{name}}>, IAdditionOperators<{{name}}, uint, {{name}}> - { - public static {{name}} operator +({{name}} left, {{name}} right) - { - return left.Value + right.Value; - } - - public static {{name}} operator +({{name}} left, uint right) - { - return left.Value + right; - } - } - """); - } - - if (metadata.IncrementOperators) - { - sourceBuilder.AppendLine($$""" - - internal readonly partial struct {{name}} : IIncrementOperators<{{name}}> - { - public static unsafe {{name}} operator ++({{name}} value) - { - ++*(uint*)&value; - return value; - } - } - """); - } - - context.AddSource($"{name}.g.cs", sourceBuilder.ToString()); - } - - private sealed class IdentityStructMetadata - { - public string Name { get; set; } = default!; - - public string? Documentation { get; set; } - - public bool Equatable { get; set; } - - public bool EqualityOperators { get; set; } - - public bool AdditionOperators { get; set; } - - public bool IncrementOperators { get; set; } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs deleted file mode 100644 index 24d06d4e..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/JsonParser.cs +++ /dev/null @@ -1,439 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; - -namespace Snap.Hutao.SourceGeneration; - -// Really simple JSON parser in ~300 lines -// - Attempts to parse JSON files with minimal GC allocation -// - Nice and simple "[1,2,3]".FromJson>() API -// - Classes and structs can be parsed too! -// class Foo { public int Value; } -// "{\"Value\":10}".FromJson() -// - Can parse JSON without type information into Dictionary and List e.g. -// "[1,2,3]".FromJson().GetType() == typeof(List) -// "{\"Value\":10}".FromJson().GetType() == typeof(Dictionary) -// - No JIT Emit support to support AOT compilation on iOS -// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead. -// - Only public fields and property setters on classes/structs will be written to -// -// Limitations: -// - No JIT Emit support to parse structures quickly -// - Limited to parsing <2GB JSON files (due to int.MaxValue) -// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception. -public static class JsonParser -{ - [ThreadStatic] - private static Stack>? splitArrayPool; - - [ThreadStatic] - private static StringBuilder? stringBuilder; - - [ThreadStatic] - private static Dictionary>? fieldInfoCache; - - [ThreadStatic] - private static Dictionary>? propertyInfoCache; - - public static T? FromJson(this string json) - { - // Initialize, if needed, the ThreadStatic variables - propertyInfoCache ??= []; - fieldInfoCache ??= []; - stringBuilder ??= new(); - splitArrayPool ??= []; - - // Remove all whitespace not within strings to make parsing simpler - stringBuilder.Length = 0; - for (int i = 0; i < json.Length; i++) - { - char c = json[i]; - if (c == '"') - { - i = AppendUntilStringEnd(true, i, json); - continue; - } - if (char.IsWhiteSpace(c)) - { - continue; - } - - stringBuilder.Append(c); - } - - // Parse the thing! - return (T?)ParseValue(typeof(T), stringBuilder.ToString()); - } - - private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json) - { - stringBuilder!.Append(json[startIdx]); - for (int i = startIdx + 1; i < json.Length; i++) - { - if (json[i] == '\\') - { - if (appendEscapeCharacter) - { - stringBuilder.Append(json[i]); - } - - stringBuilder.Append(json[i + 1]); - i++;//Skip next character as it is escaped - } - else if (json[i] == '"') - { - stringBuilder.Append(json[i]); - return i; - } - else - { - stringBuilder.Append(json[i]); - } - } - return json.Length - 1; - } - - // Splits { :, : } and [ , ] into a list of strings - private static List Split(string json) - { - List splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : []; - splitArray.Clear(); - if (json.Length == 2) - { - return splitArray; - } - - int parseDepth = 0; - stringBuilder!.Length = 0; - for (int i = 1; i < json.Length - 1; i++) - { - switch (json[i]) - { - case '[': - case '{': - parseDepth++; - break; - case ']': - case '}': - parseDepth--; - break; - case '"': - i = AppendUntilStringEnd(true, i, json); - continue; - case ',': - case ':': - if (parseDepth == 0) - { - splitArray.Add(stringBuilder.ToString()); - stringBuilder.Length = 0; - continue; - } - break; - } - - stringBuilder.Append(json[i]); - } - - splitArray.Add(stringBuilder.ToString()); - - return splitArray; - } - - internal static object? ParseValue(Type type, string json) - { - if (type == typeof(string)) - { - if (json.Length <= 2) - { - return string.Empty; - } - - StringBuilder parseStringBuilder = new(json.Length); - for (int i = 1; i < json.Length - 1; ++i) - { - if (json[i] == '\\' && i + 1 < json.Length - 1) - { - int j = "\"\\nrtbf/".IndexOf(json[i + 1]); - if (j >= 0) - { - parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]); - ++i; - continue; - } - if (json[i + 1] == 'u' && i + 5 < json.Length - 1) - { - if (uint.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c)) - { - parseStringBuilder.Append((char)c); - i += 5; - continue; - } - } - } - parseStringBuilder.Append(json[i]); - } - return parseStringBuilder.ToString(); - } - if (type.IsPrimitive) - { - object result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture); - return result; - } - if (type == typeof(decimal)) - { - decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result); - return result; - } - if (type == typeof(DateTime)) - { - DateTime.TryParse(json.Replace("\"", ""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime result); - return result; - } - if (json == "null") - { - return null; - } - if (type.IsEnum) - { - if (json[0] == '"') - { - json = json.Substring(1, json.Length - 2); - } - - try - { - return System.Enum.Parse(type, json, false); - } - catch - { - return 0; - } - } - if (type.IsArray) - { - Type arrayType = type.GetElementType(); - if (json[0] != '[' || json[json.Length - 1] != ']') - { - return null; - } - - List elems = Split(json); - Array newArray = Array.CreateInstance(arrayType, elems.Count); - for (int i = 0; i < elems.Count; i++) - { - newArray.SetValue(ParseValue(arrayType, elems[i]), i); - } - - splitArrayPool!.Push(elems); - return newArray; - } - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) - { - Type listType = type.GetGenericArguments()[0]; - if (json[0] != '[' || json[json.Length - 1] != ']') - { - return null; - } - - List elems = Split(json); - IList list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count }); - for (int i = 0; i < elems.Count; i++) - { - list.Add(ParseValue(listType, elems[i])); - } - - splitArrayPool!.Push(elems); - return list; - } - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - Type keyType, valueType; - { - Type[] args = type.GetGenericArguments(); - keyType = args[0]; - valueType = args[1]; - } - - //Refuse to parse dictionary keys that aren't of type string - if (keyType != typeof(string)) - { - return null; - } - //Must be a valid dictionary element - if (json[0] != '{' || json[json.Length - 1] != '}') - { - return null; - } - //The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return null; - } - - IDictionary dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 }); - for (int i = 0; i < elems.Count; i += 2) - { - if (elems[i].Length <= 2) - { - continue; - } - - string keyValue = elems[i].Substring(1, elems[i].Length - 2); - object? val = ParseValue(valueType, elems[i + 1]); - dictionary[keyValue] = val; - } - return dictionary; - } - if (type == typeof(object)) - { - return ParseAnonymousValue(json); - } - if (json[0] == '{' && json[json.Length - 1] == '}') - { - return ParseObject(type, json); - } - - return null; - } - - private static object? ParseAnonymousValue(string json) - { - if (json.Length == 0) - { - return null; - } - - if (json[0] == '{' && json[json.Length - 1] == '}') - { - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return null; - } - - Dictionary dict = new(elems.Count / 2); - for (int i = 0; i < elems.Count; i += 2) - { - dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]); - } - - return dict; - } - if (json[0] == '[' && json[json.Length - 1] == ']') - { - List items = Split(json); - List finalList = new(items.Count); - for (int i = 0; i < items.Count; i++) - { - finalList.Add(ParseAnonymousValue(items[i])); - } - - return finalList; - } - if (json[0] == '"' && json[json.Length - 1] == '"') - { - string str = json.Substring(1, json.Length - 2); - return str.Replace("\\", string.Empty); - } - if (char.IsDigit(json[0]) || json[0] == '-') - { - if (json.Contains(".")) - { - double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result); - return result; - } - else - { - int.TryParse(json, out int result); - return result; - } - } - if (json == "true") - { - return true; - } - - if (json == "false") - { - return false; - } - // handles json == "null" as well as invalid JSON - return null; - } - - private static Dictionary CreateMemberNameDictionary(T[] members) where T : MemberInfo - { - Dictionary nameToMember = new(StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < members.Length; i++) - { - T member = members[i]; - if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true)) - { - continue; - } - - string name = member.Name; - if (member.IsDefined(typeof(DataMemberAttribute), true)) - { - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true); - if (!string.IsNullOrEmpty(dataMemberAttribute.Name)) - { - name = dataMemberAttribute.Name; - } - } - - nameToMember.Add(name, member); - } - - return nameToMember; - } - - private static object ParseObject(Type type, string json) - { - object instance = FormatterServices.GetUninitializedObject(type); - - // The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return instance; - } - - if (!fieldInfoCache!.TryGetValue(type, out Dictionary nameToField)) - { - nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); - fieldInfoCache.Add(type, nameToField); - } - if (!propertyInfoCache!.TryGetValue(type, out Dictionary nameToProperty)) - { - nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); - propertyInfoCache.Add(type, nameToProperty); - } - - for (int i = 0; i < elems.Count; i += 2) - { - if (elems[i].Length <= 2) - { - continue; - } - - string key = elems[i].Substring(1, elems[i].Length - 2); - string value = elems[i + 1]; - - if (nameToField.TryGetValue(key, out FieldInfo fieldInfo)) - { - fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value)); - } - else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo)) - { - propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null); - } - } - - return instance; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs deleted file mode 100644 index 98496f2f..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/AttributeDataExtension.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class AttributeDataExtension -{ - public static bool HasNamedArgumentWith(this AttributeData data, string key, Func predicate) - { - return data.NamedArguments.Any(a => a.Key == key && predicate((TValue)a.Value.Value!)); - } - - public static bool TryGetNamedArgumentValue(this AttributeData data, string key, out TypedConstant value) - { - foreach (KeyValuePair pair in data.NamedArguments) - { - if (pair.Key == key) - { - value = pair.Value; - return true; - } - } - - value = default; - return false; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs deleted file mode 100644 index 544504a3..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/EnumerableExtension.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class EnumerableExtension -{ - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) - { - return DistinctByIterator(source, keySelector); - } - - private static IEnumerable DistinctByIterator(IEnumerable source, Func keySelector) - { - using IEnumerator enumerator = source.GetEnumerator(); - - if (enumerator.MoveNext()) - { - HashSet set = []; - - do - { - TSource element = enumerator.Current; - if (set.Add(keySelector(element))) - { - yield return element; - } - } - while (enumerator.MoveNext()); - } - } - - public static Dictionary ToDictionary(this IEnumerable> source) - { - return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs deleted file mode 100644 index 16a6be19..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContext2.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using System.Collections.Immutable; -using System.Linq; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal readonly struct GeneratorSyntaxContext2 -{ - public readonly GeneratorSyntaxContext Context; - public readonly INamedTypeSymbol Symbol; - public readonly ImmutableArray Attributes; - public readonly bool HasValue = false; - - public GeneratorSyntaxContext2(GeneratorSyntaxContext context, INamedTypeSymbol symbol, ImmutableArray attributes) - { - Context = context; - Symbol = symbol; - Attributes = attributes; - HasValue = true; - } - - public static bool NotNull(GeneratorSyntaxContext2 context) - { - return context.HasValue; - } - - public bool HasAttributeWithName(string name) - { - return Attributes.Any(attr => attr.AttributeClass!.ToDisplayString() == name); - } - - public AttributeData SingleAttribute(string name) - { - return Attributes.Single(attribute => attribute.AttributeClass!.ToDisplayString() == name); - } - - public AttributeData? SingleOrDefaultAttribute(string name) - { - return Attributes.SingleOrDefault(attribute => attribute.AttributeClass!.ToDisplayString() == name); - } - - public TSyntaxNode Node() - where TSyntaxNode : SyntaxNode - { - return (TSyntaxNode)Context.Node; - } -} - -internal readonly struct GeneratorSyntaxContext2 - where TSymbol : ISymbol -{ - public readonly GeneratorSyntaxContext Context; - public readonly TSymbol Symbol; - public readonly ImmutableArray Attributes; - public readonly bool HasValue = false; - - public GeneratorSyntaxContext2(GeneratorSyntaxContext context, TSymbol symbol, ImmutableArray attributes) - { - Context = context; - Symbol = symbol; - Attributes = attributes; - HasValue = true; - } - - public static bool NotNull(GeneratorSyntaxContext2 context) - { - return context.HasValue; - } - - public AttributeData SingleAttribute(string name) - { - return Attributes.Single(attribute => attribute.AttributeClass!.ToDisplayString() == name); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContextExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContextExtension.cs deleted file mode 100644 index 216f3ff7..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/GeneratorSyntaxContextExtension.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using System.Diagnostics.CodeAnalysis; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class GeneratorSyntaxContextExtension -{ - public static bool TryGetDeclaredSymbol(this GeneratorSyntaxContext context, System.Threading.CancellationToken token, [NotNullWhen(true)] out TSymbol? symbol) - where TSymbol : class, ISymbol - { - symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, token) as TSymbol; - return symbol != null; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SymbolDisplayFormats.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SymbolDisplayFormats.cs deleted file mode 100644 index 16c5d008..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SymbolDisplayFormats.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class SymbolDisplayFormats -{ - public static SymbolDisplayFormat FullyQualifiedNonNullableFormat { get; } = new( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - - public static SymbolDisplayFormat QualifiedNonNullableFormat { get; } = new( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SyntaxExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SyntaxExtension.cs deleted file mode 100644 index 02e058d8..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/SyntaxExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class SyntaxExtension -{ - /// - /// Checks whether a given has or could potentially have any attribute lists. - /// - /// The input to check. - /// Whether has or potentially has any attribute lists. - public static bool HasAttributeLists(this TSyntax declaration) - where TSyntax : MemberDeclarationSyntax - { - return declaration.AttributeLists.Count > 0; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/TypeSymbolExtension.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/TypeSymbolExtension.cs deleted file mode 100644 index 73e7959d..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Primitive/TypeSymbolExtension.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Snap.Hutao.SourceGeneration.Primitive; - -internal static class TypeSymbolExtension -{ - /// - /// Checks whether or not a given has or inherits from a specified type. - /// - /// The target instance to check. - /// The full name of the type to check for inheritance. - /// Whether or not is or inherits from . - public static bool IsOrInheritsFrom(this ITypeSymbol typeSymbol, string name) - { - for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType) - { - if (currentType.ToDisplayString() == name) - { - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs deleted file mode 100644 index d9670fca..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs +++ /dev/null @@ -1,629 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using System.Xml.XPath; - -namespace Snap.Hutao.SourceGeneration.Resx; - -[Generator] -public sealed class ResxGenerator : IIncrementalGenerator -{ - private static readonly DiagnosticDescriptor InvalidResx = new("SH401", "Couldn't parse Resx file", "Couldn't parse Resx file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true); - private static readonly DiagnosticDescriptor InvalidPropertiesForNamespace = new("SH402", "Couldn't compute namespace", "Couldn't compute namespace for file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true); - private static readonly DiagnosticDescriptor InvalidPropertiesForResourceName = new("SH403", "Couldn't compute resource name", "Couldn't compute resource name for file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true); - private static readonly DiagnosticDescriptor InconsistentProperties = new("SH404", "Inconsistent properties", "Property '{0}' values for '{1}' are inconsistent", "ResxGenerator", DiagnosticSeverity.Warning, true); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValueProvider<(string? AssemblyName, bool SupportNullableReferenceTypes)> compilationProvider = context.CompilationProvider - .Select(static (compilation, cancellationToken) => (compilation.AssemblyName, SupportNullableReferenceTypes: compilation.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute") is not null)); - - IncrementalValueProvider> resxProvider = context.AdditionalTextsProvider - .Where(text => text.Path.EndsWith(".resx", StringComparison.OrdinalIgnoreCase)) - .Collect(); - - context.RegisterSourceOutput( - source: context.AnalyzerConfigOptionsProvider.Combine(compilationProvider.Combine(resxProvider)), - action: (ctx, source) => Execute(ctx, source.Left, source.Right.Left.AssemblyName, source.Right.Left.SupportNullableReferenceTypes, source.Right.Right)); - } - - private static void Execute(SourceProductionContext context, AnalyzerConfigOptionsProvider options, string? assemblyName, bool supportNullableReferenceTypes, ImmutableArray files) - { - // Group additional file by resource kind ((a.resx, a.en.resx, a.en-us.resx), (b.resx, b.en-us.resx)) - IOrderedEnumerable> group = files - .GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase) - .OrderBy(x => x.Key, StringComparer.Ordinal); - List> resxGroups = [.. group]; - - foreach (IGrouping? resxGroup in resxGroups) - { - string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroup); - string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroup); - string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroup); - string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroup); - string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroup); - - string rootNamespace = rootNamespaceConfiguration ?? assemblyName ?? ""; - string projectDir = projectDirConfiguration ?? assemblyName ?? ""; - string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroup.Key); - string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroup.Key); - - string? ns = namespaceConfiguration ?? defaultNamespace; - string? resourceName = resourceNameConfiguration ?? defaultResourceName; - string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroup.Key)); - - if (ns == null) - { - context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroup.First().Path)); - } - - if (resourceName == null) - { - context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroup.First().Path)); - } - - List? entries = LoadResourceFiles(context, resxGroup); - - string content = $""" - // Debug info: - // key: {resxGroup.Key} - // files: {string.Join(", ", resxGroup.Select(f => f.Path))} - // RootNamespace (metadata): {rootNamespaceConfiguration} - // ProjectDir (metadata): {projectDirConfiguration} - // Namespace / DefaultResourcesNamespace (metadata): {namespaceConfiguration} - // ResourceName (metadata): {resourceNameConfiguration} - // ClassName (metadata): {classNameConfiguration} - // AssemblyName: {assemblyName} - // RootNamespace (computed): {rootNamespace} - // ProjectDir (computed): {projectDir} - // defaultNamespace: {defaultNamespace} - // defaultResourceName: {defaultResourceName} - // Namespace: {ns} - // ResourceName: {resourceName} - // ClassName: {className} - """; - - if (resourceName != null && entries != null) - { - content += GenerateCode(ns, className, resourceName, entries, supportNullableReferenceTypes); - } - - context.AddSource($"{Path.GetFileName(resxGroup.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8)); - } - } - - private static string GenerateCode(string? ns, string className, string resourceName, List entries, bool enableNullableAttributes) - { - StringBuilder sb = new(); - sb.AppendLine(); - sb.AppendLine("#nullable enable"); - - if (ns != null) - { - sb.AppendLine($$""" - - namespace {{ns}}; - - """); - } - - sb.AppendLine($$""" - internal partial class {{className}} - { - private static global::System.Resources.ResourceManager? resourceMan; - - public {{className}}() - { - } - - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (resourceMan is null) - { - resourceMan = new global::System.Resources.ResourceManager("{{resourceName}}", typeof({{className}}).Assembly); - } - - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo? Culture { get; set; } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static object? GetObject(global::System.Globalization.CultureInfo? culture, string name, object? defaultValue) - { - culture ??= Culture; - object? obj = ResourceManager.GetObject(name, culture); - if (obj == null) - { - return defaultValue; - } - - return obj; - } - - public static object? GetObject(global::System.Globalization.CultureInfo? culture, string name) - { - return GetObject(culture: culture, name: name, defaultValue: null); - } - - public static object? GetObject(string name) - { - return GetObject(culture: null, name: name, defaultValue: null); - } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static object? GetObject(string name, object? defaultValue) - { - return GetObject(culture: null, name: name, defaultValue: defaultValue); - } - - public static global::System.IO.Stream? GetStream(string name) - { - return GetStream(culture: null, name: name); - } - - public static global::System.IO.Stream? GetStream(global::System.Globalization.CultureInfo? culture, string name) - { - culture ??= Culture; - return ResourceManager.GetStream(name, culture); - } - - public static string? GetString(global::System.Globalization.CultureInfo? culture, string name) - { - return GetString(culture: culture, name: name, args: null); - } - - public static string? GetString(global::System.Globalization.CultureInfo? culture, string name, params object?[]? args) - { - culture ??= Culture; - string? str = ResourceManager.GetString(name, culture); - if (str == null) - { - return null; - } - - if (args != null) - { - return string.Format(culture, str, args); - } - else - { - return str; - } - } - - public static string? GetString(string name, params object?[]? args) - { - return GetString(culture: null, name: name, args: args); - } - - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static string? GetString(string name, string? defaultValue) - { - return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: null); - } - - public static string? GetString(string name) - { - return GetStringOrDefault(culture: null, name: name, defaultValue: null, args: null); - } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static string? GetStringOrDefault(global::System.Globalization.CultureInfo? culture, string name, string? defaultValue) - { - return GetStringOrDefault(culture: culture, name: name, defaultValue: defaultValue, args: null); - } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static string? GetStringOrDefault(global::System.Globalization.CultureInfo? culture, string name, string? defaultValue, params object?[]? args) - { - culture ??= Culture; - string? str = ResourceManager.GetString(name, culture); - if (str == null) - { - if (defaultValue == null || args == null) - { - return defaultValue; - } - else - { - return string.Format(culture, defaultValue, args); - } - } - - if (args != null) - { - return string.Format(culture, str, args); - } - else - { - return str; - } - } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static string? GetStringOrDefault(string name, string? defaultValue, params object?[]? args) - { - return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: args); - } - - [return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")] - public static string? GetStringOrDefault(string name, string? defaultValue) - { - return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: null); - } - """); - - foreach (ResxEntry? entry in entries.OrderBy(e => e.Name, StringComparer.Ordinal)) - { - if (string.IsNullOrEmpty(entry.Name)) - { - continue; - } - - if (entry.IsText) - { - XElement summary = new("summary", new XElement("para", $"Looks up a localized string for \"{entry.Name}\".")); - if (!string.IsNullOrWhiteSpace(entry.Comment)) - { - summary.Add(new XElement("para", entry.Comment)); - } - - if (!entry.IsFileRef) - { - foreach((string? each, string locale) in entry.Values.Zip(entry.Locales,(x,y)=>(x,y))) - { - summary.Add(new XElement("para", $"{GetStringWithPadding(locale, 8)} Value: \"{each}\"")); - } - } - - string comment = summary.ToString().Replace("\r\n", "\r\n /// ", StringComparison.Ordinal); - - sb.AppendLine($$""" - /// {{comment}} - public static string {{ToCSharpNameIdentifier(entry.Name!)}} - { - get => GetString("{{entry.Name}}")!; - } - - """); - - if (entry.Values.FirstOrDefault() is string value) - { - int args = Regex.Matches(value, "\\{(?[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant) - .Cast() - .Select(m => int.Parse(m.Groups["num"].Value, CultureInfo.InvariantCulture)) - .Distinct() - .DefaultIfEmpty(-1) - .Max(); - - if (args >= 0) - { - string inParams = string.Join(", ", Enumerable.Range(0, args + 1).Select(arg => "object? arg" + arg.ToString(CultureInfo.InvariantCulture))); - string callParams = string.Join(", ", Enumerable.Range(0, args + 1).Select(arg => "arg" + arg.ToString(CultureInfo.InvariantCulture))); - - sb.AppendLine($$""" - /// {{comment}} - public static string Format{{ToCSharpNameIdentifier(entry.Name!)}}({{inParams}}) - { - return GetString("{{entry.Name}}", {{callParams}})!; - } - - """); - } - } - } - else - { - sb.AppendLine($$""" - public static global::{{entry.FullTypeName}}? {{ToCSharpNameIdentifier(entry.Name!)}} - { - get => (global::{{entry.FullTypeName}}?)GetObject("{{entry.Name}}"); - } - - """); - } - } - - sb.AppendLine($$""" - } - - internal partial class {{className}}Names - { - """); - - foreach (ResxEntry entry in entries) - { - if (string.IsNullOrEmpty(entry.Name)) - { - continue; - } - - sb.AppendLine($$""" - public const string {{ToCSharpNameIdentifier(entry.Name!)}} = "entry.Name"; - """); - } - - sb.AppendLine("}"); - - return sb.ToString(); - } - - private static string GetStringWithPadding(string source, int length) - { - if (source.Length >= length) - { - return source; - } - - return source + new string('_', length - source.Length); - } - - private static string? ComputeResourceName(string rootNamespace, string projectDir, string resourcePath) - { - string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir)); - string fullResourcePath = Path.GetFullPath(resourcePath); - - if (fullProjectDir == fullResourcePath) - { - return rootNamespace; - } - - if (fullResourcePath.StartsWith(fullProjectDir, StringComparison.Ordinal)) - { - string relativePath = fullResourcePath.Substring(fullProjectDir.Length); - return rootNamespace + '.' + relativePath.Replace('/', '.').Replace('\\', '.'); - } - - return null; - } - - private static string? ComputeNamespace(string rootNamespace, string projectDir, string resourcePath) - { - string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir)); - string fullResourcePath = EnsureEndSeparator(Path.GetDirectoryName(Path.GetFullPath(resourcePath))!); - - if (fullProjectDir == fullResourcePath) - { - return rootNamespace; - } - - if (fullResourcePath.StartsWith(fullProjectDir, StringComparison.Ordinal)) - { - string relativePath = fullResourcePath.Substring(fullProjectDir.Length); - return rootNamespace + '.' + relativePath.Replace('/', '.').Replace('\\', '.').TrimEnd('.'); - } - - return null; - } - - private static List? LoadResourceFiles(SourceProductionContext context, IGrouping resxGroug) - { - List entries = []; - foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal)) - { - SourceText? content = entry.GetText(context.CancellationToken); - if (content is null) - { - continue; - } - - try - { - XDocument document = XDocument.Parse(content.ToString()); - foreach (XElement? element in document.XPathSelectElements("/root/data")) - { - string? name = element.Attribute("name")?.Value; - string? type = element.Attribute("type")?.Value; - string? comment = element.Attribute("comment")?.Value; - string? value = element.Element("value")?.Value; - - ResxEntry existingEntry = entries.Find(e => e.Name == name); - if (existingEntry != null) - { - existingEntry.Comment ??= comment; - existingEntry.Values.Add(value); - existingEntry.Locales.Add(GetLocaleName(entry.Path)); - } - else - { - entries.Add(new() { Name = name, Values = [value], Locales = [GetLocaleName(entry.Path)], Comment = comment, Type = type }); - } - } - } - catch - { - context.ReportDiagnostic(Diagnostic.Create(InvalidResx, location: null, entry.Path)); - return null; - } - } - - return entries; - } - - private static string? GetMetadataValue(SourceProductionContext context, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, string name, IEnumerable additionalFiles) - { - return GetMetadataValue(context, analyzerConfigOptionsProvider, name, name, additionalFiles); - } - - private static string? GetMetadataValue(SourceProductionContext context, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, string name, string? globalName, IEnumerable additionalFiles) - { - string? result = null; - foreach (AdditionalText file in additionalFiles) - { - if (analyzerConfigOptionsProvider.GetOptions(file).TryGetValue("build_metadata.AdditionalFiles." + name, out string? value)) - { - if (result != null && value != result) - { - context.ReportDiagnostic(Diagnostic.Create(InconsistentProperties, location: null, name, file.Path)); - return null; - } - - result = value; - } - } - - if (!string.IsNullOrEmpty(result)) - { - return result; - } - - if (globalName != null && analyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property." + globalName, out string? globalValue) && !string.IsNullOrEmpty(globalValue)) - { - return globalValue; - } - - return null; - } - - private static string ToCSharpNameIdentifier(string name) - { - // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#identifiers - // https://docs.microsoft.com/en-us/dotnet/api/system.globalization.unicodecategory?view=net-5.0 - StringBuilder sb = new(); - foreach (char c in name) - { - UnicodeCategory category = char.GetUnicodeCategory(c); - switch (category) - { - case UnicodeCategory.UppercaseLetter: - case UnicodeCategory.LowercaseLetter: - case UnicodeCategory.TitlecaseLetter: - case UnicodeCategory.ModifierLetter: - case UnicodeCategory.OtherLetter: - case UnicodeCategory.LetterNumber: - sb.Append(c); - break; - - case UnicodeCategory.DecimalDigitNumber: - case UnicodeCategory.ConnectorPunctuation: - case UnicodeCategory.Format: - if (sb.Length == 0) - { - sb.Append('_'); - } - sb.Append(c); - break; - - default: - sb.Append('_'); - break; - } - } - - return sb.ToString(); - } - - private static string EnsureEndSeparator(string path) - { - if (path[path.Length - 1] == Path.DirectorySeparatorChar) - { - return path; - } - - return path + Path.DirectorySeparatorChar; - } - - private static string GetResourceName(string path) - { - string pathWithoutExtension = Path.Combine(Path.GetDirectoryName(path)!, Path.GetFileNameWithoutExtension(path)); - int indexOf = pathWithoutExtension.LastIndexOf('.'); - if (indexOf < 0) - { - return pathWithoutExtension; - } - - return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2,4})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1)) - ? pathWithoutExtension.Substring(0, indexOf) - : pathWithoutExtension; - } - - private static string GetLocaleName(string path) - { - string fileName = Path.GetFileNameWithoutExtension(path); - int indexOf = fileName.LastIndexOf('.'); - if (indexOf < 0) - { - return "Neutral"; - } - - return fileName.Substring(indexOf + 1); - } - - private sealed class ResxEntry - { - public string? Name { get; set; } - public List Values { get; set; } = default!; - public List Locales { get; set; } = default!; - public string? Comment { get; set; } - public string? Type { get; set; } - - public bool IsText - { - get - { - if (Type == null) - { - return true; - } - - if (Values.FirstOrDefault() is string value) - { - string[] parts = value.Split(';'); - if (parts.Length > 1) - { - string type = parts[1]; - if (type.StartsWith("System.String,", StringComparison.Ordinal)) - { - return true; - } - } - } - - return false; - } - } - - public string? FullTypeName - { - get - { - if (IsText) - { - return "string"; - } - - if (Values.FirstOrDefault() is string value) - { - string[] parts = value.Split(';'); - if (parts.Length > 1) - { - string type = parts[1]; - return type.Split(',')[0]; - } - } - - return null; - } - } - - public bool IsFileRef - { - get => Type != null && Type.StartsWith("System.Resources.ResXFileRef,", StringComparison.Ordinal); - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/StringExtensions.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/StringExtensions.cs deleted file mode 100644 index bfc386ff..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/StringExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Text; - -namespace Snap.Hutao.SourceGeneration.Resx; - -internal static class StringExtensions -{ - public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) - { - StringBuilder sb = new(); - - int previousIndex = 0; - int index = str.IndexOf(oldValue, comparison); - while (index is not -1) - { - sb.Append(str, previousIndex, index - previousIndex); - sb.Append(newValue); - index += oldValue.Length; - - previousIndex = index; - index = str.IndexOf(oldValue, index, comparison); - } - - sb.Append(str, previousIndex, str.Length - previousIndex); - return sb.ToString(); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj deleted file mode 100644 index e4b92cd8..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netstandard2.0 - false - latest - enable - x64 - true - Debug;Release - true - - - - - - - - \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs deleted file mode 100644 index d09bbc6a..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/UniversalAnalyzer.cs +++ /dev/null @@ -1,302 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Snap.Hutao.SourceGeneration.Primitive; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; - -namespace Snap.Hutao.SourceGeneration; - -/// -/// 通用分析器 -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -internal sealed class UniversalAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor typeInternalDescriptor = new("SH001", "Type should be internal", "Type [{0}] should be internal", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor readOnlyStructRefDescriptor = new("SH002", "ReadOnly struct should be passed with ref-like key word", "ReadOnly Struct [{0}] should be passed with ref-like key word", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor useValueTaskIfPossibleDescriptor = new("SH003", "Use ValueTask instead of Task whenever possible", "Use ValueTask instead of Task", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor useIsNotNullPatternMatchingDescriptor = new("SH004", "Use \"is not null\" instead of \"!= null\" whenever possible", "Use \"is not null\" instead of \"!= null\"", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor useIsNullPatternMatchingDescriptor = new("SH005", "Use \"is null\" instead of \"== null\" whenever possible", "Use \"is null\" instead of \"== null\"", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor useIsPatternRecursiveMatchingDescriptor = new("SH006", "Use \"is { } obj\" whenever possible", "Use \"is {{ }} {0}\"", "Quality", DiagnosticSeverity.Info, true); - private static readonly DiagnosticDescriptor useArgumentNullExceptionThrowIfNullDescriptor = new("SH007", "Use \"ArgumentNullException.ThrowIfNull()\" instead of \"!\"", "Use \"ArgumentNullException.ThrowIfNull()\"", "Quality", DiagnosticSeverity.Info, true); - - - private static readonly ImmutableHashSet RefLikeKeySkipTypes = new HashSet() - { - "System.Threading.CancellationToken", - "System.Guid" - }.ToImmutableHashSet(); - - public override ImmutableArray SupportedDiagnostics - { - get - { - return new DiagnosticDescriptor[] - { - typeInternalDescriptor, - readOnlyStructRefDescriptor, - useValueTaskIfPossibleDescriptor, - useIsNotNullPatternMatchingDescriptor, - useIsNullPatternMatchingDescriptor, - useIsPatternRecursiveMatchingDescriptor, - useArgumentNullExceptionThrowIfNullDescriptor - }.ToImmutableArray(); - } - } - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterCompilationStartAction(CompilationStart); - } - - private static void CompilationStart(CompilationStartAnalysisContext context) - { - SyntaxKind[] types = [SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration,]; - context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types); - context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration); - context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration); - context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration); - - SyntaxKind[] expressions = [SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression,]; - context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions); - context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression); - context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression); - - // TODO add analyzer for unnecessary IServiceProvider registration - // TODO add analyzer for Singlton service use Scoped or Transient services - } - - private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context) - { - BaseTypeDeclarationSyntax syntax = (BaseTypeDeclarationSyntax)context.Node; - - bool privateExists = false; - bool internalExists = false; - bool fileExists = false; - - foreach (SyntaxToken token in syntax.Modifiers) - { - if (token.IsKind(SyntaxKind.PrivateKeyword)) - { - privateExists = true; - } - - if (token.IsKind(SyntaxKind.InternalKeyword)) - { - internalExists = true; - } - - if (token.IsKind(SyntaxKind.FileKeyword)) - { - fileExists = true; - } - } - - if (!privateExists && !internalExists && !fileExists) - { - Location location = syntax.Identifier.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(typeInternalDescriptor, location, syntax.Identifier); - context.ReportDiagnostic(diagnostic); - } - } - - private static void HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask(SyntaxNodeAnalysisContext context) - { - MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node; - IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!; - - // 跳过重载方法 - if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.OverrideKeyword))) - { - return; - } - - // ICommand can only use Task or Task - if (methodSymbol.GetAttributes().Any(attr => attr.AttributeClass!.ToDisplayString() == Automation.CommandGenerator.AttributeName)) - { - return; - } - - if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task")) - { - Location location = methodSyntax.ReturnType.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(useValueTaskIfPossibleDescriptor, location); - context.ReportDiagnostic(diagnostic); - } - } - - private static void HandleMethodParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context) - { - MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node; - - // 跳过方法定义 如 接口 - if (methodSyntax.Body == null) - { - return; - } - - IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!; - - if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task")) - { - return; - } - - if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.ValueTask")) - { - return; - } - - foreach (SyntaxToken token in methodSyntax.Modifiers) - { - // 跳过异步方法,因为异步方法无法使用 ref/in/out - if (token.IsKind(SyntaxKind.AsyncKeyword)) - { - return; - } - - // 跳过重载方法 - if (token.IsKind(SyntaxKind.OverrideKeyword)) - { - return; - } - } - - foreach (ParameterSyntax parameter in methodSyntax.ParameterList.Parameters) - { - if (context.SemanticModel.GetDeclaredSymbol(parameter) is IParameterSymbol symbol) - { - if (IsBuiltInType(symbol.Type)) - { - continue; - } - - if (RefLikeKeySkipTypes.Contains(symbol.Type.ToDisplayString())) - { - continue; - } - - if (symbol.Type.IsReadOnly && symbol.RefKind == RefKind.None) - { - Location location = parameter.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(readOnlyStructRefDescriptor, location, symbol.Type); - context.ReportDiagnostic(diagnostic); - } - } - } - } - - private static void HandleConstructorParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context) - { - ConstructorDeclarationSyntax constructorSyntax = (ConstructorDeclarationSyntax)context.Node; - - foreach (ParameterSyntax parameter in constructorSyntax.ParameterList.Parameters) - { - if (context.SemanticModel.GetDeclaredSymbol(parameter) is IParameterSymbol symbol) - { - if (IsBuiltInType(symbol.Type)) - { - continue; - } - - // 跳过 CancellationToken - if (symbol.Type.ToDisplayString() == "System.Threading.CancellationToken") - { - continue; - } - - if (symbol.Type.IsReadOnly && symbol.RefKind == RefKind.None) - { - Location location = parameter.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(readOnlyStructRefDescriptor, location, symbol.Type); - context.ReportDiagnostic(diagnostic); - } - } - } - } - - public static void HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching(SyntaxNodeAnalysisContext context) - { - BinaryExpressionSyntax syntax = (BinaryExpressionSyntax)context.Node; - if (syntax.IsKind(SyntaxKind.NotEqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression)) - { - Location location = syntax.OperatorToken.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(useIsNotNullPatternMatchingDescriptor, location); - context.ReportDiagnostic(diagnostic); - } - else if (syntax.IsKind(SyntaxKind.EqualsExpression) && syntax.Right.IsKind(SyntaxKind.NullLiteralExpression)) - { - Location location = syntax.OperatorToken.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(useIsNullPatternMatchingDescriptor, location); - context.ReportDiagnostic(diagnostic); - } - } - - private static void HandleIsPatternShouldUseRecursivePattern(SyntaxNodeAnalysisContext context) - { - IsPatternExpressionSyntax syntax = (IsPatternExpressionSyntax)context.Node; - if (syntax.Pattern is DeclarationPatternSyntax declaration) - { - ITypeSymbol? leftType = context.SemanticModel.GetTypeInfo(syntax.Expression).ConvertedType; - ITypeSymbol? rightType = context.SemanticModel.GetTypeInfo(declaration).ConvertedType; - if (SymbolEqualityComparer.Default.Equals(leftType, rightType)) - { - Location location = declaration.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(useIsPatternRecursiveMatchingDescriptor, location, declaration.Designation); - context.ReportDiagnostic(diagnostic); - } - } - } - - private static void HandleArgumentNullExceptionThrowIfNull(SyntaxNodeAnalysisContext context) - { - PostfixUnaryExpressionSyntax syntax = (PostfixUnaryExpressionSyntax)context.Node; - - if (syntax.Operand is LiteralExpressionSyntax literal) - { - if (literal.IsKind(SyntaxKind.DefaultLiteralExpression)) - { - return; - } - } - - if (syntax.Operand is DefaultExpressionSyntax expression) - { - return; - } - - - Location location = syntax.GetLocation(); - Diagnostic diagnostic = Diagnostic.Create(useArgumentNullExceptionThrowIfNullDescriptor, location); - context.ReportDiagnostic(diagnostic); - } - - private static bool IsBuiltInType(ITypeSymbol symbol) - { - return symbol.SpecialType switch - { - SpecialType.System_Boolean => true, - SpecialType.System_Char => true, - SpecialType.System_SByte => true, - SpecialType.System_Byte => true, - SpecialType.System_Int16 => true, - SpecialType.System_UInt16 => true, - SpecialType.System_Int32 => true, - SpecialType.System_UInt32 => true, - SpecialType.System_Int64 => true, - SpecialType.System_UInt64 => true, - SpecialType.System_Decimal => true, - SpecialType.System_Single => true, - SpecialType.System_Double => true, - SpecialType.System_IntPtr => true, - SpecialType.System_UIntPtr => true, - _ => false, - }; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json deleted file mode 100644 index 6852fc3d..00000000 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/stylecop.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "documentationRules": { - "companyName": "DGP Studio", - "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license.", - "xmlHeader": false, - "variables": { - "licenseName": "MIT" - } - }, - "orderingRules": { - "elementOrder": [ - "kind", - "accessibility", - "constant", - "static", - "readonly" - ], - "usingDirectivesPlacement": "outsideNamespace" - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao.sln b/src/Snap.Hutao/Snap.Hutao.sln index ce385152..b07b5517 100644 --- a/src/Snap.Hutao/Snap.Hutao.sln +++ b/src/Snap.Hutao/Snap.Hutao.sln @@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{0F7ABEB2-5107-4037-B9DC-84D288FB0801}" @@ -52,22 +50,6 @@ Global {AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86 {AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86 {AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.Build.0 = Debug|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.ActiveCfg = Debug|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x64.Build.0 = Debug|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.ActiveCfg = Debug|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|x86.Build.0 = Debug|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|Any CPU.Build.0 = Release|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.ActiveCfg = Release|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|arm64.Build.0 = Release|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.ActiveCfg = Release|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64 - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU - {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU {D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.Build.0 = Debug|Any CPU {D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.ActiveCfg = Debug|Any CPU @@ -105,11 +87,11 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]} - RESX_ShowErrorsInErrorList = False - RESX_SortFileContentOnSave = True - SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA} - RESX_NeutralResourcesLanguage = zh-CN RESX_AutoApplyExistingTranslations = False + RESX_NeutralResourcesLanguage = zh-CN + SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA} + RESX_SortFileContentOnSave = True + RESX_ShowErrorsInErrorList = False + RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]} EndGlobalSection EndGlobal diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 15efc0ee..855fbd43 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="1.8.3.0" /> Snap Hutao diff --git a/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest index cc32924c..d4e25e6e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest @@ -12,7 +12,7 @@ + Version="1.8.3.0" /> Snap Hutao Dev diff --git a/src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml b/src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..a2ef0de8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,19 @@ + + + + + Release + x64 + bin\Release\Publish + FileSystem + <_TargetId>Folder + net8.0-windows10.0.22621.0 + win10-x64 + true + false + false + false + + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 96c0b0e1..a1ed520d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -5,12 +5,10 @@ 10.0.19041.0 Snap.Hutao app.manifest - x64 x64 true win-x64 - win-x64 true False False @@ -23,7 +21,7 @@ True D15C4474363D2AF157C5CC6F230C7BFF2CF19B25 SHA256 - True + False False False Never @@ -37,6 +35,15 @@ True x64 Debug;Release + + true @@ -295,6 +302,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -306,10 +317,9 @@ - - +