| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,275 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System.Collections.Immutable; | ||
| using System.Text; | ||
|
|
||
| // Note that ProjectElementContainer-derived types lack any properties that expose | ||
| // the specific types of children they support. And the Children member that is exposed | ||
| // allows any type of ProjectElement. | ||
| // So an enhancement we should consider is to optionally hide the Children member | ||
| // from the public API and allow more constrained types of children via the public API. | ||
|
|
||
| // class ElementLocation { | ||
| // int column; | ||
| // int line; | ||
| // string file; | ||
| // string locationString; | ||
| // } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| abstract partial class ProjectElement | ||
| { | ||
| readonly string condition; | ||
| // readonly ElementLocation conditionLocation; | ||
|
|
||
| readonly string label; | ||
| // readonly ElementLocation labelLocation; | ||
|
|
||
| // readonly ElementLocation location; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| abstract partial class ProjectElementContainer : ProjectElement | ||
| { | ||
| readonly ImmutableList<ProjectElement> children; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectRootElement : ProjectElementContainer | ||
| { | ||
| readonly string fullPath; | ||
| readonly Encoding encoding; | ||
|
|
||
| readonly string toolsVersion; | ||
| // readonly ElementLocation toolsVersionLocation; | ||
|
|
||
| readonly string defaultTargets; | ||
| // readonly ElementLocation defaultTargetsLocation; | ||
|
|
||
| readonly string initialTargets; | ||
| // readonly ElementLocation initialTargetsLocation; | ||
|
|
||
| readonly bool treatAsLocalProperty; | ||
| // readonly ElementLocation treatAsLocalPropertylocation; | ||
|
|
||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<ProjectElement>(); | ||
| } | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectPropertyGroupElement : ProjectElementContainer | ||
| { | ||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<ProjectElement>(); | ||
| } | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectItemGroupElement : ProjectElementContainer | ||
| { | ||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<ProjectElement>(); | ||
| } | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectChooseElement : ProjectElementContainer | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectOtherwiseElement : ProjectElementContainer | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectWhenElement : ProjectElementContainer | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectPropertyElement : ProjectElement | ||
| { | ||
| readonly string name; | ||
| readonly string value; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectItemElement : ProjectElementContainer | ||
| { | ||
| readonly string exclude; | ||
| // readonly ElementLocation ExcludeLocation; | ||
|
|
||
| readonly string include; | ||
| // readonly ElementLocation IncludeLocation; | ||
|
|
||
| readonly string itemType; | ||
|
|
||
| readonly string keepDuplicates; | ||
| // readonly ElementLocation KeepDuplicatesLocation; | ||
|
|
||
| readonly string keepMetadata; | ||
| // readonly ElementLocation KeepMetadataLocation; | ||
|
|
||
| readonly string remove; | ||
| // readonly ElementLocation RemoveLocation; | ||
|
|
||
| readonly string removeMetadata; | ||
| // readonly ElementLocation RemoveMetadataLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectMetadataElement : ProjectElement | ||
| { | ||
| readonly string name; | ||
| readonly string value; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectExtensionsElement : ProjectElement | ||
| { | ||
| readonly string content; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectImportElement : ProjectElement | ||
| { | ||
| readonly string project; | ||
| // readonly ElementLocation projectLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectImportGroupElement : ProjectElementContainer | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectItemDefinitionElement : ProjectElementContainer | ||
| { | ||
| readonly string itemType; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectItemDefinitionGroupElement : ProjectElementContainer | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectOnErrorElement : ProjectElement | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectOutputElement : ProjectElement | ||
| { | ||
| readonly bool isOutputItem; | ||
| readonly bool isOutputProperty; | ||
|
|
||
| readonly string itemType; | ||
| // readonly ElementLocation itemTypeLocation; | ||
|
|
||
| readonly string propertyName; | ||
| // readonly ElementLocation propertyNameLocation; | ||
|
|
||
| readonly string taskParameter; | ||
| // readonly ElementLocation TaskParameterLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectTargetElement : ProjectElementContainer | ||
| { | ||
| readonly string afterTargets; | ||
| // readonly ElementLocation AfterTargetsLocation; | ||
|
|
||
| readonly string beforeTargets; | ||
| // readonly ElementLocation BeforeTargetsLocation; | ||
|
|
||
| readonly string dependsOnTargets; | ||
| // readonly ElementLocation DependsOnTargetsLocation; | ||
|
|
||
| readonly string inputs; | ||
| // readonly ElementLocation InputsLocation; | ||
|
|
||
| readonly string keepDuplicateOutputs; | ||
| // readonly ElementLocation KeepDuplicateOutputsLocation; | ||
|
|
||
| readonly string name; | ||
| // readonly ElementLocation NameLocation; | ||
|
|
||
| readonly string outputs; | ||
| // readonly ElementLocation OutputsLocation; | ||
|
|
||
| readonly string returns; | ||
| // readonly ElementLocation ReturnsLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectTaskElement : ProjectElementContainer | ||
| { | ||
| readonly string continueOnError; | ||
| // readonly ElementLocation ContinueOnErrorLocation; | ||
|
|
||
| readonly string msbuildArchitecture; | ||
| // readonly ElementLocation MSBuildArchitectureLocation; | ||
|
|
||
| readonly string msbuildRuntime; | ||
| // readonly ElementLocation MSBuildRuntimeLocation; | ||
|
|
||
| readonly string name; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectUsingTaskBodyElement : ProjectElement | ||
| { | ||
| readonly string evaluate; | ||
| // readonly ElementLocation evaluateLocation; | ||
|
|
||
| readonly string taskBody; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectUsingTaskElement : ProjectElementContainer | ||
| { | ||
| readonly string architecture; | ||
| // readonly ElementLocation ArchitectureLocation; | ||
|
|
||
| readonly string assemblyFile; | ||
| // readonly ElementLocation AssemblyFileLocation; | ||
|
|
||
| readonly string assemblyName; | ||
| // readonly ElementLocation AssemblyNameLocation; | ||
|
|
||
| readonly string runtime; | ||
| // readonly ElementLocation RuntimeLocation; | ||
|
|
||
| readonly string taskFactory; | ||
| // readonly ElementLocation TaskFactoryLocation; | ||
|
|
||
| readonly string taskName; | ||
| // readonly ElementLocation TaskNameLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class ProjectUsingTaskParameterElement : ProjectElement | ||
| { | ||
| readonly string name; | ||
|
|
||
| readonly string output; | ||
| // readonly ElementLocation outputLocation; | ||
|
|
||
| readonly string parameterType; | ||
| // readonly ElementLocation parameterTypeLocation; | ||
|
|
||
| readonly string required; | ||
| // readonly ElementLocation requiredLocation; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true, Delta = true, DefineWithMethodsPerProperty = true)] | ||
| partial class UsingTaskParameterGroupElement : ProjectElementContainer | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| public class NestedTests | ||
| { | ||
| [Fact] | ||
| public void NestedCreateBuilder() | ||
| { | ||
| var nestedBuilder = Nested.NestedClass.CreateBuilder(); | ||
| Assert.NotNull(nestedBuilder); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void NestedConstruction() | ||
| { | ||
| var nested = Nested.NestedClass.Create("a name"); | ||
| Assert.NotNull(nested); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| partial class Nested | ||
| { | ||
| [GenerateImmutable(GenerateBuilder = true, DefineWithMethodsPerProperty = true)] | ||
| public partial class NestedClass | ||
| { | ||
| readonly string name; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class Empty { } | ||
|
|
||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class EmptyDerived : Empty | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class Empty | ||
| { | ||
| } | ||
|
|
||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class NotSoEmptyDerived : Empty | ||
| { | ||
| readonly bool oneField; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| [GenerateImmutable] | ||
| abstract partial class RootRecursive | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable] | ||
| abstract partial class RecursiveContainer : RootRecursive | ||
| { | ||
| readonly ImmutableList<RootRecursive> children; | ||
| } | ||
|
|
||
| [GenerateImmutable] | ||
| partial class ContainerOfNonRecursiveCollection : RootRecursive | ||
| { | ||
| [NotRecursive] | ||
| readonly ImmutableList<NonRecursiveElement> metadata; | ||
| } | ||
|
|
||
| [GenerateImmutable] | ||
| partial class NonRecursiveElement : RootRecursive | ||
| { | ||
| readonly string name; | ||
| readonly string value; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable] | ||
| partial class One | ||
| { | ||
| readonly Other buddy; | ||
| } | ||
|
|
||
| [ImmutableObjectGraph.Generation.GenerateImmutable] | ||
| partial class Other | ||
| { | ||
| readonly int count; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable] | ||
| partial class Apple | ||
| { | ||
| readonly int seeds; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class NonEmptyBase | ||
| { | ||
| readonly bool oneField; | ||
| } | ||
|
|
||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class EmptyDerivedFromNonEmptyBase : NonEmptyBase | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| [ImmutableObjectGraph.Generation.GenerateImmutable(GenerateBuilder = true)] | ||
| partial class Apple | ||
| { | ||
| readonly int seeds; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using Xunit; | ||
|
|
||
| public class PersonBuilderTests | ||
| { | ||
| [Fact] | ||
| public void ToBuilderReturnsSimilarObject() | ||
| { | ||
| var person = Person.Create("bill", age: 10); | ||
|
|
||
| var personBuilder = person.ToBuilder(); | ||
| Assert.NotNull(personBuilder); | ||
| Assert.Equal(person.Name, personBuilder.Name); | ||
| Assert.Equal(person.Age, personBuilder.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ToBuilderToImmutableRoundtripReusesInstance() | ||
| { | ||
| var person = Person.Create("bill", age: 10); | ||
| var personBuilder = person.ToBuilder(); | ||
| var roundTripPerson = personBuilder.ToImmutable(); | ||
| Assert.Same(person, roundTripPerson); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void MutablePropertiesRetainChanges() | ||
| { | ||
| var person = Person.Create("bill", age: 10); | ||
| var personBuilder = person.ToBuilder(); | ||
|
|
||
| personBuilder.Name = "billy"; | ||
| personBuilder.Age = 8; | ||
|
|
||
| Assert.Equal("billy", personBuilder.Name); | ||
| Assert.Equal(8, personBuilder.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ToImmutableReturnsSimilarObject() | ||
| { | ||
| var person = Person.Create("bill", age: 10); | ||
| var personBuilder = person.ToBuilder(); | ||
|
|
||
| personBuilder.Name = "billy"; | ||
| personBuilder.Age = 8; | ||
|
|
||
| var recreatedPerson = personBuilder.ToImmutable(); | ||
| Assert.Equal(personBuilder.Name, recreatedPerson.Name); | ||
| Assert.Equal(personBuilder.Age, recreatedPerson.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ToImmutableCalledRepeatedlyAfterChangesReusesInstance() | ||
| { | ||
| var person = Person.Create(null); | ||
| var builder = person.ToBuilder(); | ||
| Assert.Same(person, builder.ToImmutable()); | ||
| builder.Name = "bill"; | ||
| var bill1 = builder.ToImmutable(); | ||
| var bill2 = builder.ToImmutable(); | ||
| Assert.NotSame(person, bill1); | ||
| Assert.Same(bill1, bill2); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void PropertiesAreAlsoBuilders() | ||
| { | ||
| var person = Person.Create("bill", watch: Watch.Create()); | ||
| var personBuilder = person.ToBuilder(); | ||
| personBuilder.Watch.Color = "Red"; | ||
| var modifiedPerson = personBuilder.ToImmutable(); | ||
| Assert.Equal("Red", modifiedPerson.Watch.Color); | ||
|
|
||
| personBuilder.Watch = null; | ||
| var personWithoutWatch = personBuilder.ToImmutable(); | ||
| Assert.Null(personWithoutWatch.Watch); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void PropertyBuildersAreNullIfImmutableIsNull() | ||
| { | ||
| var person = Person.Create("bill"); | ||
| var builder = person.ToBuilder(); | ||
| Assert.Null(builder.Watch); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CreateBuilder() | ||
| { | ||
| var builder = Person.CreateBuilder(); | ||
| builder.Name = "name"; | ||
| var immutable = builder.ToImmutable(); | ||
| Assert.Equal("name", immutable.Name); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using Xunit; | ||
|
|
||
| public class PersonTests | ||
| { | ||
| /// <summary> | ||
| /// Immutable types should have 1 public constructor to support deserialization. | ||
| /// </summary> | ||
| [Fact] | ||
| public void PublicConstructor() | ||
| { | ||
| #pragma warning disable CS0618 | ||
| var p = new Person(Name: "Andrew", Age: 15, Watch: null); | ||
| #pragma warning restore CS0618 | ||
| Assert.Equal("Andrew", p.Name); | ||
| Assert.Equal(15, p.Age); | ||
| Assert.Null(p.Watch); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultInstance() | ||
| { | ||
| var defaultPerson = Person.Create(null); | ||
| Assert.NotNull(defaultPerson); | ||
| Assert.Equal(0, defaultPerson.Age); | ||
| Assert.Null(defaultPerson.Name); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CreateWithArguments() | ||
| { | ||
| var billyAge10 = Person.Create(name: "billy", age: 10); | ||
| Assert.Equal("billy", billyAge10.Name); | ||
| Assert.Equal(10, billyAge10.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetScalarReferenceTypeProperty() | ||
| { | ||
| var original = Person.Create(null); | ||
| var modified = original.WithName("bill"); | ||
| Assert.Equal("bill", modified.Name); | ||
| Assert.Null(original.Name); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetScalarValueTypeProperty() | ||
| { | ||
| var original = Person.Create(null); | ||
| var modified = original.WithAge(8); | ||
| Assert.Equal(8, modified.Age); | ||
| Assert.Equal(0, original.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetScalarReferenceTypePropertyToSameValueReturnsSameInstance() | ||
| { | ||
| var expected = Person.Create(null).WithName("bill"); | ||
| var actual = expected.WithName(expected.Name); | ||
| Assert.Same(expected, actual); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetScalarValueTypePropertyToSameValueReturnsSameInstance() | ||
| { | ||
| var expected = Person.Create(null).WithAge(8); | ||
| var actual = expected.WithAge(expected.Age); | ||
| Assert.Same(expected, actual); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void WithSetsNonDefaultValues() | ||
| { | ||
| // Initialize | ||
| var billAge10 = Person.Create(null).With(name: "bill", age: 10); | ||
| Assert.Equal("bill", billAge10.Name); | ||
| Assert.Equal(10, billAge10.Age); | ||
|
|
||
| // Full modification | ||
| var jillAge9 = billAge10.With(name: "jill", age: 9); | ||
| Assert.Equal("jill", jillAge9.Name); | ||
| Assert.Equal(9, jillAge9.Age); | ||
|
|
||
| // Partial modification | ||
| var billAge12 = billAge10.With(age: 12); | ||
| Assert.Equal("bill", billAge12.Name); | ||
| Assert.Equal(12, billAge12.Age); | ||
|
|
||
| var billyAge10 = billAge10.With(name: "billy"); | ||
| Assert.Equal("billy", billyAge10.Name); | ||
| Assert.Equal(10, billyAge10.Age); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void WithRevertsToDefaultValues() | ||
| { | ||
| var billAge10 = Person.Create(name: "bill", age: 10); | ||
|
|
||
| var billAge0 = billAge10.With(age: 0); | ||
| Assert.Equal(0, billAge0.Age); | ||
| Assert.Equal("bill", billAge0.Name); | ||
|
|
||
| var age10 = billAge10.With(name: null); | ||
| Assert.Equal(10, age10.Age); | ||
| Assert.Null(age10.Name); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ReferenceToOtherImmutable() | ||
| { | ||
| var blackWatch = Watch.Create(color: "black", size: 10); | ||
| var personWithBlackWatch = Person.Create(null, watch: blackWatch); | ||
|
|
||
| var silverWatch = blackWatch.WithColor("silver"); | ||
| var personWithSilverWatch = personWithBlackWatch.WithWatch(silverWatch); | ||
| Assert.Equal(silverWatch, personWithSilverWatch.Watch); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void WithPreservesInstanceWhenNoChangesMade() | ||
| { | ||
| var bill = Person.Create(name: "bill"); | ||
| Assert.Same(bill, bill.With()); | ||
| Assert.Same(bill, bill.With(name: "bill")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultValues() | ||
| { | ||
| // We expect Members to be non-null because we have a partial class defined that specifies that. | ||
| var family = Family.Create(); | ||
| Assert.NotNull(family.Members); | ||
| Assert.Empty(family.Members); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultValuesCanBeOverriddenWithTypeDefaults() | ||
| { | ||
| Assert.NotNull(Family.Create().Members); // the test is only valid if the default value is non-null | ||
| Assert.Null(Family.Create(members: null).Members); | ||
| Assert.Null(Family.Create().WithMembers((ImmutableSortedSet<Person>)null).Members); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultValuesCanBeOverriddenWithOtherValue() | ||
| { | ||
| Assert.NotNull(Family.Create().Members); // the test is only valid if the default value is non-null | ||
|
|
||
| var otherMembers = ImmutableSortedSet.Create(Person.Create("bill")); | ||
| Assert.Same(otherMembers, Family.Create(members: otherMembers).Members); | ||
| Assert.Same(otherMembers, Family.Create().WithMembers(otherMembers).Members); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ImmutableCollection() | ||
| { | ||
| var members = ImmutableSortedSet.Create<Person>(); | ||
| var family = Family.Create(members: members); | ||
| Assert.Same(members, family.Members); | ||
| var newMembers = family.Members.Add(Person.Create("bill")); | ||
| Assert.Same(members, family.Members); | ||
| Assert.NotSame(newMembers, members); | ||
|
|
||
| var newFamily = family.WithMembers(newMembers); | ||
| Assert.Same(newMembers, newFamily.Members); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CollectionsAlternateMutationMethods() | ||
| { | ||
| var family = Family.Create(); | ||
| var familyAdd1 = family.AddMembers(Person.Create("billy", age: 5)); | ||
| Assert.Empty(family.Members); | ||
| Assert.Single(familyAdd1.Members); | ||
|
|
||
| var familyAdd1More = familyAdd1.AddMembers(Person.Create("sally", age: 8)); | ||
| Assert.Equal(2, familyAdd1More.Members.Count); | ||
|
|
||
| var familyRemove1 = familyAdd1More.RemoveMembers(familyAdd1.Members[0]); | ||
| Assert.Equal(2, familyAdd1More.Members.Count); | ||
| Assert.Single(familyRemove1.Members); | ||
|
|
||
| var familyAddMany = familyAdd1.AddMembers( | ||
| Person.Create("sally", age: 8), | ||
| Person.Create("sam", age: 4)); | ||
| Assert.Equal(3, familyAddMany.Members.Count); | ||
|
|
||
| var familyRemoveMany = familyAddMany.RemoveMembers(familyAdd1More.Members); | ||
| Assert.Single(familyRemoveMany.Members); | ||
|
|
||
| var familyCleared = familyAddMany.RemoveMembers(); | ||
| Assert.Empty(familyCleared.Members); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using ImmutableObjectGraph; | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| partial class Family | ||
| { | ||
| readonly ImmutableSortedSet<Person> members; | ||
|
|
||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Members = ImmutableSortedSet.Create<Person>(new FamilyMemberComparer()); | ||
| } | ||
|
|
||
| private class FamilyMemberComparer : IComparer<Person> | ||
| { | ||
| public int Compare(Person x, Person y) | ||
| { | ||
| return x.Age.CompareTo(y.Age); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| partial class Person | ||
| { | ||
| [Required] | ||
| readonly string name; | ||
| readonly int age; | ||
| readonly Watch watch; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| partial class Watch | ||
| { | ||
| readonly string color; | ||
| readonly int size; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System.Collections.Immutable; | ||
|
|
||
| public interface IRule { } | ||
|
|
||
| public interface IProjectPropertiesContext { } | ||
|
|
||
| public interface IPropertySheet { } | ||
|
|
||
| public class ProjectPropertiesContext : IProjectPropertiesContext | ||
| { | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineInterface = true, GenerateBuilder = true, DefineWithMethodsPerProperty = true, DefineRootedStruct = true, Delta = true)] | ||
| partial class ProjectTree | ||
| { | ||
| [Required] | ||
| readonly string caption; | ||
| readonly string filePath; | ||
| readonly System.Drawing.Image icon; | ||
| readonly System.Drawing.Image expandedIcon; | ||
| readonly bool visible; | ||
| readonly IRule browseObjectProperties; | ||
| readonly ImmutableHashSet<string> capabilities; | ||
| readonly ImmutableSortedSet<ProjectTree> children; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineInterface = true, GenerateBuilder = true, DefineWithMethodsPerProperty = true, DefineRootedStruct = true, Delta = true)] | ||
| partial class ProjectItemTree : ProjectTree | ||
| { | ||
| [Required] | ||
| readonly IProjectPropertiesContext projectPropertiesContext; | ||
| readonly IPropertySheet propertySheet; | ||
| readonly bool isLinked; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| //----------------------------------------------------------------------- | ||
| // <copyright file="ProjectTreeCapabilities.cs" company="Microsoft"> | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // </copyright> | ||
| //----------------------------------------------------------------------- | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| /// <summary> | ||
| /// String constants that may appear in <see cref="IProjectTree.Capabilities"/>. | ||
| /// </summary> | ||
| public static class ProjectTreeCapabilities | ||
| { | ||
| /// <summary> | ||
| /// An empty set of project tree capabilities with the case-insensitive comparer. | ||
| /// </summary> | ||
| [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Is indeed immutable.")] | ||
| public static readonly ImmutableHashSet<string> EmptyCapabilities = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this node is the root project node. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This is useful for <see cref="IProjectTreeModifier"/> extensions so they know which node is actually the root one, | ||
| /// since all nodes look like root nodes at the time they are being modified. | ||
| /// </remarks> | ||
| public const string ProjectRoot = "ProjectRoot"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this node is the well-known "References" folder. | ||
| /// </summary> | ||
| public const string ReferencesFolder = "ReferencesFolder"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this node is the special project Properties folder. | ||
| /// </summary> | ||
| public const string AppDesignerFolder = "AppDesignerFolder"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item represents a reference (e.g. Assembly, COM, or Project reference). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This capability should only appear on <see cref="IProjectItemTree"/> instances. | ||
| /// </remarks> | ||
| public const string Reference = "Reference"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item is a reference that has been successfully resolved. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This capability should only appear on <see cref="IProjectItemTree"/> instances. | ||
| /// </remarks> | ||
| public const string ResolvedReference = "ResolvedReference"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item is a reference that failed to resolve. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This capability should only appear on <see cref="IProjectItemTree"/> instances. | ||
| /// </remarks> | ||
| public const string BrokenReference = "BrokenReference"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item represents a source file (e.g. *.cs, *.resx, *.bmp) that is included in the build. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This capability should only appear on <see cref="IProjectItemTree"/> instances. | ||
| /// </remarks> | ||
| public const string SourceFile = "SourceFile"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item represents a folder on disk, and may contain sub-items that can be manipulated by the user. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This capability may appear on any <see cref="IProjectTree"/> instance. | ||
| /// </remarks> | ||
| public const string Folder = "Folder"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item represents a file on disk (not a folder, and not a virtual node). | ||
| /// </summary> | ||
| public const string FileOnDisk = "FileOnDisk"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item represents a file or folder on disk (not a virtual node). | ||
| /// </summary> | ||
| public const string FileSystemEntity = "FileSystemEntity"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item should appear near the top of its containing list with other similarly tagged nodes. | ||
| /// </summary> | ||
| public const string BubbleUp = "BubbleUp"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item does not exist in the project, but does exist on disk and might be included in the project later. | ||
| /// </summary> | ||
| public const string IncludeInProjectCandidate = "IncludeInProjectCandidate"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item does not exist in the project, but does exist on disk and might be included in the project later, | ||
| /// but that the file entity on disk is marked as hidden in the file system. | ||
| /// </summary> | ||
| public const string HiddenIncludeInProjectCandidate = "HiddenIncludeInProjectCandidate"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item may not exist in the project but implies nothing about includability into a project. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Node providers can use this capability to claim responsibility for resolving a node to a path | ||
| /// if path resolution fails because the node is not a project item. SDK references are an example. | ||
| /// </remarks> | ||
| [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "NonMember", Justification = "Ignored")] | ||
| public const string NonMemberItem = "NonMemberItem"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item should always be copyable. | ||
| /// </summary> | ||
| [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Copyable", Justification = "Ignored")] | ||
| public const string AlwaysCopyable = "AlwaysCopyable"; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that this item should be implicitly copied, moved, or dragged whenever its immediate parent is. | ||
| /// </summary> | ||
| public const string FollowsParent = "FollowsParent"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,321 @@ | ||
| //----------------------------------------------------------------------- | ||
| // <copyright file="UnattachedProjectTreeNodeTest2.cs" company="Microsoft"> | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // </copyright> | ||
| //----------------------------------------------------------------------- | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Xunit; | ||
|
|
||
| /// <summary> | ||
| /// This is a test class for ImmutableNodeTest and is intended | ||
| /// to contain all ImmutableNodeTest Unit Tests | ||
| /// </summary> | ||
| public class UnattachedProjectTreeNodeTest2 : ProjectTreeNodeTestBase | ||
| { | ||
| private ProjectTree node; | ||
|
|
||
| public UnattachedProjectTreeNodeTest2() | ||
| { | ||
| this.node = (ProjectTree)this.NewTree(Caption, children: this.Children); | ||
| } | ||
|
|
||
| protected override void Cleanup() | ||
| { | ||
| RecursiveTypeExtensions.LookupTable<ProjectTree, ProjectTree>.ValidateInternalIntegrity(this.node); | ||
| base.Cleanup(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A test for ImmutableNode Constructor | ||
| /// </summary> | ||
| [Fact] | ||
| public void ImmutableNodeConstructorTest() | ||
| { | ||
| this.Children = this.Children.Add(this.NewNode()); // children must be non-empty for the collection to be used in the node. | ||
| this.node = (ProjectTree)this.NewTree(Caption, children: this.Children); | ||
|
|
||
| Assert.Same(Caption, this.node.Caption); | ||
| Assert.Same(Children, this.node.Children); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetSelfAndDescendentsBreadthFirst_GreenNode() | ||
| { | ||
| var grandChild1 = this.NewNode(); | ||
| var grandChild2 = this.NewNode(); | ||
| var child1 = this.NewNode(grandChild1); | ||
| var child2 = this.NewNode(grandChild2); | ||
| var parent = this.NewNode(child1, child2); | ||
|
|
||
| var array = parent.GetSelfAndDescendentsBreadthFirst().ToArray(); | ||
| Assert.Same(parent, array[0]); | ||
| Assert.Same(child1, array[1]); | ||
| Assert.Same(child2, array[2]); | ||
| Assert.Same(grandChild1, array[3]); | ||
| Assert.Same(grandChild2, array[4]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetSelfAndDescendentsBreadthFirst_RedNode() | ||
| { | ||
| var grandChild1 = this.NewNode(); | ||
| var grandChild2 = this.NewNode(); | ||
| var child1 = this.NewNode(grandChild1); | ||
| var child2 = this.NewNode(grandChild2); | ||
| var parent = this.NewNode(child1, child2); | ||
|
|
||
| var array = parent.AsRoot.GetSelfAndDescendentsBreadthFirst().ToArray(); | ||
| Assert.Same(parent, array[0].ProjectTree); | ||
| Assert.Same(child1, array[1].ProjectTree); | ||
| Assert.Same(child2, array[2].ProjectTree); | ||
| Assert.Same(grandChild1, array[3].ProjectTree); | ||
| Assert.Same(grandChild2, array[4].ProjectTree); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AddChild() | ||
| { | ||
| this.Children = this.Children.Add(this.NewNode()); // children must be non-empty for the collection to be used in the node. | ||
| this.node = this.NewTree(Caption, children: this.Children); | ||
|
|
||
| var newChild = this.NewNode(); | ||
| var newNode = this.node.AddChildren(newChild); | ||
| Assert.Same(this.Children, this.node.Children); | ||
| Assert.Equal(2, newNode.Children.Count); | ||
| Assert.Same(newChild, newNode.Children[1]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void RemoveChild() | ||
| { | ||
| var newChild = this.NewNode(); | ||
| var nodeWithChildren = this.node.AddChildren(newChild); | ||
| var nodeWithoutChildren = nodeWithChildren.RemoveChildren(newChild); | ||
| Assert.Single(nodeWithChildren.Children); | ||
| Assert.Empty(nodeWithoutChildren.Children); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void RemoveChildrenThrowsOnMissingChild() | ||
| { | ||
| var newChild = this.NewNode(); | ||
| Assert.Throws<ArgumentException>(() => this.node.RemoveChildren(new[] { newChild })); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void RemoveChildThrowsOnMissingChild() | ||
| { | ||
| var newChild = this.NewNode(); | ||
| Assert.Throws<ArgumentException>(() => this.node.RemoveChild(newChild)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void RemoveChildDeep() | ||
| { | ||
| var grandChild1 = this.NewNode(); | ||
| var child = this.NewNode(grandChild1); | ||
| var parent = this.NewNode(child); | ||
|
|
||
| var newParent = parent.RemoveDescendent(grandChild1); | ||
| Assert.Single(parent.Children[0].Children); | ||
| Assert.Empty(newParent.Children[0].Children); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ReplaceChild() | ||
| { | ||
| var newChild1 = this.NewNode(); | ||
| var newChild2 = this.NewNode(); | ||
| var nodeWithChildren = this.node.AddChildren(newChild1); | ||
| var newNodeWithChildren = nodeWithChildren.ReplaceDescendent(newChild1, newChild2); | ||
| Assert.Single(nodeWithChildren.Children); | ||
| Assert.Single(newNodeWithChildren.Children); | ||
| Assert.Same(newChild1, nodeWithChildren.Children[0]); | ||
| Assert.Same(newChild2, newNodeWithChildren.Children[0]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ReplaceChildThrowsOnMissingChild() | ||
| { | ||
| var newChild1 = this.NewNode(); | ||
| var newChild2 = this.NewNode(); | ||
| Assert.Throws<ArgumentException>(() => this.node.ReplaceDescendent(newChild1, newChild2)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ReplaceChildDeep() | ||
| { | ||
| var grandChild1 = this.NewNode(); | ||
| var grandChild2 = this.NewNode(); | ||
| var child = this.NewNode(grandChild1); | ||
| var parent = this.NewNode(child); | ||
|
|
||
| var newParent = parent.ReplaceDescendent(grandChild1, grandChild2); | ||
| Assert.Same(grandChild1, parent.Children[0].Children[0]); | ||
| Assert.Single(newParent.Children[0].Children); | ||
| Assert.Same(grandChild2, newParent.Children[0].Children[0]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ReplaceNodeWithChildrenAndChangeIdentity() | ||
| { | ||
| var grandchild = this.NewTree("grandchild"); | ||
| var child = this.NewTree("child", new[] { grandchild }); | ||
| this.node = this.node.AddChildren(child); | ||
|
|
||
| // Ensure we exercise our lookup table update code by filling the tree with enough nodes. | ||
| for (int i = 0; i < RecursiveTypeExtensions.InefficiencyLoadThreshold; i++) | ||
| { | ||
| this.node = this.node.AddChildren(this.NewTree("child " + i)); | ||
| } | ||
|
|
||
| // Verify that we can find the interesting child. | ||
| var spine = this.node.GetSpine(child.Identity); | ||
| Assert.Same(this.node, spine.Peek()); | ||
| Assert.Same(child, spine.Pop().Peek()); | ||
|
|
||
| // Now replace the child with one of a different identity. | ||
| var newChild = this.NewTree("newChild", child.Children); | ||
| this.node = this.node.ReplaceDescendent(child, newChild); | ||
|
|
||
| spine = this.node.GetSpine(newChild.Identity); | ||
| Assert.Same(this.node, spine.Peek()); | ||
| Assert.Same(newChild, spine.Last()); | ||
|
|
||
| spine = this.node.GetSpine(grandchild.Identity); | ||
| Assert.Same(this.node, spine.Peek()); | ||
| Assert.Same(newChild, spine.Pop().Peek()); | ||
| Assert.Same(grandchild, spine.Last()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Contains() | ||
| { | ||
| var leaf = (ProjectTree)this.NewTree("leaf"); | ||
| var expectedChain = new List<ProjectTree>(); | ||
| var head = leaf; | ||
| expectedChain.Add(head); | ||
| for (int i = 0; i < RecursiveTypeExtensions.InefficiencyLoadThreshold * 3; i++) | ||
| { | ||
| head = (ProjectTree)this.NewTree("step " + (i + 1), children: new[] { head }); | ||
| expectedChain.Insert(0, head); | ||
| } | ||
|
|
||
| for (int i = 0; i < expectedChain.Count - 1; i++) | ||
| { | ||
| Assert.True(expectedChain[i].HasDescendent(expectedChain.Last().Identity)); | ||
| Assert.False(expectedChain[i].HasDescendent(this.NewTree("missing").Identity)); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Validate() | ||
| { | ||
| var node1 = this.NewNode(); | ||
| var node2 = this.NewNode(node1); | ||
| RecursiveTypeExtensions.LookupTable<ProjectTree, ProjectTree>.ValidateInternalIntegrity(node1); | ||
| RecursiveTypeExtensions.LookupTable<ProjectTree, ProjectTree>.ValidateInternalIntegrity(node2); | ||
| Assert.Throws<RecursiveChildNotUniqueException>(() => | ||
| { | ||
| var cycle = node1.AddChildren(node2); | ||
| RecursiveTypeExtensions.LookupTable<ProjectTree, ProjectTree>.ValidateInternalIntegrity(cycle); | ||
| }); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetSpine() | ||
| { | ||
| var path = this.node.GetSpine(this.node.Identity).ToList(); | ||
| Assert.Single(path); | ||
| Assert.Same(this.node, path[0]); | ||
|
|
||
| ProjectTree child; | ||
| var parent = this.node.AddChildren(child = this.NewTree("child")); | ||
| path = parent.GetSpine(child.Identity).ToList(); | ||
| Assert.Equal(2, path.Count); | ||
| Assert.Same(parent, path[0]); | ||
| Assert.Same(child, path[1]); | ||
|
|
||
| var leaf = this.NewTree("leaf"); | ||
| for (int steps = 0; steps < RecursiveTypeExtensions.InefficiencyLoadThreshold * 3; steps++) | ||
| { | ||
| var expectedChain = new List<ProjectTree>(steps + 1); | ||
| var head = leaf; | ||
| expectedChain.Add(head); | ||
| for (int i = 0; i < steps; i++) | ||
| { | ||
| head = (ProjectTree)this.NewTree("step " + (i + 1), head); | ||
| expectedChain.Insert(0, head); | ||
| } | ||
|
|
||
| // Find every single node along the chain from the head to the tail. | ||
| for (int i = 0; i <= steps; i++) | ||
| { | ||
| var actualChain = head.GetSpine(expectedChain[i].Identity).ToList(); | ||
| Assert.Equal(expectedChain.Take(i + 1).Select(n => n.Identity).ToList(), actualChain.Select(n => n.Identity).ToList()); | ||
| } | ||
|
|
||
| // Now find the tail from every node, starting at the tail to get code coverage on the inner-nodes' lazy search-building capabilities. | ||
| for (int i = steps; i >= 0; i--) | ||
| { | ||
| var actualChain = expectedChain[i].GetSpine(expectedChain.Last().Identity).ToList(); | ||
| Assert.Equal(expectedChain.Skip(i).Select(n => n.Identity).ToList(), actualChain.Select(n => n.Identity).ToList()); | ||
| } | ||
|
|
||
| // And test searches for non-related nodes. | ||
| Assert.True(head.GetSpine(this.node.Identity).IsEmpty); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AsProjectItemTree_OnNonItem() | ||
| { | ||
| var root = ProjectTree.Create("hi").AsRoot; | ||
| var item = root.AsProjectItemTree; | ||
| Assert.True(item.IsDefault); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void FindThrowsOnMissingNode() | ||
| { | ||
| var child = this.NewTree("child"); | ||
| Assert.Throws<KeyNotFoundException>(() => this.node.Find(child.Identity)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void EqualsTest() | ||
| { | ||
| Assert.False(this.node.Equals((object)null)); | ||
| Assert.False(this.node.Equals((ProjectTree)null)); | ||
| Assert.True(this.node.Equals((object)this.node)); | ||
| Assert.True(this.node.Equals((ProjectTree)this.node)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetHashCodeTest() | ||
| { | ||
| var newNode = this.node.WithCaption("some caption"); | ||
| Assert.NotEqual(this.node.GetHashCode(), newNode.GetHashCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void EqualsIdentityComparerTest() | ||
| { | ||
| var newNode = this.node.WithCaption("some caption"); | ||
| Assert.NotEqual(this.node, newNode, EqualityComparer<ProjectTree>.Default); | ||
| Assert.Equal(this.node, newNode, ProjectTree.Comparers.Identity); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetHashCodeIdentityComparerTest() | ||
| { | ||
| var newNode = this.node.WithCaption("some caption"); | ||
| Assert.Equal(ProjectTree.Comparers.Identity.GetHashCode(this.node), ProjectTree.Comparers.Identity.GetHashCode(newNode)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| //----------------------------------------------------------------------- | ||
| // <copyright file="ProjectTreeNodeTestBase.cs" company="Microsoft"> | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // </copyright> | ||
| //----------------------------------------------------------------------- | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using System.Text; | ||
|
|
||
| /// <summary> | ||
| /// A base class for testing of red and green trees. | ||
| /// </summary> | ||
| public abstract class ProjectTreeNodeTestBase : IDisposable | ||
| { | ||
| internal const string Caption = "some caption.txt"; | ||
|
|
||
| internal const string ModifiedCaption = "some other caption.txt"; | ||
|
|
||
| internal const string ItemType = "itemType"; | ||
|
|
||
| internal const string ItemName = "some item name.txt"; | ||
|
|
||
| private int nodeCounter; | ||
|
|
||
| internal ImmutableSortedSet<ProjectTree> Children { get; set; } | ||
|
|
||
| public ProjectTreeNodeTestBase() | ||
| { | ||
| this.nodeCounter = 0; | ||
| this.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| this.Cleanup(); | ||
| } | ||
|
|
||
| protected virtual void Cleanup() | ||
| { | ||
| } | ||
|
|
||
| internal ProjectTree NewNode(params ProjectTree[] children) | ||
| { | ||
| this.nodeCounter++; | ||
| var tree = ProjectTree.Create(Caption + this.nodeCounter); | ||
| if (children != null) | ||
| { | ||
| tree = tree.WithChildren(children); | ||
| } | ||
|
|
||
| return tree; | ||
| } | ||
|
|
||
| internal ProjectTree NewTree(string caption, ProjectTree singleChild) | ||
| { | ||
| return this.NewTree(caption, new[] { singleChild }); | ||
| } | ||
|
|
||
| internal ProjectTree NewTree(string caption, IEnumerable<ProjectTree> children = null) | ||
| { | ||
| var tree = ProjectTree.Create(caption); | ||
| if (children != null) | ||
| { | ||
| tree = tree.WithChildren(children); | ||
| } | ||
|
|
||
| return tree; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Validation; | ||
|
|
||
| [DebuggerDisplay("{Caption} ({Identity})")] | ||
| partial class ProjectTree | ||
| { | ||
| public static IReadOnlyList<DiffGram> GetDelta(ProjectTree before, ProjectTree after) | ||
| { | ||
| return after.ChangesSince(before); | ||
| } | ||
|
|
||
| static partial void CreateDefaultTemplate(ref ProjectTree.Template template) | ||
| { | ||
| template.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default); | ||
| template.Capabilities = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase); | ||
| template.Visible = true; | ||
| } | ||
|
|
||
| public override string ToString() | ||
| { | ||
| return this.Caption; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| //----------------------------------------------------------------------- | ||
| // <copyright file="ProjectTreeSort.cs" company="Microsoft"> | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // </copyright> | ||
| //----------------------------------------------------------------------- | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using Validation; | ||
|
|
||
| /// <summary> | ||
| /// Standard sort routine(s) for project tree nodes. | ||
| /// </summary> | ||
| internal class ProjectTreeSort : IComparer<ProjectTree> | ||
| { | ||
| /// <summary> | ||
| /// Backing field for the <see cref="Default"/> property. | ||
| /// </summary> | ||
| private static IComparer<ProjectTree> defaultInstance = new ProjectTreeSort(); | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="ProjectTreeSort"/> class. | ||
| /// </summary> | ||
| private ProjectTreeSort() | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the default sorting algorithm, which sorts alphabetically, puts folders on top, and any special folders or items above that. | ||
| /// </summary> | ||
| internal static IComparer<ProjectTree> Default | ||
| { | ||
| get { return defaultInstance; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Compares two objects and returns a value indicating whether one is less than, | ||
| /// equal to, or greater than the other. | ||
| /// </summary> | ||
| /// <param name="x">The first object to compare.</param> | ||
| /// <param name="y">The second object to compare.</param> | ||
| /// <returns>Standard -1, 0, 1 sorting responses.</returns> | ||
| public int Compare(ProjectTree x, ProjectTree y) | ||
| { | ||
| Requires.NotNull(x, "x"); | ||
| Requires.NotNull(y, "y"); | ||
|
|
||
| // Start by putting "special" folders on top. | ||
| bool xUp = x.Capabilities.Contains(ProjectTreeCapabilities.BubbleUp); | ||
| bool yUp = y.Capabilities.Contains(ProjectTreeCapabilities.BubbleUp); | ||
| if (xUp ^ yUp) | ||
| { | ||
| return xUp ? -1 : 1; | ||
| } | ||
|
|
||
| // Then folders should appear above files. | ||
| bool xFolder = x.Capabilities.Contains(ProjectTreeCapabilities.Folder); | ||
| bool yFolder = y.Capabilities.Contains(ProjectTreeCapabilities.Folder); | ||
| if (xFolder ^ yFolder) | ||
| { | ||
| return xFolder ? -1 : 1; | ||
| } | ||
|
|
||
| // Finally, sort alphabetically. Notice we use CurrentCulture here to get the right effect when sorting. | ||
| int cultureIgnoreCaseSort = StringComparer.CurrentCultureIgnoreCase.Compare(x.Caption, y.Caption); | ||
| if (cultureIgnoreCaseSort == 0) | ||
| { | ||
| // The rule is that two items are equivalent in this tree only if they are ordinal-equivalent, | ||
| // not just culture-equivalent. For instance if one inserted an item with a Turkish ı and English I | ||
| // (for instance fıle.txt and fIle.txt), such items should be considered distinct, but a culture | ||
| // comparison would return equivalence. This causes problems as code to check for existence of items | ||
| // in the tree uses explicit ordinal comparisons, but when an item is inserted, the comparison in this | ||
| // method is used to validate the tree's state. | ||
| return StringComparer.OrdinalIgnoreCase.Compare(x.Caption, y.Caption); | ||
| } | ||
|
|
||
| return cultureIgnoreCaseSort; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Validation; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| public class ProjectTreeTests | ||
| { | ||
| private readonly ITestOutputHelper logger; | ||
|
|
||
| public ProjectTreeTests(ITestOutputHelper logger) | ||
| { | ||
| Requires.NotNull(logger, nameof(logger)); | ||
| this.logger = logger; | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ProjectTreeValueComparer() | ||
| { | ||
| var tree = ProjectTree.Create("root"); | ||
| Assert.Equal(tree, tree, ProjectTree.Comparers.Identity); | ||
| Assert.Equal(tree, tree, ProjectTree.Comparers.ByValue); | ||
|
|
||
| var newPath = tree.WithFilePath("c:\\some\\path"); | ||
| Assert.Equal(tree, newPath, ProjectTree.Comparers.Identity); | ||
| Assert.NotEqual(tree, newPath, ProjectTree.Comparers.ByValue); | ||
|
|
||
| var changedBackToOriginal = newPath.WithFilePath(tree.FilePath); | ||
| Assert.Equal(tree, changedBackToOriginal, ProjectTree.Comparers.Identity); | ||
| Assert.Equal(tree, changedBackToOriginal, ProjectTree.Comparers.ByValue); | ||
|
|
||
| var derivedType = tree.ToProjectItemTree(new ProjectPropertiesContext()); | ||
| Assert.Equal(tree, derivedType, ProjectTree.Comparers.Identity); | ||
| Assert.NotEqual(tree, derivedType, ProjectTree.Comparers.ByValue); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ProjectTreeChangesSinceLargeTreeTest() | ||
| { | ||
| int seed = Environment.TickCount; | ||
| this.logger.WriteLine("Random seed: {0}", seed); | ||
| var random = new Random(seed); | ||
| var largeTree = ConstructVeryLargeTree(random, 4, 500, 10000); | ||
| int actualSize = largeTree.ProjectTree.GetSelfAndDescendents().Count(); | ||
| this.logger.WriteLine("Total tree size: {0} nodes", actualSize); | ||
| IRecursiveParent lt = largeTree.ProjectTree; | ||
| //lt.Write(Console.Out); | ||
|
|
||
| // Pick one random node to change. | ||
| var changedNodeIdentity = ((uint)random.Next(actualSize)) + largeTree.Identity; | ||
| var originalNode = largeTree.Find(changedNodeIdentity); | ||
| var changedNode = originalNode.WithCaption("Changed!"); | ||
|
|
||
| // Now diff the two versions. | ||
| var changes = largeTree.ChangesSince(changedNode.Root); | ||
| Assert.Equal(1, changes.Count); | ||
| Assert.Equal(ChangeKind.Replaced, changes[0].Kind); | ||
| Assert.Equal(ProjectTreeChangedProperties.Caption, changes[0].Changes & ~ProjectTreeChangedProperties.PositionUnderParent); | ||
| Assert.Equal(changedNodeIdentity, changes[0].Identity); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConstructLargeTreeOptimal() | ||
| { | ||
| this.ConstructLargeTreeOptimalHelper(null, 1000); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Constructs a tree based on a template in top down order (root to leaf), | ||
| /// instead of the optimal leaf to roof order. | ||
| /// </summary> | ||
| [Fact] | ||
| public void ConstructLargeTreeSuboptimalWithoutBuilder() | ||
| { | ||
| this.CloneProjectTreeRootToLeafWithoutBuilders(ConstructLargeTreeOptimalHelper()); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Constructs a tree based on a template in top down order (root to leaf), | ||
| /// instead of the optimal leaf to roof order. | ||
| /// </summary> | ||
| [Fact] | ||
| public void ConstructLargeTreeSuboptimalUsingBuilder() | ||
| { | ||
| this.CloneProjectTreeRootToLeafWithBuilders(ConstructLargeTreeOptimalHelper()); | ||
| } | ||
|
|
||
| internal RootedProjectTree ConstructLargeTreeOptimalHelper(int? seed = null, int maxSize = 1000) | ||
| { | ||
| int randSeed = seed.HasValue ? seed.Value : Environment.TickCount; | ||
| this.logger.WriteLine("Random seed: {0}", randSeed); | ||
| var random = new Random(randSeed); | ||
| return ConstructVeryLargeTree(random, 4, 100, maxSize); | ||
| } | ||
|
|
||
| internal RootedProjectTree CloneProjectTreeLeafToRoot(RootedProjectTree templateTree) | ||
| { | ||
| var clone = ProjectTree.Create(templateTree.Caption).AddChildren(templateTree.ProjectTree.Children.Select(this.CloneProjectTreeLeafToRoot)); | ||
| return clone.AsRoot; | ||
| } | ||
|
|
||
| internal ProjectTree CloneProjectTreeLeafToRoot(ProjectTree templateTree) | ||
| { | ||
| var clone = ProjectTree.Create(templateTree.Caption).AddChildren(templateTree.Children.Select(this.CloneProjectTreeLeafToRoot)); | ||
| return clone; | ||
| } | ||
|
|
||
| internal RootedProjectTree CloneProjectTreeRootToLeafWithoutBuilders(RootedProjectTree templateTree) | ||
| { | ||
| var root = RootedProjectTree.Create(templateTree.Caption); | ||
| var rootWithChildren = RecursiveAddChildren(templateTree.ProjectTree, root); | ||
| return rootWithChildren; | ||
| } | ||
|
|
||
| internal RootedProjectTree CloneProjectTreeRootToLeafWithBuilders(RootedProjectTree templateTree) | ||
| { | ||
| var rootBuilder = ProjectTree.Create(templateTree.Caption).ToBuilder(); | ||
| RecursiveAddChildren(templateTree.ProjectTree, rootBuilder); | ||
| var root = rootBuilder.ToImmutable(); | ||
| return root.AsRoot; | ||
| } | ||
|
|
||
| private static void RecursiveAddChildren(ProjectTree template, ProjectTree.Builder receiver) | ||
| { | ||
| foreach (var templateChild in template) | ||
| { | ||
| var clonedTemplateChild = ProjectTree.Create(templateChild.Caption).ToBuilder(); | ||
| RecursiveAddChildren(templateChild, clonedTemplateChild); | ||
| receiver.Children.Add(clonedTemplateChild.ToImmutable()); | ||
| } | ||
| } | ||
|
|
||
| private static RootedProjectTree RecursiveAddChildren(ProjectTree template, RootedProjectTree receiver) | ||
| { | ||
| RootedProjectTree latest = receiver; | ||
| foreach (var templateChild in template) | ||
| { | ||
| var clonedTemplateChild = ProjectTree.Create(templateChild.Caption); | ||
| var asChild = latest.AddChild(clonedTemplateChild).Value; | ||
| var childWithChildren = RecursiveAddChildren(templateChild, asChild); | ||
| latest = childWithChildren.Parent; | ||
| } | ||
|
|
||
| return latest; | ||
| } | ||
|
|
||
| internal static RootedProjectTree ConstructVeryLargeTree(Random random, int depth, int maxImmediateChildrenCount, int totalNodeCount, Func<string> counter = null) | ||
| { | ||
| Requires.NotNull(random, "random"); | ||
| Requires.Range(depth > 0, "maxDepth"); | ||
| Requires.Range(totalNodeCount > 0, "totalNodeCount"); | ||
|
|
||
| if (counter == null) | ||
| { | ||
| int counterPosition = 0; | ||
| counter = () => "Node " + ++counterPosition; | ||
| } | ||
|
|
||
| var tree = RootedProjectTree.Create(counter()); | ||
| int nodesAllocated = 1; | ||
|
|
||
| int maxChildrenCount = Math.Min(maxImmediateChildrenCount, totalNodeCount - nodesAllocated); | ||
| if (depth == 1) | ||
| { | ||
| tree = tree.AddChildren(Enumerable.Range(1, maxChildrenCount).Select(n => ProjectTree.Create(counter()))); | ||
| nodesAllocated += maxChildrenCount; | ||
| } | ||
| else | ||
| { | ||
| int childrenCount = random.Next(maxChildrenCount) + 1; | ||
| int sizePerBranch = (totalNodeCount - nodesAllocated) / childrenCount; | ||
| if (sizePerBranch > 0) | ||
| { | ||
| tree = tree.AddChildren(Enumerable.Range(1, childrenCount).Select(n => ConstructVeryLargeTree(random, depth - 1, maxImmediateChildrenCount, sizePerBranch, counter).ProjectTree)); | ||
| } | ||
| } | ||
|
|
||
| return tree; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using Xunit; | ||
|
|
||
| public class RequiresAndHierarchyTests | ||
| { | ||
| [Fact] | ||
| public void RequiredFieldNotDeclaredFirst() | ||
| { | ||
| ReqAndHierL1 value = ReqAndHierL1.Create("value2"); | ||
| Assert.Null(value.L1Field1); | ||
| Assert.Equal("value2", value.L1Field2); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void RequiredFieldsAppearInMultipleRelatedTypes() | ||
| { | ||
| ReqAndHierL2 value = ReqAndHierL2.Create("l1f2Value", "l2f2Value"); | ||
| Assert.Equal("l1f2Value", value.L1Field2); | ||
| Assert.Equal("l2f2Value", value.L2Field2); | ||
| Assert.Null(value.L1Field1); | ||
| Assert.Null(value.L2Field1); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| [GenerateImmutable] | ||
| partial class ReqAndHierL1 | ||
| { | ||
| readonly string l1Field1; | ||
| [Required] | ||
| readonly string l1Field2; | ||
| } | ||
|
|
||
| [GenerateImmutable] | ||
| partial class ReqAndHierL2 : ReqAndHierL1 | ||
| { | ||
| readonly string l2Field1; | ||
| [Required] | ||
| readonly string l2Field2; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| [GenerateImmutable(DefineRootedStruct = true)] | ||
| public partial class Tree | ||
| { | ||
| readonly ImmutableSortedSet<Tree> children; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| public class SealedTests | ||
| { | ||
| [Fact] | ||
| public void SealedCreateBuilder() | ||
| { | ||
| Sealed.CreateBuilder(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SealedWithBaseCreateBuilder() | ||
| { | ||
| Sealed_WithBase.CreateBuilder(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| [GenerateImmutable(DefineInterface = true, DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| sealed partial class Sealed | ||
| { | ||
| readonly string name; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineInterface = true, DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| partial class Sealed_UnsealedBase | ||
| { | ||
| readonly string name; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineInterface = true, DefineWithMethodsPerProperty = true, GenerateBuilder = true)] | ||
| sealed partial class Sealed_WithBase : Sealed_UnsealedBase | ||
| { | ||
| readonly int age; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| public class TreeNodeTests | ||
| { | ||
| [Fact] | ||
| public void TreeConstruction() | ||
| { | ||
| var root = TreeNode.Create("temp", @"c:\temp\").WithChildren( | ||
| TreeNode.Create("a.cs", @"c:\temp\a.cs"), | ||
| TreeNode.Create("subfolder", @"c:\temp\subfolder\").WithChildren( | ||
| TreeNode.Create("g.h", @"c:\temp\subfolder\g.h"))); | ||
|
|
||
| Assert.Equal("temp", root.Caption); | ||
| Assert.Equal("a.cs", root.Children[0].Caption); | ||
| Assert.Equal("subfolder", root.Children[1].Caption); | ||
| Assert.Equal("g.h", root.Children[1].Children[0].Caption); | ||
|
|
||
| var root2 = root.AddChildren(TreeNode.Create("b.cs", @"c:\temp\b.cs")); | ||
| Assert.Equal(3, root2.Children.Count); | ||
|
|
||
| var root3 = root2.RemoveChildren(root2.Children[0]); | ||
| Assert.Equal(2, root3.Children.Count); | ||
| Assert.Equal(root2.Children[1], root3.Children[0]); | ||
| } | ||
| } | ||
|
|
||
| [DebuggerDisplay("{FilePath}")] | ||
| partial class TreeNode | ||
| { | ||
| static partial void CreateDefaultTemplate(ref TreeNode.Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<TreeNode>(); | ||
| template.Visible = true; | ||
| template.Attributes = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| [DebuggerDisplay("{FilePath}")] | ||
| partial class Builder | ||
| { | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System.Collections.Immutable; | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true)] | ||
| partial class TreeNode | ||
| { | ||
| readonly string caption; | ||
| readonly string filePath; | ||
| readonly bool visible; | ||
| readonly ImmutableHashSet<string> attributes; | ||
| readonly ImmutableList<TreeNode> children; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using ImmutableObjectGraph; // this Using statement should remain | ||
| using ImmutableObjectGraph.Generation; | ||
|
|
||
| [GenerateImmutable] | ||
| partial class SomeClass | ||
| { | ||
| ImmutableDeque<int> deque; // something to use the ImmutableObjectGraph namespace | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| public class XmlNodeTests | ||
| { | ||
| [Fact] | ||
| public void XmlTreeConstruction() | ||
| { | ||
| XmlElement root = XmlElement.Create("Root").WithChildren( | ||
| XmlElement.Create("Child1"), | ||
| XmlElement.Create("Child2")); | ||
|
|
||
| XmlElement xe = XmlElement.Create("l1", "n1"); | ||
| XmlElement xel2 = xe.With("l2"); | ||
| Assert.Equal("l2", xel2.LocalName); | ||
| Assert.Equal("n1", xel2.NamespaceName); | ||
| XmlElement xel3n3 = xe.With("l3", "n3"); | ||
| Assert.Equal("l3", xel3n3.LocalName); | ||
| Assert.Equal("n3", xel3n3.NamespaceName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies that the With overloads don't cause compiler errors due to ambiguity. | ||
| /// </summary> | ||
| [Fact] | ||
| public void WithNoArguments() | ||
| { | ||
| XmlElement e = XmlElement.Create("ln1", "ns1"); | ||
| XmlElement e2 = e.With(); | ||
| Assert.Equal("ln1", e2.LocalName); | ||
| Assert.Equal("ns1", e2.NamespaceName); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Polymorphism() | ||
| { | ||
| XmlElement e = XmlElement.Create("ln1", "ns1"); | ||
| XmlNode n = e; | ||
| XmlNode n2 = n.WithLocalName("newName"); | ||
| Assert.Equal("newName", n2.LocalName); | ||
| XmlElement e2 = (XmlElement)n2; | ||
| Assert.Equal(n2.LocalName, e2.LocalName); | ||
| Assert.Equal(e.NamespaceName, e2.NamespaceName); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TypeConversion() | ||
| { | ||
| XmlElement ordinaryElement = XmlElement.Create("TagName"); | ||
| Assert.IsNotType<XmlElementWithContent>(ordinaryElement); | ||
|
|
||
| // Switch to derived type, without extra data. | ||
| XmlElementWithContent elementWithContent = ordinaryElement.ToXmlElementWithContent(); | ||
| Assert.Equal(ordinaryElement.LocalName, elementWithContent.LocalName); | ||
|
|
||
| // Switch to derived type, including extra data. | ||
| elementWithContent = ordinaryElement.ToXmlElementWithContent("SomeContent"); | ||
| Assert.Equal(ordinaryElement.LocalName, elementWithContent.LocalName); | ||
| Assert.Equal("SomeContent", elementWithContent.Content); | ||
|
|
||
| // Switch back to base type. | ||
| XmlElement backAgain = elementWithContent.ToXmlElement(); | ||
| Assert.IsNotType<XmlElementWithContent>(backAgain); | ||
| Assert.Equal(ordinaryElement.LocalName, backAgain.LocalName); | ||
| } | ||
| } | ||
|
|
||
| [DebuggerDisplay("<{LocalName,nq}>")] | ||
| partial class XmlElement | ||
| { | ||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<XmlNode>(); | ||
| } | ||
| } | ||
|
|
||
| [DebuggerDisplay("<{LocalName,nq}>{Content}</{LocalName,nq}>")] | ||
| partial class XmlElementWithContent | ||
| { | ||
| static partial void CreateDefaultTemplate(ref Template template) | ||
| { | ||
| template.Children = ImmutableList.Create<XmlNode>(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| namespace ImmutableObjectGraph.Generation.Tests.TestSources | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true)] | ||
| abstract partial class XmlNode | ||
| { | ||
| readonly string localName; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true)] | ||
| partial class XmlElement : XmlNode | ||
| { | ||
| readonly string namespaceName; | ||
| readonly ImmutableList<XmlNode> children; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true)] | ||
| partial class XmlElementWithContent : XmlElement | ||
| { | ||
| readonly string content; | ||
| } | ||
|
|
||
| [GenerateImmutable(DefineWithMethodsPerProperty = true)] | ||
| partial class XmlAttribute : XmlNode | ||
| { | ||
| readonly string namespaceName; | ||
| readonly string value; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <configuration> | ||
| <appSettings> | ||
| <add key="xunit.methodDisplay" value="method" /> | ||
| </appSettings> | ||
| </configuration> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| namespace ImmutableObjectGraph.Generation | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Data.Entity.Design.PluralizationServices; | ||
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Validation; | ||
| using LookupTableHelper = RecursiveTypeExtensions.LookupTable<IRecursiveType, IRecursiveParentWithLookupTable<IRecursiveType>>; | ||
|
|
||
| public partial class CodeGen | ||
| { | ||
| protected class DefineWithMethodsPerPropertyGen : FeatureGenerator | ||
| { | ||
| internal const string WithPropertyMethodPrefix = "With"; | ||
|
|
||
| public DefineWithMethodsPerPropertyGen(CodeGen generator) | ||
| : base(generator) | ||
| { | ||
| } | ||
|
|
||
| public override bool IsApplicable | ||
| { | ||
| get { return this.generator.options.DefineWithMethodsPerProperty; } | ||
| } | ||
|
|
||
| protected override void GenerateCore() | ||
| { | ||
| var valueParameterName = SyntaxFactory.IdentifierName("value"); | ||
|
|
||
| foreach (var field in this.generator.applyToMetaType.LocalFields) | ||
| { | ||
| var withPropertyMethod = SyntaxFactory.MethodDeclaration( | ||
| GetFullyQualifiedSymbolName(this.generator.applyToSymbol), | ||
| WithPropertyMethodPrefix + field.Name.ToPascalCase()) | ||
| .WithAdditionalAnnotations() | ||
| .AddModifiers( | ||
| SyntaxFactory.Token(SyntaxKind.PublicKeyword)) | ||
| .AddAttributeLists(PureAttributeList) | ||
| .AddParameterListParameters( | ||
| SyntaxFactory.Parameter(valueParameterName.Identifier) | ||
| .WithType(GetFullyQualifiedSymbolName(field.Type))) | ||
| .WithBody(SyntaxFactory.Block( | ||
| SyntaxFactory.IfStatement( | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.EqualsExpression, | ||
| valueParameterName, | ||
| SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), field.NameAsField)), | ||
| SyntaxFactory.Block( | ||
| SyntaxFactory.ReturnStatement(SyntaxFactory.ThisExpression()))), | ||
| SyntaxFactory.ReturnStatement( | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| SyntaxFactory.ThisExpression(), | ||
| WithMethodName), | ||
| SyntaxFactory.ArgumentList( | ||
| SyntaxFactory.SingletonSeparatedList( | ||
| SyntaxFactory.Argument( | ||
| SyntaxFactory.NameColon(field.Name), | ||
| NoneToken, | ||
| Syntax.OptionalFor(valueParameterName)))))))); | ||
|
|
||
| this.innerMembers.Add(withPropertyMethod); | ||
| } | ||
|
|
||
| foreach (var field in this.generator.applyToMetaType.InheritedFields) | ||
| { | ||
| string withMethodName = WithPropertyMethodPrefix + field.Name.ToPascalCase(); | ||
| var withPropertyMethod = SyntaxFactory.MethodDeclaration( | ||
| GetFullyQualifiedSymbolName(this.generator.applyToSymbol), | ||
| withMethodName) | ||
| .AddModifiers( | ||
| SyntaxFactory.Token(SyntaxKind.NewKeyword), | ||
| SyntaxFactory.Token(SyntaxKind.PublicKeyword)) | ||
| .AddAttributeLists(PureAttributeList) | ||
| .AddParameterListParameters( | ||
| SyntaxFactory.Parameter(valueParameterName.Identifier) | ||
| .WithType(GetFullyQualifiedSymbolName(field.Type))) | ||
| .WithBody(SyntaxFactory.Block( | ||
| SyntaxFactory.ReturnStatement( | ||
| SyntaxFactory.CastExpression( | ||
| GetFullyQualifiedSymbolName(this.generator.applyToSymbol), | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| SyntaxFactory.BaseExpression(), | ||
| SyntaxFactory.IdentifierName(withMethodName)), | ||
| SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(valueParameterName)))))))); | ||
|
|
||
| this.innerMembers.Add(withPropertyMethod); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| namespace ImmutableObjectGraph.Generation | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Data.Entity.Design.PluralizationServices; | ||
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Validation; | ||
| using LookupTableHelper = RecursiveTypeExtensions.LookupTable<IRecursiveType, IRecursiveParentWithLookupTable<IRecursiveType>>; | ||
|
|
||
| public partial class CodeGen | ||
| { | ||
| protected class InterfacesGen : FeatureGenerator | ||
| { | ||
| public InterfacesGen(CodeGen generator) | ||
| : base(generator) | ||
| { | ||
| } | ||
|
|
||
| public override bool IsApplicable | ||
| { | ||
| get { return this.generator.options.DefineInterface; } | ||
| } | ||
|
|
||
| protected override BaseTypeSyntax[] AdditionalApplyToBaseTypes | ||
| { | ||
| get | ||
| { | ||
| return new BaseTypeSyntax[] { SyntaxFactory.SimpleBaseType( | ||
| SyntaxFactory.IdentifierName("I" + this.generator.applyTo.Identifier.Text)) }; | ||
| } | ||
| } | ||
|
|
||
| protected override void GenerateCore() | ||
| { | ||
| var iface = SyntaxFactory.InterfaceDeclaration( | ||
| "I" + this.generator.applyTo.Identifier.Text) | ||
| .AddModifiers(GetModifiersForAccessibility(this.generator.applyToSymbol)) | ||
| .WithMembers( | ||
| SyntaxFactory.List<MemberDeclarationSyntax>( | ||
| from field in this.generator.applyToMetaType.LocalFields | ||
| select SyntaxFactory.PropertyDeclaration( | ||
| GetFullyQualifiedSymbolName(field.Type), | ||
| field.Name.ToPascalCase()) | ||
| .WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.SingletonList( | ||
| SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) | ||
| .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))))); | ||
| if (this.generator.applyToMetaType.HasAncestor) | ||
| { | ||
| iface = iface.WithBaseList(SyntaxFactory.BaseList( | ||
| SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>(SyntaxFactory.SimpleBaseType( | ||
| SyntaxFactory.IdentifierName("I" + this.generator.applyToMetaType.Ancestor.TypeSymbol.Name))))); | ||
| } | ||
|
|
||
| this.siblingMembers.Add(iface); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| namespace ImmutableObjectGraph.Generation | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Data.Entity.Design.PluralizationServices; | ||
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Validation; | ||
| using LookupTableHelper = RecursiveTypeExtensions.LookupTable<IRecursiveType, IRecursiveParentWithLookupTable<IRecursiveType>>; | ||
|
|
||
| public partial class CodeGen | ||
| { | ||
| protected class RecursiveTypeGen : FeatureGenerator | ||
| { | ||
| public RecursiveTypeGen(CodeGen generator) | ||
| : base(generator) | ||
| { | ||
| } | ||
|
|
||
| public override bool IsApplicable | ||
| { | ||
| get { return this.generator.applyToMetaType.IsRecursiveType; } | ||
| } | ||
|
|
||
| protected override void GenerateCore() | ||
| { | ||
| this.baseTypes.Add(SyntaxFactory.SimpleBaseType(Syntax.GetTypeSyntax(typeof(IRecursiveType)))); | ||
|
|
||
| //// uint IRecursiveType.Identity => this.Identity; | ||
| this.innerMembers.Add(SyntaxFactory.PropertyDeclaration( | ||
| IdentityFieldTypeSyntax, | ||
| nameof(IRecursiveType.Identity)) | ||
| .WithExplicitInterfaceSpecifier( | ||
| SyntaxFactory.ExplicitInterfaceSpecifier(Syntax.GetTypeSyntax(typeof(IRecursiveType)))) | ||
| .WithExpressionBody( | ||
| SyntaxFactory.ArrowExpressionClause(Syntax.ThisDot(IdentityPropertyName))) | ||
| .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) | ||
| .AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(DebuggerBrowsableNeverAttribute)))); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| namespace ImmutableObjectGraph.Generation | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Data.Entity.Design.PluralizationServices; | ||
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Validation; | ||
|
|
||
| public partial class CodeGen | ||
| { | ||
| protected class StyleCopCompliance : FeatureGenerator | ||
| { | ||
| public StyleCopCompliance(CodeGen generator) : base(generator) | ||
| { | ||
| } | ||
|
|
||
| public override bool IsApplicable | ||
| { | ||
| get { return true; } | ||
| } | ||
|
|
||
| protected override void GenerateCore() | ||
| { | ||
| } | ||
|
|
||
| public override SyntaxList<MemberDeclarationSyntax> ProcessFinalGeneratedResult(SyntaxList<MemberDeclarationSyntax> applyToAndOtherTypes) | ||
| { | ||
| var result = base.ProcessFinalGeneratedResult(applyToAndOtherTypes); | ||
|
|
||
| for (int i = 0; i < result.Count; i++) | ||
| { | ||
| var member = result[i]; | ||
| var types = member.DescendantNodesAndSelf(n => n is ClassDeclarationSyntax || n is StructDeclarationSyntax) | ||
| .OfType<TypeDeclarationSyntax>() | ||
| .ToArray(); | ||
| var trackingMember = member.TrackNodes(types); | ||
|
|
||
| foreach (var type in types) | ||
| { | ||
| var currentMember = trackingMember.GetCurrentNode(type); | ||
| var updatedMember = currentMember; | ||
| var cl = currentMember as ClassDeclarationSyntax; | ||
| if (cl != null) | ||
| { | ||
| updatedMember = SortMembers(cl); | ||
| } | ||
|
|
||
| var str = currentMember as StructDeclarationSyntax; | ||
| if (str != null) | ||
| { | ||
| updatedMember = SortMembers(str); | ||
| } | ||
|
|
||
| trackingMember = trackingMember.ReplaceNode(currentMember, updatedMember); | ||
| } | ||
|
|
||
| result = result.Replace(member, trackingMember); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static ClassDeclarationSyntax SortMembers(ClassDeclarationSyntax type) | ||
| { | ||
| var innerMembers = type.Members.ToList(); | ||
| innerMembers.Sort(StyleCop.Sort); | ||
| type = type.WithMembers(SyntaxFactory.List(innerMembers)); | ||
| return type; | ||
| } | ||
|
|
||
| private static StructDeclarationSyntax SortMembers(StructDeclarationSyntax type) | ||
| { | ||
| var innerMembers = type.Members.ToList(); | ||
| innerMembers.Sort(StyleCop.Sort); | ||
| type = type.WithMembers(SyntaxFactory.List(innerMembers)); | ||
| return type; | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,261 @@ | ||
| namespace ImmutableObjectGraph.Generation | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Data.Entity.Design.PluralizationServices; | ||
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Validation; | ||
| using LookupTableHelper = RecursiveTypeExtensions.LookupTable<IRecursiveType, IRecursiveParentWithLookupTable<IRecursiveType>>; | ||
|
|
||
| public partial class CodeGen | ||
| { | ||
| protected class TypeConversionGen : FeatureGenerator | ||
| { | ||
| private static readonly IdentifierNameSyntax CreateWithIdentityMethodName = SyntaxFactory.IdentifierName("CreateWithIdentity"); | ||
|
|
||
| public TypeConversionGen(CodeGen generator) | ||
| : base(generator) | ||
| { | ||
| } | ||
|
|
||
| public override bool IsApplicable | ||
| { | ||
| get { return true; } | ||
| } | ||
|
|
||
| protected override void GenerateCore() | ||
| { | ||
| if (!this.generator.applyToSymbol.IsAbstract && (this.generator.applyToMetaType.HasAncestor || this.generator.applyToMetaType.Descendents.Any())) | ||
| { | ||
| this.innerMembers.Add(this.CreateCreateWithIdentityMethod()); | ||
| } | ||
|
|
||
| if (this.generator.applyToMetaType.HasAncestor && !this.generator.applyToMetaType.Ancestor.TypeSymbol.IsAbstract) | ||
| { | ||
| this.innerMembers.Add(this.CreateToAncestorTypeMethod()); | ||
| } | ||
|
|
||
| // Only generate derived type conversion methods if we have something to add: | ||
| // either because we're the most base class, or because we have fields to add. | ||
| if (this.generator.applyToMetaType.LocalFields.Any() || !this.generator.applyToMetaType.Ancestors.Any()) | ||
| { | ||
| foreach (MetaType derivedType in this.generator.applyToMetaType.Descendents.Where(d => !d.TypeSymbol.IsAbstract)) | ||
| { | ||
| this.innerMembers.Add(this.CreateToDerivedTypeMethod(derivedType)); | ||
|
|
||
| if (this.generator.applyToMetaType.LocalFields.Any()) | ||
| { | ||
| foreach (MetaType ancestor in this.generator.applyToMetaType.Ancestors.Where(a => a.LocalFields.Any())) | ||
| { | ||
| this.innerMembers.Add(this.CreateToDerivedTypeOverrideMethod(derivedType, ancestor)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal static IdentifierNameSyntax GetToTypeMethodName(string typeName) | ||
| { | ||
| return SyntaxFactory.IdentifierName("To" + typeName); | ||
| } | ||
|
|
||
| private MemberDeclarationSyntax CreateCreateWithIdentityMethod() | ||
| { | ||
| ExpressionSyntax returnExpression = DefaultInstanceFieldName; | ||
| if (this.generator.applyToMetaType.LocalFields.Any()) | ||
| { | ||
| returnExpression = SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| returnExpression, | ||
| WithFactoryMethodName), | ||
| this.generator.CreateArgumentList(this.generator.applyToMetaType.AllFields, ArgSource.OptionalArgumentOrTemplate, OptionalStyle.Always) | ||
| .AddArguments(OptionalIdentityArgument)); | ||
| } | ||
|
|
||
| var method = SyntaxFactory.MethodDeclaration( | ||
| this.generator.applyToTypeName, | ||
| CreateWithIdentityMethodName.Identifier) | ||
| .AddModifiers( | ||
| SyntaxFactory.Token(SyntaxKind.InternalKeyword), | ||
| SyntaxFactory.Token(SyntaxKind.StaticKeyword)) | ||
| .WithParameterList( | ||
| this.generator.CreateParameterList( | ||
| this.generator.applyToMetaType.AllFields, | ||
| ParameterStyle.OptionalOrRequired) | ||
| .AddParameters(OptionalIdentityParameter)) | ||
| .WithBody(SyntaxFactory.Block( | ||
| SyntaxFactory.IfStatement( | ||
| SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, Syntax.OptionalIsDefined(IdentityParameterName)), | ||
| SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( | ||
| SyntaxKind.SimpleAssignmentExpression, | ||
| IdentityParameterName, | ||
| SyntaxFactory.InvocationExpression(NewIdentityMethodName, SyntaxFactory.ArgumentList())))), | ||
| SyntaxFactory.ReturnStatement(returnExpression))); | ||
|
|
||
| // BUG: the condition should be if there are local fields on *any* ancestor | ||
| // from the closest non-abstract ancestor (exclusive) to this type (inclusive). | ||
| if (!this.generator.applyToMetaType.LocalFields.Any() && this.generator.applyToMetaType.Ancestors.Any(a => !a.TypeSymbol.IsAbstract)) | ||
| { | ||
| method = Syntax.AddNewKeyword(method); | ||
| } | ||
|
|
||
| return method; | ||
| } | ||
|
|
||
| private MemberDeclarationSyntax CreateToAncestorTypeMethod() | ||
| { | ||
| var ancestor = this.generator.applyToMetaType.Ancestor; | ||
| var ancestorType = GetFullyQualifiedSymbolName(ancestor.TypeSymbol); | ||
| return SyntaxFactory.MethodDeclaration( | ||
| ancestorType, | ||
| GetToTypeMethodName(this.generator.applyToMetaType.Ancestor.TypeSymbol.Name).Identifier) | ||
| .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) | ||
| .AddAttributeLists(PureAttributeList) | ||
| .WithBody(SyntaxFactory.Block( | ||
| SyntaxFactory.ReturnStatement( | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| ancestorType, | ||
| CreateWithIdentityMethodName), | ||
| this.generator.CreateArgumentList(ancestor.AllFields, asOptional: OptionalStyle.WhenNotRequired) | ||
| .AddArguments(RequiredIdentityArgumentFromProperty))))); | ||
| } | ||
|
|
||
| private MemberDeclarationSyntax CreateToDerivedTypeMethod(MetaType derivedType) | ||
| { | ||
| var derivedTypeName = GetFullyQualifiedSymbolName(derivedType.TypeSymbol); | ||
| var thatLocal = SyntaxFactory.IdentifierName("that"); | ||
| var body = new List<StatementSyntax>(); | ||
|
|
||
| // var that = this as DerivedType; | ||
| body.Add(SyntaxFactory.LocalDeclarationStatement( | ||
| SyntaxFactory.VariableDeclaration( | ||
| varType, | ||
| SyntaxFactory.SingletonSeparatedList( | ||
| SyntaxFactory.VariableDeclarator(thatLocal.Identifier) | ||
| .WithInitializer(SyntaxFactory.EqualsValueClause( | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.AsExpression, | ||
| SyntaxFactory.ThisExpression(), | ||
| derivedTypeName))))))); | ||
|
|
||
| // this.GetType() | ||
| var thisDotGetType = SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName("GetType")), | ||
| SyntaxFactory.ArgumentList()); | ||
|
|
||
| // {0}.Equals(typeof(derivedType)) | ||
| var thisTypeIsEquivalentToDerivedType = | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| thisDotGetType, | ||
| SyntaxFactory.IdentifierName(nameof(Type.Equals))), | ||
| SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( | ||
| SyntaxFactory.TypeOfExpression(derivedTypeName))))); | ||
|
|
||
| var ifEquivalentTypeBlock = new List<StatementSyntax>(); | ||
| var fieldsBeyond = derivedType.GetFieldsBeyond(this.generator.applyToMetaType); | ||
| if (fieldsBeyond.Any()) | ||
| { | ||
| Func<MetaField, ExpressionSyntax> isUnchanged = v => | ||
| SyntaxFactory.ParenthesizedExpression( | ||
| v.IsRequired | ||
| ? // ({0} == that.{1}) | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.EqualsExpression, | ||
| v.NameAsField, | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| thatLocal, | ||
| v.NameAsProperty)) | ||
| : // (!{0}.IsDefined || {0}.Value == that.{1}) | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.LogicalOrExpression, | ||
| SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, Syntax.OptionalIsDefined(v.NameAsField)), | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.EqualsExpression, | ||
| Syntax.OptionalValue(v.NameAsField), | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| thatLocal, | ||
| v.NameAsProperty)))); | ||
| var noChangesExpression = fieldsBeyond.Select(isUnchanged).ChainBinaryExpressions(SyntaxKind.LogicalAndExpression); | ||
|
|
||
| ifEquivalentTypeBlock.Add(SyntaxFactory.IfStatement( | ||
| noChangesExpression, | ||
| SyntaxFactory.ReturnStatement(thatLocal))); | ||
| } | ||
| else | ||
| { | ||
| ifEquivalentTypeBlock.Add(SyntaxFactory.ReturnStatement(thatLocal)); | ||
| } | ||
|
|
||
| // if (that != null && this.GetType().IsEquivalentTo(typeof(derivedType))) { ... } | ||
| body.Add(SyntaxFactory.IfStatement( | ||
| SyntaxFactory.BinaryExpression( | ||
| SyntaxKind.LogicalAndExpression, | ||
| SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, thatLocal, SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), | ||
| thisTypeIsEquivalentToDerivedType), | ||
| SyntaxFactory.Block(ifEquivalentTypeBlock))); | ||
|
|
||
| // return DerivedType.CreateWithIdentity(...) | ||
| body.Add(SyntaxFactory.ReturnStatement( | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| derivedTypeName, | ||
| CreateWithIdentityMethodName), | ||
| this.generator.CreateArgumentList(this.generator.applyToMetaType.AllFields, asOptional: OptionalStyle.WhenNotRequired) | ||
| .AddArguments(RequiredIdentityArgumentFromProperty) | ||
| .AddArguments(this.generator.CreateArgumentList(fieldsBeyond, ArgSource.Argument).Arguments.ToArray())))); | ||
|
|
||
| return SyntaxFactory.MethodDeclaration( | ||
| derivedTypeName, | ||
| GetToTypeMethodName(derivedType.TypeSymbol.Name).Identifier) | ||
| .AddModifiers( | ||
| SyntaxFactory.Token(SyntaxKind.PublicKeyword), | ||
| SyntaxFactory.Token(SyntaxKind.VirtualKeyword)) | ||
| .AddAttributeLists(PureAttributeList) | ||
| .WithParameterList(this.generator.CreateParameterList(fieldsBeyond, ParameterStyle.OptionalOrRequired)) | ||
| .WithBody(SyntaxFactory.Block(body)); | ||
| } | ||
|
|
||
| private MemberDeclarationSyntax CreateToDerivedTypeOverrideMethod(MetaType derivedType, MetaType ancestor) | ||
| { | ||
| var derivedTypeName = GetFullyQualifiedSymbolName(derivedType.TypeSymbol); | ||
| return SyntaxFactory.MethodDeclaration( | ||
| derivedTypeName, | ||
| GetToTypeMethodName(derivedType.TypeSymbol.Name).Identifier) | ||
| .AddModifiers( | ||
| SyntaxFactory.Token(SyntaxKind.PublicKeyword), | ||
| SyntaxFactory.Token(SyntaxKind.OverrideKeyword)) | ||
| .AddAttributeLists(PureAttributeList) | ||
| .WithParameterList( | ||
| this.generator.CreateParameterList(derivedType.GetFieldsBeyond(ancestor), ParameterStyle.OptionalOrRequired)) | ||
| .WithBody(SyntaxFactory.Block( | ||
| // return base.ToDerivedType(args); | ||
| SyntaxFactory.ReturnStatement( | ||
| SyntaxFactory.InvocationExpression( | ||
| SyntaxFactory.MemberAccessExpression( | ||
| SyntaxKind.SimpleMemberAccessExpression, | ||
| SyntaxFactory.BaseExpression(), | ||
| GetToTypeMethodName(derivedType.TypeSymbol.Name)), | ||
| this.generator.CreateArgumentList(this.generator.applyToMetaType.GetFieldsBeyond(ancestor), ArgSource.OptionalArgumentOrPropertyExceptWhenRequired, OptionalStyle.WhenNotRequired) | ||
| .AddArguments(this.generator.CreateArgumentList(derivedType.GetFieldsBeyond(this.generator.applyToMetaType), ArgSource.Argument).Arguments.ToArray()))))); | ||
| } | ||
| } | ||
| } | ||
| } |