Merge pull request #6 from nventive/dev/cdb/codegen-options

Immutable: Added copy of attributes on the builder
+ minor fixes
This commit is contained in:
Carl de Billy 2018-02-06 10:02:55 -05:00 коммит произвёл GitHub
Родитель e09e4c98f7 1e1e7db34a
Коммит ca95d57c25
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 249 добавлений и 69 удалений

Просмотреть файл

@ -23,7 +23,7 @@
}
```
1. Compile (the generation process occurres at compile time).
1. Compile (the generation process occurs at compile time).
1. It will generate the following public methods for you:
``` csharp
partial class MyEntity : IEquatable<MyEntity>, IKeyEquatable<MyEntity>
@ -100,7 +100,7 @@ two entities are the same.
Suppose you have 2 versions of the same entity, an original version and
an updated version after a change. The two won't be equals because they
are representing different values, but they should be _key equals_ because
they have the same key (reprensenting the same thing, just different versions
they have the same key (representing the same thing, just different versions
of it).
@ -114,7 +114,7 @@ of it).
* **Only read-only fields/properties should be used**
A warning will be generated when using mutable fields.
* **Nested classes not supported**, the class must be directly in its
namespace for the generation to happend.
namespace for the generation to happen.
1. **KeyEquality**:
The _Key Equality_ is a feature to compare entities for the equality
of their key(s). The main usage of this equality is to compare if
@ -123,7 +123,7 @@ of it).
server, having the same unique key... instances will be _key equals_
but not _equals_).
* **To activate KeyEquality**, you need to have at least one field
or property maked as _Equality Key_, or to derive from a type
or property marked as _Equality Key_, or to derive from a type
implementing it.
* When activated, this feature will generate:
* The class will implements the `IKeyEquatable<T>` interface
@ -220,6 +220,9 @@ The generation logic for fields/properties in the class, using a first-match rul
> * Some field/property names could implicitly become _key equality member_
> * Members with `[EqualityKey]` (explicit or implicit) will also be considered
> as `[EqualityHash]`.
> * Using the [Key] attribute from
> [Data Annotations](https://www.nuget.org/packages/System.ComponentModel.Annotations)
> will produce the same behavior.
## Important Considerations

Просмотреть файл

@ -41,7 +41,7 @@
* **Static members are ok**, as they can only manipulate immutable stuff.
Same apply for extensions.
* **Nested classes not supported**, the class must be directly in its
namespace for the generator to happend.
namespace for the generator to happen.
1. Property Initializers will become default values in the builder.
ex:
``` csharp
@ -65,12 +65,12 @@
/// </summary>
public string FullName => LastName + ", " + FirstName;
```
* Generated builder are implicitely convertible to/from the entity
* Generated builder are implicitly convertible to/from the entity
* Collections must be IReadOnlyCollection, IReadonlyDictionary or an
immutable type from System.Collection.Immutable namespace:
ImmutableList, ImmutableDictionary, ImmutableArray, ImmutableSet...
**The type of the collection must be immutable too**.
1. A static `.Default` readonly property will containt a default instance with
1. A static `.Default` readonly property will contain a default instance with
properties to their default initial values.
It can be used as starting point to create a new instance, example:
@ -198,7 +198,7 @@ Important:
# FAQ
## What if I need to use it with [Newtownsoft's JSON.NET](https://www.newtonsoft.com/json)?
You simply need to deserialze the builder instead of the class itself.
You simply need to deserialize the builder instead of the class itself.
The implicit casting will automatically convert it to the right type.
Example:
@ -257,4 +257,12 @@ will give you a visual warning when you're not using the result.
Not supported yet. Open an issue if you need this.
## Can I use this for value types? (`struct`)
No. The type must be a reference type (`class`).
No. The type must be a reference type (`class`).
## What is happening with attributes on my properties?
All attributes are replicated, except those defined in `Uno.Immutables` and
`Uno.Equality`. If you need to remove other attributes, you just need
to use the `[ImmutableAttributeCopyIgnore(<regex>)]` attribute.
For a finer control, you can put it at assembly level, on a type or
even on a property itself.

Просмотреть файл

@ -16,6 +16,7 @@
// ******************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -90,6 +91,8 @@ namespace Uno.CodeGen.Tests
{
[EqualityKey]
internal string A { get; }
[Key]
internal string B { get; }
}

Просмотреть файл

@ -17,6 +17,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -98,6 +100,15 @@ namespace Uno.CodeGen.Tests
A original = A.Default.WithEntity(x => x.WithMyField2(223));
original.Entity.MyField2.Should().Be(223);
}
[TestMethod]
public void Immutable_When_Using_Attributes_Then_They_Are_Copied_On_Builder()
{
var tBuilder = typeof(MySuperGenericImmutable<string, string, string, string, string, string>.Builder);
var idProperty = tBuilder.GetProperty("Id");
var attributes = idProperty.GetCustomAttributes(false);
attributes.Should().HaveCount(3);
}
}
[GeneratedImmutable]
@ -148,13 +159,24 @@ namespace Uno.CodeGen.Tests
{
}
[ImmutableAttributeCopyIgnore("RequiredAttribute")]
[GeneratedImmutable(GenerateEquality = true)]
public partial class MySuperGenericImmutable<T1, T2, T3, T4, T5, T6>
{
[Required, System.ComponentModel.DataAnnotations.DataType(DataType.Text)]
[System.ComponentModel.DataAnnotations.Key]
[System.ComponentModel.DataAnnotations.RegularExpression("regex-pattern", ErrorMessage = "error-msg")]
public string Id { get; }
[Required(AllowEmptyStrings = true, ErrorMessage = "Entity1 is required!")]
public T1 Entity1 { get; }
[EqualityIgnore]
public T2 Entity2 { get; }
[EqualityIgnore]
public T3 Entity3 { get; }
[EqualityIgnore]
public T4 Entity4 { get; }
[EqualityIgnore]
public T5 Entity5 { get; }
[EqualityHash]
public T6 Entity6 { get; }

Просмотреть файл

@ -20,6 +20,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.1" />
<PackageReference Include="Uno.Core" Version="1.20.0-dev.4" />
<PackageReference Include="Uno.SourceGenerationTasks" Version="1.20.0-dev.6" />
</ItemGroup>

Просмотреть файл

@ -46,6 +46,7 @@ namespace Uno
private INamedTypeSymbol _ignoreForEqualityAttributeSymbol;
private INamedTypeSymbol _equalityHashCodeAttributeSymbol;
private INamedTypeSymbol _equalityKeyCodeAttributeSymbol;
private INamedTypeSymbol _dataAnnonationsKeyAttributeSymbol;
private SourceGeneratorContext _context;
private static readonly int[] PrimeNumbers =
@ -91,6 +92,7 @@ namespace Uno
_ignoreForEqualityAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityIgnoreAttribute");
_equalityHashCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityHashAttribute");
_equalityKeyCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityKeyAttribute");
_dataAnnonationsKeyAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.KeyAttribute");
foreach (var type in EnumerateEqualityTypesToGenerate())
{
@ -621,7 +623,9 @@ namespace Uno
equalityMembers.Add(symbol);
if (symbolAttributes.Any(a => a.AttributeClass.Equals(_equalityKeyCodeAttributeSymbol)))
if (symbolAttributes.Any(a =>
a.AttributeClass.Equals(_equalityKeyCodeAttributeSymbol)
|| a.AttributeClass.Equals(_dataAnnonationsKeyAttributeSymbol)))
{
// [EqualityKey] on the member: this member is used for both key & hash
hashMembers.Add(symbol);

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -0,0 +1,40 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
namespace Uno
{
/// <summary>
/// Defines which attributes to ignore when copying to builder
/// </summary>
/// <remarks>
/// Can be put on an assembly, a type or a property.
/// </remarks>
[System.AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
public sealed class ImmutableAttributeCopyIgnoreAttribute : Attribute
{
/// <summary>
/// Regex use to match the name (fullname) of the attribute type(s) to ignore.
/// </summary>
public string AttributeToIgnoreRegex { get; }
public ImmutableAttributeCopyIgnoreAttribute(string attributeToIgnoreRegex)
{
AttributeToIgnoreRegex = attributeToIgnoreRegex;
}
}
}