mirror of
https://github.com/AlexMacocian/WpfExtended.git
synced 2026-07-15 14:59:30 +00:00
Switch to Incremental Generators
Implement INotifyPropertyChanged Generator
This commit is contained in:
@@ -1,24 +1,41 @@
|
||||
using FluentAssertions;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace WpfExtended.SourceGeneration.Tests
|
||||
{
|
||||
internal partial class Class1 : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
public int someF;
|
||||
[GenerateDependencyProperty(InitialValue = "This has a value")]
|
||||
public string someValue;
|
||||
[GenerateDependencyProperty]
|
||||
public Effect effect;
|
||||
namespace WpfExtended.SourceGeneration.Tests;
|
||||
|
||||
public void TestValues()
|
||||
{
|
||||
this.SomeF.Should().Be(0);
|
||||
this.SomeValue.Should().Be("This has a value");
|
||||
this.Effect.Should().BeNull();
|
||||
}
|
||||
internal partial class Class1 : UserControl, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private bool calledEvent;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
public int someF;
|
||||
[GenerateDependencyProperty(InitialValue = "This has a value")]
|
||||
public string someValue;
|
||||
[GenerateDependencyProperty]
|
||||
public Effect effect;
|
||||
[GenerateNotifyPropertyChanged]
|
||||
public string someNotifyingValue;
|
||||
|
||||
|
||||
public void TestValues()
|
||||
{
|
||||
this.PropertyChanged += Class1_PropertyChanged;
|
||||
this.SomeF.Should().Be(0);
|
||||
this.SomeValue.Should().Be("This has a value");
|
||||
this.Effect.Should().BeNull();
|
||||
|
||||
this.SomeNotifyingValue = "NewValue";
|
||||
this.calledEvent.Should().BeTrue();
|
||||
this.someNotifyingValue.Should().Be("NewValue");
|
||||
}
|
||||
|
||||
private void Class1_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
this.calledEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
using System.Extensions.Templates;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Extensions
|
||||
{
|
||||
internal class CodeWriter
|
||||
{
|
||||
private StringBuilder StringBuilder { get; } = new StringBuilder();
|
||||
private CodeBlock CurrentBlock { get; set; } = new CodeBlock();
|
||||
|
||||
public CodeWriter Append(string value)
|
||||
{
|
||||
this.CurrentBlock.Append(this.StringBuilder, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine(string value)
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine()
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Append(char c)
|
||||
{
|
||||
this.CurrentBlock.Append(this.StringBuilder, c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine(char c)
|
||||
{
|
||||
this.AppendLine(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Append(AbstractTemplate abstractTemplate)
|
||||
{
|
||||
abstractTemplate.Generate(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter BeginCodeBlock()
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder).AppendLine(this.StringBuilder, '{');
|
||||
this.CurrentBlock = CodeBlock.GenerateCodeBlock(this.CurrentBlock);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter EndCodeBlock()
|
||||
{
|
||||
this.CurrentBlock = this.CurrentBlock.End(this.StringBuilder).Parent;
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder, '}');
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.StringBuilder.ToString();
|
||||
}
|
||||
|
||||
private class CodeBlock
|
||||
{
|
||||
private const char PrefixSymbol = '\t';
|
||||
private bool NewLine { get; set; } = true;
|
||||
private string Prefix { get; }
|
||||
public CodeBlock Parent { get; }
|
||||
public int Level { get; }
|
||||
public CodeBlock()
|
||||
{
|
||||
this.Level = 0;
|
||||
this.Prefix = string.Empty;
|
||||
}
|
||||
private CodeBlock(int level, CodeBlock parent)
|
||||
{
|
||||
this.Level = level;
|
||||
this.Prefix = new string(Enumerable.Repeat(PrefixSymbol, this.Level).ToArray());
|
||||
this.Parent = parent;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder)
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder, string value)
|
||||
{
|
||||
value = this.NewLine is true ? Prefix + value : value;
|
||||
stringBuilder.AppendLine(value);
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock Append(StringBuilder stringBuilder, string value)
|
||||
{
|
||||
value = this.NewLine is true ? Prefix + value : value;
|
||||
stringBuilder.Append(value);
|
||||
this.NewLine = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock Append(StringBuilder stringBuilder, char c)
|
||||
{
|
||||
var value = this.NewLine is true ? Prefix + c : c.ToString();
|
||||
stringBuilder.Append(value);
|
||||
this.NewLine = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder, char c)
|
||||
{
|
||||
var value = this.NewLine is true ? Prefix + c : c.ToString();
|
||||
stringBuilder.AppendLine(value);
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock End(StringBuilder stringBuilder)
|
||||
{
|
||||
if (this.NewLine is false)
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
this.NewLine = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static CodeBlock GenerateCodeBlock(CodeBlock codeBlock)
|
||||
{
|
||||
var child = new CodeBlock(codeBlock.Level + 1, codeBlock);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,194 +2,179 @@
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Sybil;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Extensions.Templates;
|
||||
|
||||
namespace System.Extensions
|
||||
namespace System.Extensions;
|
||||
#nullable enable
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class DependencyPropertyGenerator : IIncrementalGenerator
|
||||
{
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class DependencyPropertyGenerator : ISourceGenerator
|
||||
private const string DependencyPropertyNamespace = "System.Windows";
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateDependencyPropertyAttribute";
|
||||
private const string AttributeShortName = "GenerateDependencyProperty";
|
||||
private const string DependencyPropertySuffix = "Property";
|
||||
private const string DependencyProperty = "DependencyProperty";
|
||||
private const string Register = "Register";
|
||||
private const string Partial = "partial";
|
||||
private const string Public = "public";
|
||||
private const string Static = "static";
|
||||
private const string Readonly = "readonly";
|
||||
private const string InitialValue = "InitialValue";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateDependencyPropertyAttribute";
|
||||
private const string DependencyPropertySuffix = "Property";
|
||||
private static readonly ClassTemplate GenerateDependencyPropertyAttribute =
|
||||
new ClassTemplate()
|
||||
.WithUsings(
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System"))
|
||||
.WithNamespace(
|
||||
new NamespaceTemplate()
|
||||
.WithNamespace(AttributeNamespace))
|
||||
.WithConstructor(
|
||||
new ConstructorTemplate()
|
||||
.WithType(AttributeName)
|
||||
.WithModifiers(Modifier.Public))
|
||||
.WithAttributes(
|
||||
new AttributeTemplate()
|
||||
.WithAttribute("AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)"))
|
||||
.WithModifiers(Modifier.Public, Modifier.Sealed)
|
||||
.WithName(AttributeName)
|
||||
.WithBase("Attribute")
|
||||
.WithProperty(
|
||||
new PropertyTemplate()
|
||||
.WithModifiers(Modifier.Public)
|
||||
.WithName("InitialValue")
|
||||
.WithType("object"));
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
// Register a syntax receiver that will be created for each generation pass
|
||||
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||
var attributeNamespaceBuilder = SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
||||
.WithUsing("System.ComponentModel")
|
||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
||||
.WithModifier(Public)
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
||||
.WithModifier(Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Field)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty("object", InitialValue)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter())));
|
||||
var attributeSyntax = attributeNamespaceBuilder.Build();
|
||||
var source = attributeSyntax.ToFullString();
|
||||
context.AddSource($"{AttributeName}.g", source);
|
||||
});
|
||||
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is FieldDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
|
||||
if (fieldDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
|
||||
{
|
||||
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
|
||||
}
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation _, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
{
|
||||
var attributeText = GenerateDependencyPropertyAttribute.GenerateString();
|
||||
context.AddSource("GenerateDependencyPropertyAttribute.g", attributeText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||
return;
|
||||
|
||||
var options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions;
|
||||
var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options));
|
||||
|
||||
var attributeSymbol = compilation.GetTypeByMetadataName($"{AttributeNamespace}.{AttributeName}");
|
||||
if (attributeSymbol is null)
|
||||
var distinctClasses = classes.Distinct();
|
||||
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
var className = c.Identifier.ToFullString().Trim();
|
||||
if (!c.Modifiers.Any(m => m.ToString() == Partial))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostics.MissingAttributeDiagnostic(AttributeName, AttributeNamespace));
|
||||
return;
|
||||
Diagnostics.ClassNotPartial(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldSymbols = new List<IFieldSymbol>();
|
||||
foreach (var field in receiver.CandidateFields)
|
||||
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
|
||||
if (originalNamespaceSyntax is null)
|
||||
{
|
||||
var model = compilation.GetSemanticModel(field.SyntaxTree);
|
||||
foreach (var variable in field.Declaration.Variables)
|
||||
Diagnostics.ClassNotInNamespace(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
|
||||
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
|
||||
var generatedNamespaceBuilder = SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString())
|
||||
.WithClass(generatedClassBuilder);
|
||||
|
||||
if (originalNamespaceSyntax.Usings.OfType<UsingDirectiveSyntax>().Any(u => u.Name?.ToString() == DependencyPropertyNamespace) is false &&
|
||||
(originalNamespaceSyntax.Parent as CompilationUnitSyntax)?.Usings.Any(u => u.Name?.ToString() == DependencyPropertyNamespace) is false)
|
||||
{
|
||||
generatedNamespaceBuilder.WithUsing(DependencyPropertyNamespace);
|
||||
}
|
||||
|
||||
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
|
||||
{
|
||||
var attributeSyntax = field.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.FirstOrDefault(s => s.Name.ToString() == AttributeName ||
|
||||
s.Name.ToString() == AttributeShortName);
|
||||
if (attributeSyntax is null)
|
||||
{
|
||||
var fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol;
|
||||
if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)))
|
||||
{
|
||||
fieldSymbols.Add(fieldSymbol);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var initialValueArgument = attributeSyntax.ArgumentList?.Arguments
|
||||
.FirstOrDefault(arg => arg.NameEquals != null && arg.NameEquals.Name.Identifier.ValueText == InitialValue);
|
||||
|
||||
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString().Trim();
|
||||
var fieldType = field.Declaration.Type.ToFullString().Trim();
|
||||
var propertyName = fieldName.TrimStart('_');
|
||||
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}".Trim();
|
||||
var dependencyPropertyName = $"{propertyName.Trim()}{DependencyPropertySuffix}".Trim();
|
||||
var propertyBuilder = SyntaxBuilder.CreateProperty(fieldType, propertyName)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()
|
||||
.WithBody($"this.{fieldName} = ({fieldType})this.GetValue({dependencyPropertyName});\r\nreturn this.{fieldName};"))
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()
|
||||
.WithBody($"this.{fieldName} = value;\r\nthis.SetValue({dependencyPropertyName}, value);"));
|
||||
var dependencyPropertyFieldBuilder = SyntaxBuilder.CreateField(DependencyProperty, dependencyPropertyName)
|
||||
.WithModifiers($"{Public} {Static} {Readonly}")
|
||||
.WithInitializer($"{DependencyProperty}.Register(nameof({propertyName}), typeof({fieldType}), typeof({className}), new PropertyMetadata({initialValueArgument?.Expression.ToString()}))");
|
||||
|
||||
generatedClassBuilder
|
||||
.WithProperty(propertyBuilder)
|
||||
.WithField(dependencyPropertyFieldBuilder);
|
||||
}
|
||||
|
||||
foreach (var group in fieldSymbols.GroupBy(f => f.ContainingType, SymbolEqualityComparer.IncludeNullability))
|
||||
var generatedNamespace = generatedNamespaceBuilder.Build();
|
||||
if (originalNamespaceSyntax.Usings.Count != 0)
|
||||
{
|
||||
var classSource = ProcessClass((INamedTypeSymbol)group.Key, attributeSymbol, group.ToList(), context);
|
||||
context.AddSource($"{group.Key.Name}.DependencyPropertyGenerator.g.cs", classSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ProcessClass(INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, List<IFieldSymbol> fields, GeneratorExecutionContext generatorExecutionContext)
|
||||
{
|
||||
if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default))
|
||||
{
|
||||
var syntaxTree = classSymbol.DeclaringSyntaxReferences.First().SyntaxTree;
|
||||
var textSpan = classSymbol.DeclaringSyntaxReferences.First().Span;
|
||||
generatorExecutionContext.ReportDiagnostic(
|
||||
Diagnostics.ClassNotTopLevelDiagnostic(classSymbol.Name, classSymbol.ContainingSymbol.Name, syntaxTree, textSpan));
|
||||
return null;
|
||||
}
|
||||
|
||||
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
var source = new ClassTemplate()
|
||||
.WithUsings(
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System"),
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System.Windows"))
|
||||
.WithNamespace(
|
||||
new NamespaceTemplate()
|
||||
.WithNamespace(namespaceName))
|
||||
.WithName(classSymbol.Name);
|
||||
AssignModifiers(source, classSymbol);
|
||||
foreach (var fieldSymbol in fields)
|
||||
{
|
||||
ProcessField(source, classSymbol, attributeSymbol, fieldSymbol);
|
||||
}
|
||||
|
||||
return source.GenerateString();
|
||||
}
|
||||
|
||||
private static void AssignModifiers(ClassTemplate source, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
//Find class declaration syntax and parse modifiers.
|
||||
if (classSymbol.DeclaringSyntaxReferences.Select(sr => sr.GetSyntax()).OfType<ClassDeclarationSyntax>().First() is ClassDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
source.WithModifiers(classDeclarationSyntax.Modifiers.Select(m => Modifier.Parse(m.ValueText)).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessField(ClassTemplate source, INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, IFieldSymbol fieldSymbol)
|
||||
{
|
||||
var fieldName = fieldSymbol.Name;
|
||||
var fieldType = fieldSymbol.Type;
|
||||
|
||||
string propertyName = GenerateName(fieldName);
|
||||
|
||||
var attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
var initialValueOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "InitialValue").Value;
|
||||
|
||||
var dependencyPropertyValue = initialValueOpt.Value is null ?
|
||||
@$"DependencyProperty.Register(""{propertyName}"", typeof({fieldType}), typeof({classSymbol.Name}))" :
|
||||
@$"DependencyProperty.Register(""{propertyName}"", typeof({fieldType}), typeof({classSymbol.Name}), new PropertyMetadata({initialValueOpt.ToCSharpString()}))";
|
||||
|
||||
source
|
||||
.WithField(
|
||||
new FieldTemplate()
|
||||
.WithModifiers(Modifier.Public, Modifier.Static, Modifier.Readonly)
|
||||
.WithType("DependencyProperty")
|
||||
.WithName(propertyName + DependencyPropertySuffix)
|
||||
.WithValue(dependencyPropertyValue))
|
||||
.WithProperty(
|
||||
new PropertyTemplate()
|
||||
.WithType(fieldType.ToString())
|
||||
.WithName(propertyName)
|
||||
.WithGetter(
|
||||
new GetterTemplate()
|
||||
.WithCode(
|
||||
new CodeBlockTemplate()
|
||||
.WithLine($"this.{fieldName} = ({fieldType})this.GetValue({propertyName + DependencyPropertySuffix});")
|
||||
.WithLine($"return ({fieldType})this.GetValue({propertyName + DependencyPropertySuffix});")))
|
||||
.WithSetter(
|
||||
new SetterTemplate()
|
||||
.WithCode(
|
||||
new CodeBlockTemplate()
|
||||
.WithLine($"this.{fieldName} = value;")
|
||||
.WithLine($"this.SetValue({propertyName + DependencyPropertySuffix}, value);"))));
|
||||
}
|
||||
|
||||
private static string GenerateName(string fieldName)
|
||||
{
|
||||
fieldName = fieldName.TrimStart('_');
|
||||
if (char.IsUpper(fieldName.First()))
|
||||
{
|
||||
return char.ToLower(fieldName.First()) + fieldName.Substring(1);
|
||||
generatedNamespace = generatedNamespace.AddUsings([.. originalNamespaceSyntax.Usings]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return char.ToUpper(fieldName.First()) + fieldName.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax &&
|
||||
fieldDeclarationSyntax.AttributeLists.Count > 0)
|
||||
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
|
||||
{
|
||||
this.CandidateFields.Add(fieldDeclarationSyntax);
|
||||
generatedNamespace = generatedNamespace.AddUsings([.. compilationUnit.Usings]);
|
||||
}
|
||||
}
|
||||
|
||||
var fileSource = generatedNamespace.ToFullString();
|
||||
sourceProductionContext.AddSource($"{className}.g", fileSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode.Parent is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (syntaxNode.Parent is T parentNode)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
return GetParentOfType<T>(syntaxNode.Parent);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
@@ -7,6 +7,9 @@ namespace System.Extensions
|
||||
{
|
||||
public const string DependencyPropertyGenerator_NoAttributeFound = "DGD 0001";
|
||||
public const string DependencyPropertyGenerator_ClassMustBeTopLevel = "DGD 1001";
|
||||
public const string DependencyPropertyGenerator_ClassMustBePartial = "DGD 1002";
|
||||
public const string DependencyPropertyGenerator_ClassMustBeInNamespace = "DGD 1003";
|
||||
public const string DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged = "DGD 1004";
|
||||
|
||||
public static Diagnostic MissingAttributeDiagnostic(string attributeName, string attributeNamespace) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
@@ -33,5 +36,44 @@ namespace System.Extensions
|
||||
null),
|
||||
Location.Create(syntaxTree, textSpan),
|
||||
className, containingSymbol);
|
||||
|
||||
public static Diagnostic ClassNotPartial(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustBePartial,
|
||||
$"{className} is not partial",
|
||||
"Class {0} must be marked as partial in order for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
|
||||
public static Diagnostic ClassNotInNamespace(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustBeInNamespace,
|
||||
$"{className} is not defined in a namespace",
|
||||
"Class {0} must be defined inside a namespace for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
|
||||
public static Diagnostic ClassDoesNotImplementINotifyPropertyChanged(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged,
|
||||
$"{className} must implement INotifyPropertyChanged",
|
||||
"Class {0} must implement INotifyPropertyChanged for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Sybil;
|
||||
using System.Linq;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace System.Extensions;
|
||||
#nullable enable
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class NotifyPropertyChangedGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateNotifyPropertyChangedAttribute";
|
||||
private const string AttributeShortName = "GenerateNotifyPropertyChanged";
|
||||
private const string PropertyChangedEventHandler = "PropertyChangedEventHandler";
|
||||
private const string NullablePropertyChangedEventHandler = "PropertyChangedEventHandler?";
|
||||
private const string PropertyChanged = "PropertyChanged";
|
||||
private const string Partial = "partial";
|
||||
private const string Public = "public";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
var attributeNamespaceBuilder = SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
||||
.WithUsing("System.ComponentModel")
|
||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
||||
.WithModifier(Public)
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
||||
.WithModifier(Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Field)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithBaseClass(nameof(Attribute)));
|
||||
var attributeSyntax = attributeNamespaceBuilder.Build();
|
||||
var source = attributeSyntax.ToFullString();
|
||||
context.AddSource($"{AttributeName}.g", source);
|
||||
});
|
||||
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is FieldDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
|
||||
if (fieldDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
|
||||
{
|
||||
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation _, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var distinctClasses = classes.Distinct();
|
||||
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
var className = c.Identifier.ToFullString();
|
||||
if (!c.Modifiers.Any(m => m.ToString() == Partial))
|
||||
{
|
||||
Diagnostics.ClassNotPartial(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
|
||||
if (originalNamespaceSyntax is null)
|
||||
{
|
||||
Diagnostics.ClassNotInNamespace(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!c.Members
|
||||
.OfType<EventFieldDeclarationSyntax>()
|
||||
.Any(eventField =>
|
||||
(eventField.Declaration.Variables.Any(variable => variable.Identifier.Text == PropertyChanged) &&
|
||||
eventField.Modifiers.Any(modifier => modifier.Text == Public) &&
|
||||
eventField.Declaration.Type.ToString() == PropertyChangedEventHandler) ||
|
||||
eventField.Declaration.Type.ToString() == NullablePropertyChangedEventHandler))
|
||||
{
|
||||
Diagnostics.ClassDoesNotImplementINotifyPropertyChanged(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
|
||||
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
|
||||
var generatedNamespaceBuilder = SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString())
|
||||
.WithClass(generatedClassBuilder);
|
||||
if (originalNamespaceSyntax.Usings.Count != 0)
|
||||
{
|
||||
foreach (var u in originalNamespaceSyntax.Usings.OfType<UsingDirectiveSyntax>())
|
||||
{
|
||||
generatedNamespaceBuilder.WithUsing(u.Name?.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
|
||||
{
|
||||
foreach(var u in compilationUnit.Usings.OfType<UsingDirectiveSyntax>())
|
||||
{
|
||||
generatedNamespaceBuilder.WithUsing(u.Name?.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
|
||||
{
|
||||
if (field.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName ||
|
||||
s.Name.ToString() == AttributeShortName) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString();
|
||||
var propertyName = fieldName.TrimStart('_');
|
||||
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}";
|
||||
var propertyBuilder = SyntaxBuilder.CreateProperty(field.Declaration.Type.ToFullString(), propertyName)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()
|
||||
.WithBody($"return this.{fieldName};"))
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()
|
||||
.WithBody($"this.{fieldName} = value;\r\nthis.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));"));
|
||||
generatedClassBuilder.WithProperty(propertyBuilder);
|
||||
}
|
||||
|
||||
var fileSource = generatedNamespaceBuilder.Build().ToFullString();
|
||||
sourceProductionContext.AddSource($"{className}.g", fileSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode.Parent is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (syntaxNode.Parent is T parentNode)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
return GetParentOfType<T>(syntaxNode.Parent);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class AbstractTemplate
|
||||
{
|
||||
public abstract void Generate(CodeWriter codeWriter);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ArgumentTemplate : AbstractTemplate
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public ArgumentTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class AttributeTemplate : AbstractTemplate
|
||||
{
|
||||
public string Attribute { get; set; }
|
||||
|
||||
public AttributeTemplate WithAttribute(string attribute)
|
||||
{
|
||||
this.Attribute = attribute;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append('[')
|
||||
.Append(this.Attribute)
|
||||
.Append(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ClassTemplate : AbstractTemplate
|
||||
{
|
||||
public List<AttributeTemplate> Attributes { get; } = new List<AttributeTemplate>();
|
||||
public List<UsingsTemplate> Usings { get; } = new List<UsingsTemplate>();
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<PropertyTemplate> Properties { get; } = new List<PropertyTemplate>();
|
||||
public List<FieldTemplate> Fields { get; } = new List<FieldTemplate>();
|
||||
public List<MethodTemplate> Methods { get; } = new List<MethodTemplate>();
|
||||
public List<ConstructorTemplate> Constructors { get; } = new List<ConstructorTemplate>();
|
||||
public NamespaceTemplate Namespace { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Base { get; set; }
|
||||
|
||||
public ClassTemplate WithAttributes(params AttributeTemplate[] attributeTemplates)
|
||||
{
|
||||
this.Attributes.Clear();
|
||||
this.Attributes.AddRange(attributeTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithAttribute(AttributeTemplate attributeTemplate)
|
||||
{
|
||||
this.Attributes.Add(attributeTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithUsings(params UsingsTemplate[] usingsTemplates)
|
||||
{
|
||||
this.Usings.Clear();
|
||||
this.Usings.AddRange(usingsTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithUsing(UsingsTemplate usingsTemplate)
|
||||
{
|
||||
this.Usings.Add(usingsTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithModifier(Modifier modifier)
|
||||
{
|
||||
this.Modifiers.Add(modifier);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithMethods(params MethodTemplate[] methodTemplates)
|
||||
{
|
||||
this.Methods.Clear();
|
||||
this.Methods.AddRange(methodTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithMethod(MethodTemplate methodTemplate)
|
||||
{
|
||||
this.Methods.Add(methodTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithFields(params FieldTemplate[] fieldTemplates)
|
||||
{
|
||||
this.Fields.Clear();
|
||||
this.Fields.AddRange(fieldTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithField(FieldTemplate fieldTemplate)
|
||||
{
|
||||
this.Fields.Add(fieldTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithProperties(params PropertyTemplate[] propertyTemplates)
|
||||
{
|
||||
this.Properties.Clear();
|
||||
this.Properties.AddRange(propertyTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithProperty(PropertyTemplate propertyTemplate)
|
||||
{
|
||||
this.Properties.Add(propertyTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithConstructors(params ConstructorTemplate[] constructorTemplates)
|
||||
{
|
||||
this.Constructors.Clear();
|
||||
this.Constructors.AddRange(constructorTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithConstructor(ConstructorTemplate constructorTemplate)
|
||||
{
|
||||
this.Constructors.Add(constructorTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithNamespace(NamespaceTemplate namespaceTemplate)
|
||||
{
|
||||
this.Namespace = namespaceTemplate;
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithBase(string b)
|
||||
{
|
||||
this.Base = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var u in this.Usings)
|
||||
{
|
||||
codeWriter
|
||||
.Append(u)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.AppendLine()
|
||||
.Append(this.Namespace)
|
||||
.BeginCodeBlock();
|
||||
foreach(var attribute in this.Attributes)
|
||||
{
|
||||
codeWriter
|
||||
.Append(attribute)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append("class")
|
||||
.Append(' ')
|
||||
.Append(this.Name);
|
||||
if (!string.IsNullOrEmpty(this.Base))
|
||||
{
|
||||
codeWriter
|
||||
.Append(' ')
|
||||
.Append(':')
|
||||
.Append(' ')
|
||||
.Append(this.Base);
|
||||
}
|
||||
|
||||
codeWriter.BeginCodeBlock();
|
||||
foreach(var field in this.Fields)
|
||||
{
|
||||
codeWriter
|
||||
.Append(field)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var property in this.Properties)
|
||||
{
|
||||
codeWriter
|
||||
.Append(property)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var constructor in this.Constructors)
|
||||
{
|
||||
codeWriter
|
||||
.Append(constructor)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var method in this.Methods)
|
||||
{
|
||||
codeWriter
|
||||
.Append(method)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
codeWriter.EndCodeBlock().EndCodeBlock();
|
||||
}
|
||||
|
||||
public string GenerateString()
|
||||
{
|
||||
var codeWriter = new CodeWriter();
|
||||
this.Generate(codeWriter);
|
||||
return codeWriter.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class CodeBlockTemplate : CodeTemplate
|
||||
{
|
||||
public List<string> Lines { get; set; } = new List<string>();
|
||||
|
||||
public CodeBlockTemplate WithLines(params string[] lines)
|
||||
{
|
||||
this.Lines.Clear();
|
||||
this.Lines.AddRange(lines);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlockTemplate WithLine(string line)
|
||||
{
|
||||
this.Lines.Add(line);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var line in this.Lines)
|
||||
{
|
||||
codeWriter.AppendLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class CodeTemplate : AbstractTemplate
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ConstructorTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<ArgumentTemplate> Arguments { get; } = new List<ArgumentTemplate>();
|
||||
public string Type { get; set; }
|
||||
public string Body { get; set; }
|
||||
public string Base { get; set; }
|
||||
|
||||
public ConstructorTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithBody(string body)
|
||||
{
|
||||
this.Body = body;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithBase(string b)
|
||||
{
|
||||
this.Base = b;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithArguments(params ArgumentTemplate[] argumentTemplates)
|
||||
{
|
||||
this.Arguments.Clear();
|
||||
this.Arguments.AddRange(argumentTemplates);
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithArgument(ArgumentTemplate argumentTemplate)
|
||||
{
|
||||
this.Arguments.Add(argumentTemplate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append('(');
|
||||
for (int i = 0; i < this.Arguments.Count; i++)
|
||||
{
|
||||
var argument = this.Arguments[i];
|
||||
codeWriter.Append(argument);
|
||||
if (i < this.Arguments.Count - 1)
|
||||
{
|
||||
codeWriter.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
codeWriter.Append(')');
|
||||
if (!string.IsNullOrEmpty(this.Base))
|
||||
{
|
||||
codeWriter
|
||||
.Append(" : base(")
|
||||
.Append(this.Base)
|
||||
.Append(')');
|
||||
}
|
||||
|
||||
codeWriter.BeginCodeBlock()
|
||||
.Append(this.Body)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class FieldTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier> { Modifier.Public };
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public FieldTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithValue(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.Append(' ')
|
||||
.Append('=')
|
||||
.Append(' ')
|
||||
.Append(this.Value)
|
||||
.Append(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class GetterTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public CodeTemplate Code { get; set; }
|
||||
|
||||
public GetterTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public GetterTemplate WithCode(CodeTemplate codeTemplate)
|
||||
{
|
||||
this.Code = codeTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append("get");
|
||||
if (this.Code is SimpleCodeTemplate simpleGetter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(simpleGetter)
|
||||
.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeWriter
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Code)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class MethodTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<ArgumentTemplate> Arguments { get; } = new List<ArgumentTemplate>();
|
||||
public string Name { get; set; }
|
||||
public string ReturnType { get; set; }
|
||||
public string Body { get; set; }
|
||||
|
||||
public MethodTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithArguments(params ArgumentTemplate[] argumentTemplates)
|
||||
{
|
||||
this.Arguments.Clear();
|
||||
this.Arguments.AddRange(argumentTemplates);
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithReturnType(string returnType)
|
||||
{
|
||||
this.ReturnType = returnType;
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithBody(string body)
|
||||
{
|
||||
this.Body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.ReturnType)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.Append('(');
|
||||
for(int i = 0; i < this.Arguments.Count; i++)
|
||||
{
|
||||
var argument = this.Arguments[i];
|
||||
codeWriter.Append(argument);
|
||||
if (i < this.Arguments.Count - 1)
|
||||
{
|
||||
codeWriter.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(')')
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Body)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class Modifier : AbstractTemplate
|
||||
{
|
||||
public static Modifier Protected { get; } = new Modifier { Value = "protected" };
|
||||
public static Modifier Readonly { get; } = new Modifier { Value = "readonly" };
|
||||
public static Modifier Static { get; } = new Modifier { Value = "static" };
|
||||
public static Modifier Public { get; } = new Modifier { Value = "public" };
|
||||
public static Modifier Internal { get; } = new Modifier { Value = "internal" };
|
||||
public static Modifier Private { get; } = new Modifier { Value = "private" };
|
||||
public static Modifier Abstract { get; } = new Modifier { Value = "abstract" };
|
||||
public static Modifier Virtual { get; } = new Modifier { Value = "virtual" };
|
||||
public static Modifier Partial { get; } = new Modifier { Value = "partial" };
|
||||
public static Modifier Sealed { get; } = new Modifier { Value = "sealed" };
|
||||
|
||||
public string Value { get; private set; }
|
||||
|
||||
private Modifier()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter.Append(this.Value);
|
||||
}
|
||||
|
||||
public static bool TryParse(string value, out Modifier modifier)
|
||||
{
|
||||
foreach(var supportedModifier in SupportedModifiers)
|
||||
{
|
||||
if (supportedModifier.Value.Equals(value, StringComparison.Ordinal))
|
||||
{
|
||||
modifier = supportedModifier;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
modifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Modifier Parse(string value)
|
||||
{
|
||||
if (TryParse(value, out var modifier))
|
||||
{
|
||||
return modifier;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find a modifier with value {value}");
|
||||
}
|
||||
|
||||
private static IEnumerable<Modifier> SupportedModifiers { get; } = new List<Modifier> { Protected, Readonly, Static, Internal, Public, Private, Abstract, Virtual, Partial, Sealed };
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class NamespaceTemplate : AbstractTemplate
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
public NamespaceTemplate WithNamespace(string n)
|
||||
{
|
||||
this.Namespace = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append("namespace ")
|
||||
.Append(this.Namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class PropertyTemplate : AbstractTemplate
|
||||
{
|
||||
public static GetterTemplate DefaultGetter = new GetterTemplate()
|
||||
.WithCode(
|
||||
new SimpleCodeTemplate()
|
||||
.WithCode(";"));
|
||||
public static SetterTemplate DefaultSetter = new SetterTemplate()
|
||||
.WithCode(
|
||||
new SimpleCodeTemplate()
|
||||
.WithCode(";"));
|
||||
public List<Modifier> Modifiers { get; private set; } = new List<Modifier>() { Modifier.Public };
|
||||
public GetterTemplate Getter { get; set; } = DefaultGetter;
|
||||
public SetterTemplate Setter { get; set; } = DefaultSetter;
|
||||
public string Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public PropertyTemplate WithGetter(GetterTemplate getter)
|
||||
{
|
||||
this.Getter = getter;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithSetter(SetterTemplate setter)
|
||||
{
|
||||
this.Setter = setter;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Getter)
|
||||
.Append(this.Setter)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class SetterTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public CodeTemplate Code { get; set; }
|
||||
|
||||
public SetterTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public SetterTemplate WithCode(CodeTemplate codeTemplate)
|
||||
{
|
||||
this.Code = codeTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append("set");
|
||||
if (this.Code is SimpleCodeTemplate simpleSetter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(simpleSetter)
|
||||
.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeWriter
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Code)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class SimpleCodeTemplate : CodeTemplate
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public SimpleCodeTemplate WithCode(string code)
|
||||
{
|
||||
this.Code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter.Append(this.Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class UsingsTemplate : AbstractTemplate
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
public UsingsTemplate WithNamespace(string n)
|
||||
{
|
||||
this.Namespace = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append("using ")
|
||||
.Append(this.Namespace)
|
||||
.Append(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,20 @@
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Description>Source generator library.</Description>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Version>0.1.2</Version>
|
||||
<Version>0.2.0</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Sybil" Version="0.5.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -29,6 +33,7 @@
|
||||
<ItemGroup>
|
||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<StartupObject>WpfExtended.Tests.Launcher</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-windows;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;netcoreapp3.1</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
|
||||
Reference in New Issue
Block a user