mirror of
https://github.com/AlexMacocian/WpfExtended.git
synced 2026-07-15 14:59:30 +00:00
Alpha implementation of dependency property source generator.
Testing project for source generator
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace WpfExtended.SourceGeneration.Tests
|
||||
{
|
||||
public class Launcher
|
||||
{
|
||||
public static int Main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>obj</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IFieldSymbol>();
|
||||
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<IFieldSymbol> 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<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax &&
|
||||
fieldDeclarationSyntax.AttributeLists.Count > 0)
|
||||
{
|
||||
this.CandidateFields.Add(fieldDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class AbstractTemplate
|
||||
{
|
||||
public abstract void Generate(CodeWriter codeWriter);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class CodeTemplate : AbstractTemplate
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Description>Source generator library.</Description>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Version>0.1</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -11,7 +11,7 @@ namespace WpfExtended.Tests
|
||||
|
||||
[STAThread]
|
||||
public static int Main()
|
||||
{
|
||||
{
|
||||
return Instance.Run();
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<TargetFrameworks>net5.0-windows;netcoreapp3.1</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
|
||||
Reference in New Issue
Block a user