|
|
|
@ -15,10 +15,12 @@
|
|
|
|
|
//
|
|
|
|
|
// ******************************************************************
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
|
|
@ -36,6 +38,9 @@ namespace Uno
|
|
|
|
|
private INamedTypeSymbol _immutableAttributeSymbol;
|
|
|
|
|
private INamedTypeSymbol _generatedImmutableAttributeSymbol;
|
|
|
|
|
private INamedTypeSymbol _immutableBuilderAttributeSymbol;
|
|
|
|
|
private INamedTypeSymbol _immutableAttributeCopyIgnoreAttributeSymbol;
|
|
|
|
|
|
|
|
|
|
private Regex[] _copyIgnoreAttributeRegexes;
|
|
|
|
|
|
|
|
|
|
public override void Execute(SourceGeneratorContext context)
|
|
|
|
|
{
|
|
|
|
@ -44,13 +49,20 @@ namespace Uno
|
|
|
|
|
_immutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttribute");
|
|
|
|
|
_generatedImmutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.GeneratedImmutableAttribute");
|
|
|
|
|
_immutableBuilderAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableBuilderAttribute");
|
|
|
|
|
_immutableAttributeCopyIgnoreAttributeSymbol =
|
|
|
|
|
context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttributeCopyIgnoreAttribute");
|
|
|
|
|
|
|
|
|
|
var generationData = EnumerateImmutableGeneratedEntities()
|
|
|
|
|
.OrderBy(x=>x.symbol.Name)
|
|
|
|
|
.OrderBy(x => x.symbol.Name)
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
var immutableEntitiesToGenerate = generationData.Select(x => x.Item1).ToArray();
|
|
|
|
|
|
|
|
|
|
_copyIgnoreAttributeRegexes =
|
|
|
|
|
ExtractCopyIgnoreAttributes(context.Compilation.Assembly)
|
|
|
|
|
.Concat(new[] {new Regex(@"^Uno\.Immutable"), new Regex(@"^Uno\.Equality")})
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
foreach ((var type, var moduleAttribute) in generationData)
|
|
|
|
|
{
|
|
|
|
|
var baseTypeInfo = GetTypeInfo(context, type, immutableEntitiesToGenerate);
|
|
|
|
@ -61,6 +73,13 @@ namespace Uno
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<Regex> ExtractCopyIgnoreAttributes(ISymbol symbol)
|
|
|
|
|
{
|
|
|
|
|
return symbol.GetAttributes()
|
|
|
|
|
.Where(a => a.AttributeClass.Equals(_immutableAttributeCopyIgnoreAttributeSymbol))
|
|
|
|
|
.Select(a => new Regex(a.ConstructorArguments[0].Value.ToString()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool GetShouldGenerateEquality(AttributeData attribute)
|
|
|
|
|
{
|
|
|
|
|
return attribute.NamedArguments
|
|
|
|
@ -112,31 +131,40 @@ namespace Uno
|
|
|
|
|
{
|
|
|
|
|
var defaultMemberName = "Default";
|
|
|
|
|
|
|
|
|
|
var classCopyIgnoreRegexes = ExtractCopyIgnoreAttributes(typeSymbol).ToArray();
|
|
|
|
|
|
|
|
|
|
var builder = new IndentedStringBuilder();
|
|
|
|
|
|
|
|
|
|
var (symbolName, symbolNameWithGenerics, symbolNameForXml, symbolNameDefinition, resultFileName) = typeSymbol.GetSymbolNames();
|
|
|
|
|
var (symbolName, symbolNameWithGenerics, symbolNameForXml, symbolNameDefinition, resultFileName) =
|
|
|
|
|
typeSymbol.GetSymbolNames();
|
|
|
|
|
|
|
|
|
|
if (!IsFromPartialDeclaration(typeSymbol))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"#warning {nameof(ImmutableGenerator)}: you should add the partial modifier to the class {symbolNameWithGenerics}.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"#warning {nameof(ImmutableGenerator)}: you should add the partial modifier to the class {symbolNameWithGenerics}.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeSymbol.IsValueType)
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** be a class, not a struct.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** be a class, not a struct.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (baseTypeInfo.isBaseType && baseTypeInfo.baseType == null)
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** derive from an immutable class.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"#error {nameof(ImmutableGenerator)}: Type {symbolNameWithGenerics} **MUST** derive from an immutable class.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.AppendLineInvariant("using System;");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
builder.AppendLineInvariant("// <autogenerated>");
|
|
|
|
|
builder.AppendLineInvariant("// *****************************************************************************************************************");
|
|
|
|
|
builder.AppendLineInvariant("// This has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/nventive/Uno.CodeGen");
|
|
|
|
|
builder.AppendLineInvariant("// *****************************************************************************************************************");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"// *****************************************************************************************************************");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"// This has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/nventive/Uno.CodeGen");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"// *****************************************************************************************************************");
|
|
|
|
|
builder.AppendLineInvariant("// </autogenerated>");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
@ -154,22 +182,27 @@ namespace Uno
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant("[global::Uno.Immutable] // Mark this class as Immutable for some analyzers requiring it.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"[global::Uno.Immutable] // Mark this class as Immutable for some analyzers requiring it.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (generateEquality)
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant("[global::Uno.GeneratedEquality] // Set [GeneratedImmutable(GeneratedEquality = false)] if you don't want this attribute.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"[global::Uno.GeneratedEquality] // Set [GeneratedImmutable(GeneratedEquality = false)] if you don't want this attribute.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.AppendLineInvariant($"[global::Uno.ImmutableBuilder(typeof({symbolNameDefinition}.Builder))] // Other generators can use this to find the builder.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"[global::Uno.ImmutableBuilder(typeof({symbolNameDefinition}.Builder))] // Other generators can use this to find the builder.");
|
|
|
|
|
|
|
|
|
|
using (builder.BlockInvariant($"{typeSymbol.GetAccessibilityAsCSharpCodeString()} partial class {symbolNameWithGenerics}"))
|
|
|
|
|
using (builder.BlockInvariant(
|
|
|
|
|
$"{typeSymbol.GetAccessibilityAsCSharpCodeString()} partial class {symbolNameWithGenerics}"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"/// <summary>");
|
|
|
|
|
builder.AppendLineInvariant($"/// {defaultMemberName} instance with only property initializers set.");
|
|
|
|
|
builder.AppendLineInvariant($"/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant($"public static readonly {newModifier}{symbolNameWithGenerics} {defaultMemberName} = new {symbolNameWithGenerics}();");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"public static readonly {newModifier}{symbolNameWithGenerics} {defaultMemberName} = new {symbolNameWithGenerics}();");
|
|
|
|
|
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
@ -180,7 +213,6 @@ namespace Uno
|
|
|
|
|
var baseProperties = typeSymbol.BaseType.GetProperties()
|
|
|
|
|
.Where(x => x.IsReadOnly && IsAutoProperty(x))
|
|
|
|
|
.Select(x => x.Name)
|
|
|
|
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
properties = typeSymbol
|
|
|
|
@ -207,16 +239,21 @@ namespace Uno
|
|
|
|
|
builder.AppendLineInvariant($"/// use the .WithXXX() methods to do it in a fluent way. You can continue to");
|
|
|
|
|
builder.AppendLineInvariant($"/// change it even after calling the `.ToImmutable()` method: it will simply");
|
|
|
|
|
builder.AppendLineInvariant($"/// generate a new version from the current state.");
|
|
|
|
|
builder.AppendLineInvariant($"/// **THE BUILDER IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// **THE BUILDER IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant($"/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// <example>");
|
|
|
|
|
builder.AppendLineInvariant($"/// // The following code will create a builder using a .With{prop1Name}() method:");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"/// {symbolNameForXml}.Builder b = my{symbolName}Instance.With{prop1Name}([{prop1Name} value]);");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"/// {symbolNameForXml}.Builder b = my{symbolName}Instance.With{prop1Name}([{prop1Name} value]);");
|
|
|
|
|
builder.AppendLineInvariant("///");
|
|
|
|
|
builder.AppendLineInvariant($"/// // The following code will use implicit cast to create a new {symbolNameForXml} immutable instance:");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"/// {symbolNameForXml} my{symbolName}Instance = new {symbolNameForXml}.Builder {{ {prop1Name} = [{prop1Name} value], ... }};");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// // The following code will use implicit cast to create a new {symbolNameForXml} immutable instance:");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"/// {symbolNameForXml} my{symbolName}Instance = new {symbolNameForXml}.Builder {{ {prop1Name} = [{prop1Name} value], ... }};");
|
|
|
|
|
builder.AppendLineInvariant("/// </example>");
|
|
|
|
|
using (builder.BlockInvariant($"{typeSymbol.GetAccessibilityAsCSharpCodeString()} {newModifier}class {builderTypeNameAndBaseClass}"))
|
|
|
|
|
using (builder.BlockInvariant(
|
|
|
|
|
$"{typeSymbol.GetAccessibilityAsCSharpCodeString()} {newModifier}class {builderTypeNameAndBaseClass}"))
|
|
|
|
|
{
|
|
|
|
|
if (!baseTypeInfo.isBaseType)
|
|
|
|
|
{
|
|
|
|
@ -228,13 +265,15 @@ namespace Uno
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"internal readonly {symbolNameWithGenerics} _original;");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
builder.AppendLineInvariant("// Cached version of generated entity (flushed when the builder is updated)");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"protected {symbolNameWithGenerics} _cachedResult = default({symbolNameWithGenerics});");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"protected {symbolNameWithGenerics} _cachedResult = default({symbolNameWithGenerics});");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
using (builder.BlockInvariant($"public Builder({symbolNameWithGenerics} original)"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"_original = original ?? {symbolNameWithGenerics}.Default;");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
using (builder.BlockInvariant($"public Builder()"))
|
|
|
|
|
{
|
|
|
|
@ -243,7 +282,8 @@ namespace Uno
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
using (builder.BlockInvariant($"public Builder({symbolNameWithGenerics} original) : base(original ?? {symbolNameWithGenerics}.Default)"))
|
|
|
|
|
using (builder.BlockInvariant(
|
|
|
|
|
$"public Builder({symbolNameWithGenerics} original) : base(original ?? {symbolNameWithGenerics}.Default)"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"// Default constructor, the _original field is assigned in base constructor.");
|
|
|
|
|
}
|
|
|
|
@ -260,7 +300,8 @@ namespace Uno
|
|
|
|
|
{
|
|
|
|
|
if (prop.IsIndexer)
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLine($"#error Indexer {prop.Name} not supported! You must remove it for this to compile correctly.");
|
|
|
|
|
builder.AppendLine(
|
|
|
|
|
$"#error Indexer {prop.Name} not supported! You must remove it for this to compile correctly.");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -271,12 +312,16 @@ namespace Uno
|
|
|
|
|
|
|
|
|
|
var newPropertyModifier = isNew ? "new " : "";
|
|
|
|
|
|
|
|
|
|
var attributes = GetAttributes(classCopyIgnoreRegexes, prop);
|
|
|
|
|
|
|
|
|
|
builder.AppendLine($@"
|
|
|
|
|
// Backing field for property {prop.Name}.
|
|
|
|
|
private {prop.Type} _{prop.Name};
|
|
|
|
|
|
|
|
|
|
// If the property {prop.Name} has been set in the builder.
|
|
|
|
|
// `false` means the property hasn't been set or has been reverted to original value `{typeSymbol.Name}.Default.{prop.Name}`.
|
|
|
|
|
// `false` means the property hasn't been set or has been reverted to original value `{typeSymbol.Name}.Default.{
|
|
|
|
|
prop.Name
|
|
|
|
|
}`.
|
|
|
|
|
private bool _is{prop.Name}Set = false;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -285,13 +330,15 @@ private bool _is{prop.Name}Set = false;
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// When nothing is set in the builder, the value is `default({prop.Type})`.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public {newPropertyModifier}{prop.Type} {prop.Name}
|
|
|
|
|
{attributes}public {newPropertyModifier}{prop.Type} {prop.Name}
|
|
|
|
|
{{
|
|
|
|
|
get => _is{prop.Name}Set ? _{prop.Name} : (_original as {symbolNameWithGenerics}).{prop.Name};
|
|
|
|
|
set
|
|
|
|
|
{{
|
|
|
|
|
var originalValue = (({symbolNameWithGenerics})_original).{prop.Name};
|
|
|
|
|
var isSameAsOriginal = global::System.Collections.Generic.EqualityComparer<{prop.Type}>.Default.Equals(originalValue, value);
|
|
|
|
|
var isSameAsOriginal = global::System.Collections.Generic.EqualityComparer<{
|
|
|
|
|
prop.Type
|
|
|
|
|
}>.Default.Equals(originalValue, value);
|
|
|
|
|
if(isSameAsOriginal)
|
|
|
|
|
{{
|
|
|
|
|
// Property {prop.Name} has been set back to original value
|
|
|
|
@ -315,15 +362,18 @@ public {newPropertyModifier}{prop.Type} {prop.Name}
|
|
|
|
|
builder.AppendLineInvariant($"/// Create an immutable instance of {symbolNameForXml}.");
|
|
|
|
|
builder.AppendLineInvariant("/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant("/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// Will return original if nothing changed in the builder (and an original was specified).");
|
|
|
|
|
builder.AppendLineInvariant("/// Application code should prefer the usage of implicit casting which is calling this method.");
|
|
|
|
|
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"/// Will return original if nothing changed in the builder (and an original was specified).");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"/// Application code should prefer the usage of implicit casting which is calling this method.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant("/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
|
|
|
|
using (builder.BlockInvariant($"public {newModifier}{symbolNameWithGenerics} ToImmutable()"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLine(
|
|
|
|
|
$@"var cachedResult = _cachedResult as {symbolNameWithGenerics};
|
|
|
|
|
$@"var cachedResult = _cachedResult as {symbolNameWithGenerics};
|
|
|
|
|
if(cachedResult != null)
|
|
|
|
|
{{
|
|
|
|
|
return cachedResult; // already computed, no need to redo this.
|
|
|
|
@ -343,8 +393,8 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
if(properties.Any())
|
|
|
|
|
{
|
|
|
|
|
if (properties.Any())
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"#region .WithXXX() methods on {symbolNameWithGenerics}.Builder");
|
|
|
|
|
foreach (var (prop, isNew) in properties)
|
|
|
|
|
{
|
|
|
|
@ -354,12 +404,15 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration.");
|
|
|
|
|
builder.AppendLineInvariant($"/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant($"/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant($"/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// <example>");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
|
|
|
|
builder.AppendLineInvariant($"/// {prop.Type} new{prop.Name}Value = [...];");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} instance = builder.With{prop.Name}(new{prop.Name}Value); // create an immutable instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} instance = builder.With{prop.Name}(new{prop.Name}Value); // create an immutable instance");
|
|
|
|
|
builder.AppendLineInvariant("/// </example>");
|
|
|
|
|
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}({prop.Type} value)"))
|
|
|
|
|
{
|
|
|
|
@ -370,17 +423,23 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
builder.AppendLineInvariant($"/// <summary>");
|
|
|
|
|
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
|
|
|
|
builder.AppendLineInvariant($"/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant($"/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant($"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant($"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
|
|
|
|
builder.AppendLineInvariant($"/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// <example>");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} instance = builder.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create an immutable instance");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"/// var builder = new {symbolNameForXml}.Builder {{ {prop1Name} = xxx, ... }}; // create a builder instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} instance = builder.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create an immutable instance");
|
|
|
|
|
builder.AppendLineInvariant("/// </example>");
|
|
|
|
|
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
|
|
|
|
using (builder.BlockInvariant(
|
|
|
|
|
$"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant($"{prop.Name} = valueSelector({prop.Name});");
|
|
|
|
|
builder.AppendLineInvariant("return this;");
|
|
|
|
@ -397,8 +456,10 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
|
|
|
|
|
builder.AppendLineInvariant($"// Default constructor - to ensure it's not defined by application code.");
|
|
|
|
|
builder.AppendLineInvariant($"//");
|
|
|
|
|
builder.AppendLineInvariant($"// \"error CS0111: Type '{symbolNameWithGenerics}' already defines a member called '.ctor' with the same parameter types\":");
|
|
|
|
|
builder.AppendLineInvariant($"// => You have this error? it's because you defined a default constructor on your class!");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"// \"error CS0111: Type '{symbolNameWithGenerics}' already defines a member called '.ctor' with the same parameter types\":");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"// => You have this error? it's because you defined a default constructor on your class!");
|
|
|
|
|
builder.AppendLineInvariant($"//");
|
|
|
|
|
builder.AppendLineInvariant($"// New instances should use the builder instead.");
|
|
|
|
|
builder.AppendLineInvariant($"// We know, it's not compatible with Newtownsoft's JSON.net: It's by-design.");
|
|
|
|
@ -406,7 +467,8 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
builder.AppendLineInvariant($"//");
|
|
|
|
|
builder.AppendLineInvariant($"// Send your complaints or inquiried to \"architecture [-@-] nventive [.] com\".");
|
|
|
|
|
builder.AppendLineInvariant($"//");
|
|
|
|
|
builder.AppendLineInvariant("{0}", $"protected {symbolName}() {{}} // see previous comments if you want this removed (TL;DR: you can't)");
|
|
|
|
|
builder.AppendLineInvariant("{0}",
|
|
|
|
|
$"protected {symbolName}() {{}} // see previous comments if you want this removed (TL;DR: you can't)");
|
|
|
|
|
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
@ -414,7 +476,8 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
builder.AppendLineInvariant($"/// Construct a new immutable instance of {symbolNameForXml} from a builder.");
|
|
|
|
|
builder.AppendLineInvariant($"/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant("/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// Application code should prefer the usage of implicit casting which is calling this constructor.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"/// Application code should prefer the usage of implicit casting which is calling this constructor.");
|
|
|
|
|
builder.AppendLineInvariant("/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant($"/// <param name=\"builder\">The builder for {symbolNameForXml}.</param>");
|
|
|
|
|
|
|
|
|
@ -437,7 +500,7 @@ return ({symbolNameWithGenerics})(_cachedResult = _original);");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
builder.AppendLine(
|
|
|
|
|
$@"// Implicit cast from {symbolNameWithGenerics} to Builder: will simply create a new instance of the builder.
|
|
|
|
|
$@"// Implicit cast from {symbolNameWithGenerics} to Builder: will simply create a new instance of the builder.
|
|
|
|
|
public static implicit operator Builder({symbolNameWithGenerics} original)
|
|
|
|
|
{{
|
|
|
|
|
return new Builder(original);
|
|
|
|
@ -450,8 +513,8 @@ public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
|
|
|
|
}}");
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
if(properties.Any())
|
|
|
|
|
{
|
|
|
|
|
if (properties.Any())
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
builder.AppendLineInvariant($"#region .WithXXX() methods on {symbolNameWithGenerics}");
|
|
|
|
|
foreach (var (prop, isNew) in properties)
|
|
|
|
@ -464,13 +527,17 @@ public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
|
|
|
|
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration.");
|
|
|
|
|
builder.AppendLineInvariant("/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant("/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant($"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
|
|
|
|
builder.AppendLineInvariant("/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant("/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// <example>");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
|
|
|
|
builder.AppendLineInvariant($"/// {prop.Type} new{prop.Name}Value = [...];");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} modified = original.With{prop.Name}(new{prop.Name}Value); // create a new modified immutable instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} modified = original.With{prop.Name}(new{prop.Name}Value); // create a new modified immutable instance");
|
|
|
|
|
builder.AppendLineInvariant("/// </example>");
|
|
|
|
|
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
|
|
|
|
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}({prop.Type} value)"))
|
|
|
|
@ -482,19 +549,26 @@ public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
|
|
|
|
builder.AppendLine();
|
|
|
|
|
|
|
|
|
|
builder.AppendLineInvariant("/// <summary>");
|
|
|
|
|
builder.AppendLineInvariant($"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// Set property {prop.Name} in a fluent declaration by projecting previous value.");
|
|
|
|
|
builder.AppendLineInvariant("/// </summary>");
|
|
|
|
|
builder.AppendLineInvariant("/// <remarks>");
|
|
|
|
|
builder.AppendLineInvariant($"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
|
|
|
|
builder.AppendLineInvariant("/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant($"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// The return value is a builder which can be casted implicitly to {symbolNameForXml} or used to make more changes.");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
"/// **THIS METHOD IS NOT THREAD-SAFE** (it shouldn't be accessed concurrently from many threads)");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// The selector will be called immediately. The main usage of this overload is to specify a _method group_.");
|
|
|
|
|
builder.AppendLineInvariant("/// </remarks>");
|
|
|
|
|
builder.AppendLineInvariant("/// <example>");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
|
|
|
|
builder.AppendLineInvariant($"/// {symbolNameForXml} modified = original.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create a new modified immutable instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} original = {symbolNameForXml}.{defaultMemberName}; // first immutable instance");
|
|
|
|
|
builder.AppendLineInvariant(
|
|
|
|
|
$"/// {symbolNameForXml} modified = original.With{prop.Name}(previous{prop.Name}Value => new {prop.Type}(...)); // create a new modified immutable instance");
|
|
|
|
|
builder.AppendLineInvariant("/// </example>");
|
|
|
|
|
builder.AppendLineInvariant("[global::System.Diagnostics.Contracts.Pure]");
|
|
|
|
|
using (builder.BlockInvariant($"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
|
|
|
|
using (builder.BlockInvariant(
|
|
|
|
|
$"public {newPropertyModifier}Builder With{prop.Name}(Func<{prop.Type}, {prop.Type}> valueSelector)"))
|
|
|
|
|
{
|
|
|
|
|
builder.AppendLineInvariant("var builder = new Builder(this);");
|
|
|
|
|
builder.AppendLineInvariant($"return builder.With{prop.Name}(valueSelector({prop.Name}));");
|
|
|
|
@ -511,6 +585,31 @@ public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
|
|
|
|
_context.AddCompilationUnit(resultFileName, builder.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetAttributes(Regex[] classCopyIgnoreRegexes, IPropertySymbol prop)
|
|
|
|
|
{
|
|
|
|
|
var allAttributesIgnores =
|
|
|
|
|
_copyIgnoreAttributeRegexes
|
|
|
|
|
.Concat(classCopyIgnoreRegexes)
|
|
|
|
|
.Concat(ExtractCopyIgnoreAttributes(prop))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
IEnumerable<string> Enumerate()
|
|
|
|
|
{
|
|
|
|
|
foreach (var attribute in prop.GetAttributes())
|
|
|
|
|
{
|
|
|
|
|
var attrResult = attribute.ToString();
|
|
|
|
|
if (allAttributesIgnores.Any(r => r.IsMatch(attrResult)))
|
|
|
|
|
{
|
|
|
|
|
continue; // should be ignored
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield return $"[{attrResult}]{Environment.NewLine}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Join("", Enumerate());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static MethodInfo _isAutoPropertyGetter;
|
|
|
|
|
|
|
|
|
|
private static bool IsAutoProperty(IPropertySymbol symbol)
|
|
|
|
@ -542,7 +641,7 @@ public static implicit operator {symbolNameWithGenerics}(Builder builder)
|
|
|
|
|
_isAutoPropertyGetter = propertyInfo?.GetMethod;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var isAuto = _isAutoPropertyGetter.Invoke(symbol, new object[] {});
|
|
|
|
|
var isAuto = _isAutoPropertyGetter.Invoke(symbol, new object[] { });
|
|
|
|
|
return (bool) isAuto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|