From 7d3c2fbb9bf2ea05c8d7ddd987d4f1b2cd1f4ca0 Mon Sep 17 00:00:00 2001 From: Alexandru Macocian Date: Tue, 6 Apr 2021 10:40:06 +0200 Subject: [PATCH] Alpha implementation of dependency property source generator. Testing project for source generator --- WpfExtended.SourceGeneration.Tests/Class1.cs | 11 ++ .../Launcher.cs | 10 + .../WpfExtended.SourceGeneration.Tests.csproj | 16 ++ WpfExtended.SourceGeneration/CodeWriter.cs | 143 ++++++++++++++ .../DependencyPropertyGenerator.cs | 186 ++++++++++++++++++ WpfExtended.SourceGeneration/LICENSE | 21 ++ .../Templates/AbstractTemplate.cs | 7 + .../Templates/ArgumentTemplate.cs | 28 +++ .../Templates/AttributeTemplate.cs | 21 ++ .../Templates/ClassTemplate.cs | 185 +++++++++++++++++ .../Templates/CodeBlockTemplate.cs | 30 +++ .../Templates/CodeTemplate.cs | 6 + .../Templates/ConstructorTemplate.cs | 82 ++++++++ .../Templates/FieldTemplate.cs | 55 ++++++ .../Templates/GetterTemplate.cs | 48 +++++ .../Templates/MethodTemplate.cs | 72 +++++++ .../Templates/Modifiers.cs | 26 +++ .../Templates/NamespaceTemplate.cs | 19 ++ .../Templates/PropertyTemplate.cs | 67 +++++++ .../Templates/SetterTemplate.cs | 48 +++++ .../Templates/SimpleCodeTemplate.cs | 18 ++ .../Templates/UsingsTemplate.cs | 20 ++ .../WpfExtended.SourceGeneration.csproj | 29 +++ WpfExtended.Test/Launcher.cs | 2 +- WpfExtended.sln | 14 +- WpfExtended/WpfExtended.csproj | 2 +- 26 files changed, 1163 insertions(+), 3 deletions(-) create mode 100644 WpfExtended.SourceGeneration.Tests/Class1.cs create mode 100644 WpfExtended.SourceGeneration.Tests/Launcher.cs create mode 100644 WpfExtended.SourceGeneration.Tests/WpfExtended.SourceGeneration.Tests.csproj create mode 100644 WpfExtended.SourceGeneration/CodeWriter.cs create mode 100644 WpfExtended.SourceGeneration/DependencyPropertyGenerator.cs create mode 100644 WpfExtended.SourceGeneration/LICENSE create mode 100644 WpfExtended.SourceGeneration/Templates/AbstractTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/ArgumentTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/AttributeTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/ClassTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/CodeBlockTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/CodeTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/ConstructorTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/FieldTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/GetterTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/MethodTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/Modifiers.cs create mode 100644 WpfExtended.SourceGeneration/Templates/NamespaceTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/PropertyTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/SetterTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/SimpleCodeTemplate.cs create mode 100644 WpfExtended.SourceGeneration/Templates/UsingsTemplate.cs create mode 100644 WpfExtended.SourceGeneration/WpfExtended.SourceGeneration.csproj diff --git a/WpfExtended.SourceGeneration.Tests/Class1.cs b/WpfExtended.SourceGeneration.Tests/Class1.cs new file mode 100644 index 0000000..aae4cf8 --- /dev/null +++ b/WpfExtended.SourceGeneration.Tests/Class1.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; +using System.Windows.Extensions; + +namespace WpfExtended.SourceGeneration.Tests +{ + public partial class Class1 : UserControl + { + [GenerateDependencyProperty] + public int someF; + } +} diff --git a/WpfExtended.SourceGeneration.Tests/Launcher.cs b/WpfExtended.SourceGeneration.Tests/Launcher.cs new file mode 100644 index 0000000..dc5b595 --- /dev/null +++ b/WpfExtended.SourceGeneration.Tests/Launcher.cs @@ -0,0 +1,10 @@ +namespace WpfExtended.SourceGeneration.Tests +{ + public class Launcher + { + public static int Main() + { + return 0; + } + } +} diff --git a/WpfExtended.SourceGeneration.Tests/WpfExtended.SourceGeneration.Tests.csproj b/WpfExtended.SourceGeneration.Tests/WpfExtended.SourceGeneration.Tests.csproj new file mode 100644 index 0000000..0ae3cd6 --- /dev/null +++ b/WpfExtended.SourceGeneration.Tests/WpfExtended.SourceGeneration.Tests.csproj @@ -0,0 +1,16 @@ + + + + net5.0-windows + true + preview + Exe + true + obj + + + + + + + diff --git a/WpfExtended.SourceGeneration/CodeWriter.cs b/WpfExtended.SourceGeneration/CodeWriter.cs new file mode 100644 index 0000000..a4d7ad7 --- /dev/null +++ b/WpfExtended.SourceGeneration/CodeWriter.cs @@ -0,0 +1,143 @@ +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; + } + } + } +} diff --git a/WpfExtended.SourceGeneration/DependencyPropertyGenerator.cs b/WpfExtended.SourceGeneration/DependencyPropertyGenerator.cs new file mode 100644 index 0000000..345fadb --- /dev/null +++ b/WpfExtended.SourceGeneration/DependencyPropertyGenerator.cs @@ -0,0 +1,186 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Extensions.Templates; + +namespace System.Extensions +{ + [Generator] + public class DependencyPropertyGenerator : ISourceGenerator + { + 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) + { + // Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + var attributeText = GenerateDependencyPropertyAttribute.GenerateString(); + context.AddSource("GenerateDependencyPropertyAttribute.g", attributeText); + + 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) + { + return; + } + + var fieldSymbols = new List(); + foreach (var field in receiver.CandidateFields) + { + var model = compilation.GetSemanticModel(field.SyntaxTree); + foreach (var variable in field.Declaration.Variables) + { + var fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol; + if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))) + { + fieldSymbols.Add(fieldSymbol); + } + } + } + + foreach (var group in fieldSymbols.GroupBy(f => f.ContainingType, SymbolEqualityComparer.IncludeNullability)) + { + var classSource = ProcessClass((INamedTypeSymbol)group.Key, attributeSymbol, group.ToList()); + context.AddSource($"{group.Key.Name}.DependencyPropertyGenerator.g.cs", classSource); + } + } + + private static string ProcessClass(INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, List fields) + { + if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) + { + return null; //TODO: issue a diagnostic that it must be top level + } + + var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + + var source = new ClassTemplate() + .WithUsings( + new UsingsTemplate() + .WithNamespace("System"), + new UsingsTemplate() + .WithNamespace("System.Windows")) + .WithNamespace( + new NamespaceTemplate() + .WithNamespace(namespaceName)) + .WithModifiers(Modifier.Public, Modifier.Partial) + .WithName(classSymbol.Name); + foreach (var fieldSymbol in fields) + { + ProcessField(source, classSymbol, attributeSymbol, fieldSymbol); + } + + return source.GenerateString(); + } + + private static void ProcessField(ClassTemplate source, INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, IFieldSymbol fieldSymbol) + { + var fieldName = fieldSymbol.Name; + var fieldType = fieldSymbol.Type; + + string propertyName = GenerateName(fieldName); + if (propertyName.Length == 0 || propertyName == fieldName) + { + //TODO: issue a diagnostic that we can't process this field + return; + } + + 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); + } + else + { + return char.ToUpper(fieldName.First()) + fieldName.Substring(1); + } + } + + internal class SyntaxReceiver : ISyntaxReceiver + { + public List CandidateFields { get; } = new List(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax && + fieldDeclarationSyntax.AttributeLists.Count > 0) + { + this.CandidateFields.Add(fieldDeclarationSyntax); + } + } + } + } +} diff --git a/WpfExtended.SourceGeneration/LICENSE b/WpfExtended.SourceGeneration/LICENSE new file mode 100644 index 0000000..7b4b612 --- /dev/null +++ b/WpfExtended.SourceGeneration/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Macocian Alexandru Victor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/WpfExtended.SourceGeneration/Templates/AbstractTemplate.cs b/WpfExtended.SourceGeneration/Templates/AbstractTemplate.cs new file mode 100644 index 0000000..81005bf --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/AbstractTemplate.cs @@ -0,0 +1,7 @@ +namespace System.Extensions.Templates +{ + internal abstract class AbstractTemplate + { + public abstract void Generate(CodeWriter codeWriter); + } +} diff --git a/WpfExtended.SourceGeneration/Templates/ArgumentTemplate.cs b/WpfExtended.SourceGeneration/Templates/ArgumentTemplate.cs new file mode 100644 index 0000000..6c35c60 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/ArgumentTemplate.cs @@ -0,0 +1,28 @@ +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); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/AttributeTemplate.cs b/WpfExtended.SourceGeneration/Templates/AttributeTemplate.cs new file mode 100644 index 0000000..24df7a8 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/AttributeTemplate.cs @@ -0,0 +1,21 @@ +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(']'); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/ClassTemplate.cs b/WpfExtended.SourceGeneration/Templates/ClassTemplate.cs new file mode 100644 index 0000000..a11d3df --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/ClassTemplate.cs @@ -0,0 +1,185 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class ClassTemplate : AbstractTemplate + { + public List Attributes { get; } = new List(); + public List Usings { get; } = new List(); + public List Modifiers { get; } = new List(); + public List Properties { get; } = new List(); + public List Fields { get; } = new List(); + public List Methods { get; } = new List(); + public List Constructors { get; } = new List(); + 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 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(); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/CodeBlockTemplate.cs b/WpfExtended.SourceGeneration/Templates/CodeBlockTemplate.cs new file mode 100644 index 0000000..c36bdcd --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/CodeBlockTemplate.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class CodeBlockTemplate : CodeTemplate + { + public List Lines { get; set; } = new List(); + + 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); + } + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/CodeTemplate.cs b/WpfExtended.SourceGeneration/Templates/CodeTemplate.cs new file mode 100644 index 0000000..2c08335 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/CodeTemplate.cs @@ -0,0 +1,6 @@ +namespace System.Extensions.Templates +{ + internal abstract class CodeTemplate : AbstractTemplate + { + } +} diff --git a/WpfExtended.SourceGeneration/Templates/ConstructorTemplate.cs b/WpfExtended.SourceGeneration/Templates/ConstructorTemplate.cs new file mode 100644 index 0000000..a333abc --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/ConstructorTemplate.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class ConstructorTemplate : AbstractTemplate + { + public List Modifiers { get; } = new List(); + public List Arguments { get; } = new List(); + 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(); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/FieldTemplate.cs b/WpfExtended.SourceGeneration/Templates/FieldTemplate.cs new file mode 100644 index 0000000..0c5da93 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/FieldTemplate.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Extensions.Templates +{ + internal sealed class FieldTemplate : AbstractTemplate + { + public List Modifiers { get; } = new List { 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(';'); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/GetterTemplate.cs b/WpfExtended.SourceGeneration/Templates/GetterTemplate.cs new file mode 100644 index 0000000..78a1bb7 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/GetterTemplate.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class GetterTemplate : AbstractTemplate + { + public List Modifiers { get; } = new List(); + 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(); + } + + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/MethodTemplate.cs b/WpfExtended.SourceGeneration/Templates/MethodTemplate.cs new file mode 100644 index 0000000..3132947 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/MethodTemplate.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class MethodTemplate : AbstractTemplate + { + public List Modifiers { get; } = new List(); + public List Arguments { get; } = new List(); + 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(); + } + } +} \ No newline at end of file diff --git a/WpfExtended.SourceGeneration/Templates/Modifiers.cs b/WpfExtended.SourceGeneration/Templates/Modifiers.cs new file mode 100644 index 0000000..e3745d4 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/Modifiers.cs @@ -0,0 +1,26 @@ +namespace System.Extensions.Templates +{ + internal sealed class Modifier : AbstractTemplate + { + 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); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/NamespaceTemplate.cs b/WpfExtended.SourceGeneration/Templates/NamespaceTemplate.cs new file mode 100644 index 0000000..b131ea1 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/NamespaceTemplate.cs @@ -0,0 +1,19 @@ +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); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/PropertyTemplate.cs b/WpfExtended.SourceGeneration/Templates/PropertyTemplate.cs new file mode 100644 index 0000000..4639eaf --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/PropertyTemplate.cs @@ -0,0 +1,67 @@ +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 Modifiers { get; private set; } = new List() { 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(); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/SetterTemplate.cs b/WpfExtended.SourceGeneration/Templates/SetterTemplate.cs new file mode 100644 index 0000000..021ef91 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/SetterTemplate.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace System.Extensions.Templates +{ + internal sealed class SetterTemplate : AbstractTemplate + { + public List Modifiers { get; } = new List(); + 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(); + } + + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/SimpleCodeTemplate.cs b/WpfExtended.SourceGeneration/Templates/SimpleCodeTemplate.cs new file mode 100644 index 0000000..2aabd4f --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/SimpleCodeTemplate.cs @@ -0,0 +1,18 @@ +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); + } + } +} diff --git a/WpfExtended.SourceGeneration/Templates/UsingsTemplate.cs b/WpfExtended.SourceGeneration/Templates/UsingsTemplate.cs new file mode 100644 index 0000000..425e470 --- /dev/null +++ b/WpfExtended.SourceGeneration/Templates/UsingsTemplate.cs @@ -0,0 +1,20 @@ +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(';'); + } + } +} diff --git a/WpfExtended.SourceGeneration/WpfExtended.SourceGeneration.csproj b/WpfExtended.SourceGeneration/WpfExtended.SourceGeneration.csproj new file mode 100644 index 0000000..4aa4469 --- /dev/null +++ b/WpfExtended.SourceGeneration/WpfExtended.SourceGeneration.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + true + true + Alexandru Macocian + Source generator library. + LICENSE + 0.1 + latest + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + True + + + + + diff --git a/WpfExtended.Test/Launcher.cs b/WpfExtended.Test/Launcher.cs index a28f084..f1a25cf 100644 --- a/WpfExtended.Test/Launcher.cs +++ b/WpfExtended.Test/Launcher.cs @@ -11,7 +11,7 @@ namespace WpfExtended.Tests [STAThread] public static int Main() - { + { return Instance.Run(); } diff --git a/WpfExtended.sln b/WpfExtended.sln index e66ed47..5074308 100644 --- a/WpfExtended.sln +++ b/WpfExtended.sln @@ -5,7 +5,11 @@ VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended", "WpfExtended\WpfExtended.csproj", "{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfExtended.Tests", "WpfExtended.Test\WpfExtended.Tests.csproj", "{48D2B8E6-F906-40D8-952E-B7B567D76A9B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended.Tests", "WpfExtended.Test\WpfExtended.Tests.csproj", "{48D2B8E6-F906-40D8-952E-B7B567D76A9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfExtended.SourceGeneration", "WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj", "{BB890187-DF49-4E58-AE41-09C40F541292}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfExtended.SourceGeneration.Tests", "WpfExtended.SourceGeneration.Tests\WpfExtended.SourceGeneration.Tests.csproj", "{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +25,14 @@ Global {48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU {48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Release|Any CPU.Build.0 = Release|Any CPU + {BB890187-DF49-4E58-AE41-09C40F541292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB890187-DF49-4E58-AE41-09C40F541292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB890187-DF49-4E58-AE41-09C40F541292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB890187-DF49-4E58-AE41-09C40F541292}.Release|Any CPU.Build.0 = Release|Any CPU + {BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WpfExtended/WpfExtended.csproj b/WpfExtended/WpfExtended.csproj index a4aae2b..91fc746 100644 --- a/WpfExtended/WpfExtended.csproj +++ b/WpfExtended/WpfExtended.csproj @@ -1,7 +1,7 @@  - net5.0-windows + net5.0-windows;netcoreapp3.1 true true true