Skip to content

Commit

Permalink
optimize source generator delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
Poker-sang committed Apr 15, 2022
1 parent ce2824a commit ee13443
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 111 deletions.
28 changes: 24 additions & 4 deletions src/Pixeval.SourceGen/DependencyProperty.cs
@@ -1,8 +1,28 @@
using System;
#region Copyright (c) Pixeval/Pixeval.SourceGen

// GPL v3 License
//
// Pixeval/Pixeval.SourceGen
// Copyright (c) 2021 Pixeval.SourceGen/LoadSaveConfigurationGenerator.cs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#endregion

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Pixeval.SourceGen.Utils;
Expand All @@ -11,13 +31,13 @@ namespace Pixeval.SourceGen;

internal static partial class TypeWithAttributeDelegates
{
public static string? DependencyProperty(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, Func<AttributeData, bool> attributeEqualityComparer)
public static string? DependencyProperty(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, List<AttributeData> attributeList)
{
var members = new List<MemberDeclarationSyntax>();
var namespaces = new HashSet<string> { "Microsoft.UI.Xaml" };
var usedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);

foreach (var attribute in typeSymbol.GetAttributes().Where(attributeEqualityComparer))
foreach (var attribute in attributeList)
{
if (attribute.ConstructorArguments[0].Value is not string propertyName || attribute.ConstructorArguments[1].Value is not INamedTypeSymbol type)
continue;
Expand Down
25 changes: 23 additions & 2 deletions src/Pixeval.SourceGen/GenerateConstructor.cs
@@ -1,4 +1,25 @@
using System;
#region Copyright (c) Pixeval/Pixeval.SourceGen

// GPL v3 License
//
// Pixeval/Pixeval.SourceGen
// Copyright (c) 2021 Pixeval.SourceGen/LoadSaveConfigurationGenerator.cs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#endregion

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -12,7 +33,7 @@ namespace Pixeval.SourceGen;

internal static partial class TypeWithAttributeDelegates
{
public static string GenerateConstructor(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, Func<AttributeData, bool> attributeEqualityComparer)
public static string GenerateConstructor(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, List<AttributeData> attributeList)
{
var name = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var namespaces = new HashSet<string>();
Expand Down
144 changes: 81 additions & 63 deletions src/Pixeval.SourceGen/LoadSaveConfiguration.cs
@@ -1,61 +1,80 @@
using Microsoft.CodeAnalysis;
#region Copyright (c) Pixeval/Pixeval.SourceGen

// GPL v3 License
//
// Pixeval/Pixeval.SourceGen
// Copyright (c) 2021 Pixeval.SourceGen/LoadSaveConfigurationGenerator.cs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#endregion

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using static Pixeval.SourceGen.Utils;

namespace Pixeval.SourceGen;

internal static partial class TypeWithAttributeDelegates
{
public static string? LoadSaveConfiguration(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, Func<AttributeData, bool> attributeEqualityComparer)
public static string? LoadSaveConfiguration(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, List<AttributeData> attributeList)
{
foreach (var attribute in typeSymbol.GetAttributes().Where(attributeEqualityComparer))
{
if (attribute.ConstructorArguments[0].Value is not INamedTypeSymbol type)
continue;
if (attribute.ConstructorArguments[1].Value is not string containerName)
continue;
var attribute = attributeList[0];
if (attribute.ConstructorArguments[0].Value is not INamedTypeSymbol type)
return null;
if (attribute.ConstructorArguments[1].Value is not string containerName)
return null;

string? staticClassName = null;
string? methodName = null;
string? staticClassName = null;
string? methodName = null;

if (attribute.NamedArguments[0].Key is "CastMethod" && attribute.NamedArguments[0].Value.Value is { } value)
{
var castMethodFullName = (string)value;
var dotPosition = castMethodFullName.LastIndexOf('.');
if (dotPosition is -1)
throw new InvalidDataException("\"CastMethod\" must contain the full name.");
staticClassName = "static " + castMethodFullName.Substring(0, dotPosition);
methodName = castMethodFullName.Substring(dotPosition + 1);
}
if (attribute.NamedArguments[0].Key is "CastMethod" && attribute.NamedArguments[0].Value.Value is { } value)
{
var castMethodFullName = (string)value;
var dotPosition = castMethodFullName.LastIndexOf('.');
if (dotPosition is -1)
throw new InvalidDataException("\"CastMethod\" must contain the full name.");
staticClassName = "static " + castMethodFullName.Substring(0, dotPosition);
methodName = castMethodFullName.Substring(dotPosition + 1);
}

var name = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var namespaces = new HashSet<string>();
if (staticClassName is not null)
namespaces.Add(staticClassName);//methodName方法所用namespace
var usedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
/*-----Body Begin-----*/
var stringBuilder = new StringBuilder().AppendLine("#nullable enable\n");
/*-----Splitter-----*/
var classBegin = @$"
var name = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var namespaces = new HashSet<string>();
if (staticClassName is not null)
namespaces.Add(staticClassName); //methodName方法所用namespace
var usedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
/*-----Body Begin-----*/
var stringBuilder = new StringBuilder().AppendLine("#nullable enable\n");
/*-----Splitter-----*/
var classBegin = @$"
namespace {typeSymbol.ContainingNamespace.ToDisplayString()};
partial class {name}
{{";
var loadConfigurationBegin = $@" public static {type.Name}? LoadConfiguration()
var loadConfigurationBegin = $@" public static {type.Name}? LoadConfiguration()
{{
try
{{
return new {type.Name}(";
/*-----Splitter-----*/
var loadConfigurationContent = new StringBuilder();
/*-----Splitter-----*/
var loadConfigurationEndAndSaveConfigurationBegin = $@" );
/*-----Splitter-----*/
var loadConfigurationContent = new StringBuilder();
/*-----Splitter-----*/
var loadConfigurationEndAndSaveConfigurationBegin = $@" );
}}
catch
{{
Expand All @@ -67,38 +86,37 @@ public static void SaveConfiguration({type.Name}? configuration)
{{
if (configuration is {{ }} appConfiguration)
{{";
/*-----Splitter-----*/
var saveConfigurationContent = new StringBuilder();
/*-----Splitter-----*/
const string saveConfigurationEndAndClassEnd = $@" }}
/*-----Splitter-----*/
var saveConfigurationContent = new StringBuilder();
/*-----Splitter-----*/
const string saveConfigurationEndAndClassEnd = $@" }}
}}
}}";
/*-----Body End-----*/
foreach (var member in type.GetMembers().Where(member =>
member is { Kind: SymbolKind.Property } and not { Name: "EqualityContract" })
.Cast<IPropertySymbol>())
{
loadConfigurationContent.AppendLine(LoadRecord(member.Name, member.Type.Name, type.Name, containerName, methodName));
saveConfigurationContent.AppendLine(SaveRecord(member.Name, member.Type, type.Name, containerName, methodName));
namespaces.UseNamespace(usedTypes, typeSymbol, member.Type);
}

// 去除" \r\n"
loadConfigurationContent = loadConfigurationContent.Remove(loadConfigurationContent.Length - 3, 3);

foreach (var s in namespaces)
_ = stringBuilder.AppendLine($"using {s};");
stringBuilder.AppendLine(classBegin)
.AppendLine(loadConfigurationBegin)
.AppendLine(loadConfigurationContent.ToString())
.AppendLine(loadConfigurationEndAndSaveConfigurationBegin)
// saveConfigurationContent 后已有空行
.Append(saveConfigurationContent)
.AppendLine(saveConfigurationEndAndClassEnd);
return stringBuilder.ToString();
/*-----Body End-----*/
foreach (var member in type.GetMembers().Where(member =>
member is { Kind: SymbolKind.Property } and not { Name: "EqualityContract" })
.Cast<IPropertySymbol>())
{
loadConfigurationContent.AppendLine(LoadRecord(member.Name, member.Type.Name, type.Name, containerName,
methodName));
saveConfigurationContent.AppendLine(SaveRecord(member.Name, member.Type, type.Name, containerName,
methodName));
namespaces.UseNamespace(usedTypes, typeSymbol, member.Type);
}

return null;
// 去除" \r\n"
loadConfigurationContent = loadConfigurationContent.Remove(loadConfigurationContent.Length - 3, 3);

foreach (var s in namespaces)
_ = stringBuilder.AppendLine($"using {s};");
stringBuilder.AppendLine(classBegin)
.AppendLine(loadConfigurationBegin)
.AppendLine(loadConfigurationContent.ToString())
.AppendLine(loadConfigurationEndAndSaveConfigurationBegin)
// saveConfigurationContent 后已有空行
.Append(saveConfigurationContent)
.AppendLine(saveConfigurationEndAndClassEnd);
return stringBuilder.ToString();
}


Expand Down
77 changes: 39 additions & 38 deletions src/Pixeval.SourceGen/SettingsViewModel.cs
@@ -1,4 +1,5 @@
#region Copyright (c) Pixeval/Pixeval.SourceGen

// GPL v3 License
//
// Pixeval/Pixeval.SourceGen
Expand All @@ -16,71 +17,71 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Pixeval.SourceGen.Utils;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using static Pixeval.SourceGen.Utils;

namespace Pixeval.SourceGen;

internal static partial class TypeWithAttributeDelegates
{
public static string? SettingsViewModel(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, Func<AttributeData, bool> attributeEqualityComparer)
public static string? SettingsViewModel(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol,
List<AttributeData> attributeList)
{
foreach (var typeAttribute in typeSymbol.GetAttributes().Where(attributeEqualityComparer))
{
if (typeAttribute.ConstructorArguments[0].Value is not INamedTypeSymbol type)
continue;
if (typeAttribute.ConstructorArguments[1].Value is not string settingName)
continue;
var attribute = attributeList[0];
if (attribute.ConstructorArguments[0].Value is not INamedTypeSymbol type)
return null;
if (attribute.ConstructorArguments[1].Value is not string settingName)
return null;

var name = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var namespaces = new HashSet<string> { typeSymbol.ContainingNamespace.ToDisplayString() };
var usedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
const string nullable = "#nullable enable\n";
var classBegin = @$"namespace {typeSymbol.ContainingNamespace.ToDisplayString()};
var name = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var namespaces = new HashSet<string> { typeSymbol.ContainingNamespace.ToDisplayString() };
var usedTypes = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
const string nullable = "#nullable enable\n";
var classBegin = @$"namespace {typeSymbol.ContainingNamespace.ToDisplayString()};
partial class {name}
{{";
var propertySentences = new List<string>();
const string classEnd = @"}";
var propertySentences = new List<string>();
const string classEnd = @"}";

foreach (var property in type.GetMembers().Where(property =>
property is { Kind: SymbolKind.Property } and not { Name: "EqualityContract" }
&& !property.GetAttributes().Any(propertyAttribute => propertyAttribute.AttributeClass!.Name is "SettingsViewModelExclusionAttribute"))
.Cast<IPropertySymbol>())
foreach (var property in type.GetMembers().Where(property =>
property is { Kind: SymbolKind.Property } and not { Name: "EqualityContract" }
&& !property.GetAttributes().Any(propertyAttribute =>
propertyAttribute.AttributeClass!.Name is "SettingsViewModelExclusionAttribute"))
.Cast<IPropertySymbol>())
{
namespaces.UseNamespace(usedTypes, typeSymbol, property.Type);
foreach (var propertyAttribute in property.GetAttributes())
{
namespaces.UseNamespace(usedTypes, typeSymbol, property.Type);
foreach (var propertyAttribute in property.GetAttributes())
namespaces.UseNamespace(usedTypes, typeSymbol, propertyAttribute.AttributeClass!);
foreach (var attrConstructorArgument in propertyAttribute.ConstructorArguments.Where(arg =>
arg.Value is INamedTypeSymbol))
{
namespaces.UseNamespace(usedTypes, typeSymbol, propertyAttribute.AttributeClass!);
foreach (var attrConstructorArgument in propertyAttribute.ConstructorArguments.Where(arg => arg.Value is INamedTypeSymbol))
{
namespaces.UseNamespace(usedTypes, typeSymbol, (ITypeSymbol) attrConstructorArgument.Value!);
}
namespaces.UseNamespace(usedTypes, typeSymbol, (ITypeSymbol)attrConstructorArgument.Value!);
}
}

propertySentences.Add(Spacing(1) + Regex.Replace(property.DeclaringSyntaxReferences[0].GetSyntax().ToString(), @"{[\s\S]+}",
$@"
propertySentences.Add(Spacing(1) + Regex.Replace(
property.DeclaringSyntaxReferences[0].GetSyntax().ToString(), @"{[\s\S]+}",
$@"
{{
get => {settingName}.{property.Name};
set => SetProperty({settingName}.{property.Name}, value, {settingName}, (setting, value) => setting.{property.Name} = value);
}}"));
}


var namespaceNames = namespaces.Skip(1).Aggregate("", (current, ns) => current + $"using {ns};\n");
var allPropertySentences = propertySentences.Aggregate("\n", (current, ps) => current + $"{ps}\n\n");
allPropertySentences = allPropertySentences.Substring(0, allPropertySentences.Length - 1);
var compilationUnit = nullable + namespaceNames + classBegin + allPropertySentences + classEnd;
return compilationUnit;
}

return null;

var namespaceNames = namespaces.Skip(1).Aggregate("", (current, ns) => current + $"using {ns};\n");
var allPropertySentences = propertySentences.Aggregate("\n", (current, ps) => current + $"{ps}\n\n");
allPropertySentences = allPropertySentences.Substring(0, allPropertySentences.Length - 1);
var compilationUnit = nullable + namespaceNames + classBegin + allPropertySentences + classEnd;
return compilationUnit;
}
}

0 comments on commit ee13443

Please sign in to comment.