/// <summary>
        /// Outputs a generated model to a string builder.
        /// </summary>
        /// <param name="sb">The string builder.</param>
        /// <param name="typeModel">The model to generate.</param>
        public void Generate(StringBuilder sb, TypeModel typeModel)
        {
            WriteHeader(sb);

            foreach (var t in TypesUsing)
                sb.AppendFormat("using {0};\n", t);

            sb.Append("\n");
            sb.AppendFormat("namespace {0}\n", GetModelsNamespace());
            sb.Append("{\n");

            WriteContentType(sb, typeModel);

            sb.Append("}\n");
        }
        public void ModelsUsingAttribute()
        {
            // Umbraco returns nice, pascal-cased names

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };

            var types = new[] { type1 };

            var code1 = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
"}
            };

            var code2 = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
[assembly:ModelsUsing(""Foo.Bar.Nil"")]
"}
            };

            var parseResult = new CodeParser().Parse(code1);
            var builder = new TextBuilder(types, parseResult);
            var count = builder.Using.Count;

            parseResult = new CodeParser().Parse(code2);
            builder = new TextBuilder(types, parseResult);

            Assert.AreEqual(count + 1, builder.Using.Count);
            Assert.IsTrue(builder.Using.Contains("Foo.Bar.Nil"));
        }
        public void ContentTypeCustomBaseClass()
        {
            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            var type2 = new TypeModel
            {
                Id = 2,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            var type3 = new TypeModel
            {
                Id = 3,
                Alias = "type3",
                ClrName = "Type3",
                ParentId = 1,
                BaseType = type1,
                ItemType = TypeModel.ItemTypes.Content,
            };
            var types = new[] { type1, type2, type3 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
using Dang;
namespace Dang
{
    public abstract class MyModelBase : PublishedContentModel
    {
        public MyModelBase(IPublishedContent content)
            : base(content)
        { }
    }

    public abstract class MyType1 : Type1
    { 
        public MyType1(IPublishedContent content)
            : base(content)
        { }
    }

    public partial class Type1
    {}

    public partial class Type2 : MyModelBase
    {}

    public partial class Type3 : MyType1
    { }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.AreEqual(3, btypes.Count);
            var btype1 = btypes[0];
            Assert.AreEqual("Type1", btype1.ClrName);
            var btype2 = btypes[1];
            Assert.AreEqual("Type2", btype2.ClrName);
            var btype3 = btypes[2];
            Assert.AreEqual("Type3", btype3.ClrName);

            Assert.IsFalse(btype1.HasBase);
            Assert.IsTrue(btype2.HasBase);
            Assert.IsTrue(btype3.HasBase);

            var sb = new StringBuilder();
            builder.Generate(sb, btype3);
            var gen = sb.ToString();
            Console.WriteLine(gen);

            Assert.Greater(gen.IndexOf("public partial class Type3\n"), 0);
            Assert.Greater(0, gen.IndexOf("public partial class Type3 : "));
        }
        public void ContentTypeRenameOnClass()
        {
            // Umbraco returns nice, pascal-cased names

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };

            var type2 = new TypeModel
            {
                Id = 2,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };

            var types = new[] { type1, type2 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
namespace Models
{
    [ImplementContentType(""type1"")]
    public partial class Renamed1
    {}
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.IsFalse(parseResult.IsIgnored("type1"));
            Assert.IsFalse(parseResult.IsIgnored("type2"));
            Assert.IsTrue(parseResult.IsContentRenamed("type1"));
            Assert.IsFalse(parseResult.IsContentRenamed("type2"));
            Assert.AreEqual("Renamed1", parseResult.ContentClrName("type1"));
            Assert.IsNull(parseResult.ContentClrName("type2"));

            Assert.AreEqual(2, btypes.Count);
            Assert.IsFalse(btypes[0].IsContentIgnored);
            Assert.IsFalse(btypes[1].IsContentIgnored);
            Assert.AreEqual("Renamed1", btypes[0].ClrName);
            Assert.AreEqual("Type2", btypes[1].ClrName);
        }
        public void ContentTypeIgnoreMixin()
        {
            // Umbraco returns nice, pascal-cased names

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };

            var type2 = new TypeModel
            {
                Id = 2,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type2.MixinTypes.Add(type1);

            var types = new[] { type1, type2 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
[assembly:IgnoreContentType(""type1"")]
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.IsTrue(parseResult.IsIgnored("type1"));
            Assert.IsFalse(parseResult.IsIgnored("type2"));

            Assert.AreEqual(2, btypes.Count);
            Assert.AreEqual("type1", btypes[0].Alias);
            Assert.AreEqual("type2", btypes[1].Alias);
            Assert.IsTrue(btypes[0].IsContentIgnored);
            Assert.IsFalse(btypes[1].IsContentIgnored);

            Assert.AreEqual(0, btypes[1].DeclaringInterfaces.Count);
        }
        private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
        {
            var staticMixinGetters = Configuration.Config.StaticMixinGetters;

            // write the properties
            foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
                WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);

            // no need to write the parent properties since we inherit from the parent
            // and the parent defines its own properties. need to write the mixins properties
            // since the mixins are only interfaces and we have to provide an implementation.

            // write the mixins properties
            foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
                foreach (var prop in mixinType.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
                    if (staticMixinGetters)
                        WriteMixinProperty(sb, prop, mixinType.ClrName);
                    else
                        WriteProperty(sb, mixinType, prop);
        }
 /// <summary>
 /// Recursively collects all types inherited, or implemented as interfaces, by a specified type.
 /// </summary>
 /// <param name="types">The collection.</param>
 /// <param name="type">The type.</param>
 /// <remarks>Includes the specified type.</remarks>
 internal static void CollectImplems(ICollection<TypeModel> types, TypeModel type)
 {
     if (!type.IsContentIgnored && types.Contains(type) == false)
         types.Add(type);
     if (type.BaseType != null && !type.BaseType.IsContentIgnored)
         CollectImplems(types, type.BaseType);
     foreach (var mixin in type.MixinTypes.Where(x => !x.IsContentIgnored))
         CollectImplems(types, mixin);
 }
        public void PropertyTypeImplementOnInterface()
        {
            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1a",
                ClrName = "Prop1a",
                ClrType = typeof(string),
            });
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1b",
                ClrName = "Prop1b",
                ClrType = typeof(string),
            });
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1c",
                ClrName = "Prop1c",
                ClrType = typeof(string),
            });
            var type2 = new TypeModel
            {
                Id = 1,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type2.Properties.Add(new PropertyModel
            {
                Alias = "prop2",
                ClrName = "Prop2",
                ClrType = typeof(string),
            });
            var types = new[] { type1, type2 };

            type2.MixinTypes.Add(type1);
            type1.IsMixin = true;

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
using Dang;
namespace Dang
{
    // both attributes are ignored on the interface - do it on the class
    //[RenamePropertyType(""prop1b"", ""Prop1x"")]
    //[IgnorePropertyType(""prop1c"")]
    public partial interface IType1
    {
        // attribute is not supported (ie ignored) here
        //[ImplementPropertyType(""prop1a"")]
        // have to do this (see notes in Type1)
        public string Foo { get; }
    }

    // both attributes on the class will be mirrored on the interface
    [RenamePropertyType(""prop1b"", ""Prop1x"")]
    [IgnorePropertyType(""prop1c"")]
    public partial class Type1
    {
        // and then,
        // - property will NOT be implemented in Type2, MUST be done manually
        // - property will NOT be mirrored on the interface, MUST be done manually
        [ImplementPropertyType(""prop1a"")]
        public string Foo { get { return string.Empty; } }
    }

    public partial class Type2
    {
        // have to do this (see notes in Type1)
        [ImplementPropertyType(""prop1a"")]
        public string Foo { get { return string.Empty; } }
    }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.AreEqual(2, btypes.Count);
            var btype1 = btypes[0];
            Assert.AreEqual("Type1", btype1.ClrName);
            var btype2 = btypes[1];
            Assert.AreEqual("Type2", btype2.ClrName);

            var sb = new StringBuilder();
            builder.Generate(sb, btype1);
            var gen = sb.ToString();
            Console.WriteLine(gen);

            // contains
            Assert.Greater(gen.IndexOf("string Prop1x"), 0);
            // does not contain
            Assert.Greater(0, gen.IndexOf("string Prop1a"));
            Assert.Greater(0, gen.IndexOf("string Prop1b"));
            Assert.Greater(0, gen.IndexOf("string Prop1c"));

            sb.Clear();
            builder.Generate(sb, btype2);
            gen = sb.ToString();
            Console.WriteLine(gen);

            // contains
            Assert.Greater(gen.IndexOf("string Prop2"), 0);
            Assert.Greater(gen.IndexOf("string Prop1x"), 0);
            // does not contain
            Assert.Greater(0, gen.IndexOf("string Prop1a"));
            Assert.Greater(0, gen.IndexOf("string Prop1b"));
            Assert.Greater(0, gen.IndexOf("string Prop1c"));
        }
        public void PropertyTypeRenameOnPropertyInherit()
        {
            // Umbraco returns nice, pascal-cased names

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1",
                ClrName = "Prop1",
                ClrType = typeof(string),
            });

            var types = new[] { type1 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
namespace Models
{
    public class Type2
    {
        [ImplementPropertyType(""prop1"")]
        public string Renamed1 { get { return """"; } }
    }

    public partial class Type1 : Type2
    {
    }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.IsTrue(parseResult.IsPropertyIgnored("Type1", "prop1"));

            Assert.AreEqual(1, btypes.Count);
            Assert.IsTrue(btypes[0].Properties[0].IsIgnored);
        }
Exemplo n.º 10
0
        private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
        {
            var mixinStatic = mixinClrName != null;

            sb.Append("\n");

            if (property.Errors != null)
            {
                sb.Append("\t\t/*\n");
                sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n");
                sb.Append("\t\t *\n");
                var first = true;
                foreach (var error in property.Errors)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        sb.Append("\t\t *\n");
                    }
                    foreach (var s in SplitError(error))
                    {
                        sb.Append("\t\t * ");
                        sb.Append(s);
                        sb.Append("\n");
                    }
                }
                sb.Append("\t\t *\n");
                sb.Append("\n");
            }

            // Adds xml summary to each property containing
            // property name and property description
            if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
            {
                sb.Append("\t\t///<summary>\n");

                if (!string.IsNullOrWhiteSpace(property.Description))
                {
                    sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
                }
                else
                {
                    sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
                }

                sb.Append("\t\t///</summary>\n");
            }

            sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);

            if (mixinStatic)
            {
                sb.Append("\t\tpublic ");
                WriteClrType(sb, property.ClrType);
                sb.AppendFormat(" {0}\n\t\t{{\n\t\t\tget {{ return {1}(this); }}\n\t\t}}\n",
                                property.ClrName, MixinStaticGetterName(property.ClrName));
            }
            else
            {
                sb.Append("\t\tpublic ");
                WriteClrType(sb, property.ClrType);
                sb.AppendFormat(" {0}\n\t\t{{\n\t\t\tget {{ return this.GetPropertyValue",
                                property.ClrName);
                if (property.ClrType != typeof(object))
                {
                    sb.Append("<");
                    WriteClrType(sb, property.ClrType);
                    sb.Append(">");
                }
                sb.AppendFormat("(\"{0}\"); }}\n\t\t}}\n",
                                property.Alias);
            }

            if (property.Errors != null)
            {
                sb.Append("\n");
                sb.Append("\t\t *\n");
                sb.Append("\t\t */\n");
            }

            if (!mixinStatic)
            {
                return;
            }

            var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);

            if (type.StaticMixinMethods.Contains(mixinStaticGetterName))
            {
                return;
            }

            sb.Append("\n");

            if (!string.IsNullOrWhiteSpace(property.Name))
            {
                sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
            }

            sb.Append("\t\tpublic static ");
            WriteClrType(sb, property.ClrType);
            sb.AppendFormat(" {0}(I{1} that) {{ return that.GetPropertyValue",
                            mixinStaticGetterName, mixinClrName);
            if (property.ClrType != typeof(object))
            {
                sb.Append("<");
                WriteClrType(sb, property.ClrType);
                sb.Append(">");
            }
            sb.AppendFormat("(\"{0}\"); }}\n",
                            property.Alias);
        }
Exemplo n.º 11
0
        private void WriteContentType(StringBuilder sb, TypeModel type)
        {
            string sep;

            if (type.IsMixin)
            {
                // write the interface declaration
                sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
                if (!string.IsNullOrWhiteSpace(type.Name))
                {
                    sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
                }
                sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
                var implements = type.BaseType == null || type.BaseType.IsContentIgnored
                    ? (type.HasBase ? null : "PublishedContent")
                    : type.BaseType.ClrName;
                if (implements != null)
                {
                    sb.AppendFormat(" : I{0}", implements);
                }

                // write the mixins
                sep = implements == null ? ":" : ",";
                foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
                {
                    sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
                    sep = ",";
                }

                sb.Append("\n\t{\n");

                // write the properties - only the local (non-ignored) ones, we're an interface
                var more = false;
                foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
                {
                    if (more)
                    {
                        sb.Append("\n");
                    }
                    more = true;
                    WriteInterfaceProperty(sb, prop);
                }

                sb.Append("\t}\n\n");
            }

            // write the class declaration
            if (type.IsRenamed)
            {
                sb.AppendFormat("\t// Content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
            }
            if (!string.IsNullOrWhiteSpace(type.Name))
            {
                sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
            }
            // cannot do it now. see note in ImplementContentTypeAttribute
            //if (!type.HasImplement)
            //    sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias);
            sb.AppendFormat("\t[PublishedContentModel(\"{0}\")]\n", type.Alias);
            sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
            var inherits = type.HasBase
                ? null // has its own base already
                : (type.BaseType == null || type.BaseType.IsContentIgnored
                    ? GetModelsBaseClassName()
                    : type.BaseType.ClrName);

            if (inherits != null)
            {
                sb.AppendFormat(" : {0}", inherits);
            }

            sep = inherits == null ? ":" : ",";
            if (type.IsMixin)
            {
                // if it's a mixin it implements its own interface
                sb.AppendFormat("{0} I{1}", sep, type.ClrName);
            }
            else
            {
                // write the mixins, if any, as interfaces
                // only if not a mixin because otherwise the interface already has them already
                foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
                {
                    sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
                    sep = ",";
                }
            }

            // begin class body
            sb.Append("\n\t{\n");

            // write the constants
            // as 'new' since parent has its own - or maybe not - disable warning
            sb.Append("#pragma warning disable 0109 // new is redundant\n");
            sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
                            type.Alias);
            sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
                            type.ItemType);
            sb.Append("#pragma warning restore 0109\n\n");

            // write the ctor
            if (!type.HasCtor)
            {
                sb.AppendFormat("\t\tpublic {0}(IPublishedContent content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n",
                                type.ClrName);
            }

            // write the static methods
            // as 'new' since parent has its own - or maybe not - disable warning
            sb.Append("#pragma warning disable 0109 // new is redundant\n");
            sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n");
            sb.Append("\t\t{\n\t\t\treturn PublishedContentType.Get(ModelItemType, ModelTypeAlias);\n\t\t}\n");
            sb.Append("#pragma warning restore 0109\n\n");
            sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<{0}, TValue>> selector)\n",
                            type.ClrName);
            sb.Append("\t\t{\n\t\t\treturn PublishedContentModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n\t\t}\n");

            // write the properties
            WriteContentTypeProperties(sb, type);

            // close the class declaration
            sb.Append("\t}\n");
        }
        /// <summary>
        /// Outputs a generated model to a code namespace.
        /// </summary>
        /// <param name="ns">The code namespace.</param>
        /// <param name="typeModel">The model to generate.</param>
        public void Generate(CodeNamespace ns, TypeModel typeModel)
        {
            // what about USING?
            // what about references?

            if (typeModel.IsMixin)
            {
                var i = new CodeTypeDeclaration("I" + typeModel.ClrName)
                {
                    IsInterface = true,
                    IsPartial   = true,
                    Attributes  = MemberAttributes.Public
                };
                i.BaseTypes.Add(typeModel.BaseType == null ? "IPublishedContent" : "I" + typeModel.BaseType.ClrName);

                foreach (var mixinType in typeModel.DeclaringInterfaces)
                {
                    i.BaseTypes.Add(mixinType.ClrName);
                }

                i.Comments.Add(new CodeCommentStatement($"Mixin content Type {typeModel.Id} with alias \"{typeModel.Alias}\""));

                foreach (var propertyModel in typeModel.Properties)
                {
                    var p = new CodeMemberProperty
                    {
                        Name       = propertyModel.ClrName,
                        Type       = new CodeTypeReference(propertyModel.ModelClrType),
                        Attributes = MemberAttributes.Public,
                        HasGet     = true,
                        HasSet     = false
                    };
                    i.Members.Add(p);
                }
            }

            var c = new CodeTypeDeclaration(typeModel.ClrName)
            {
                IsClass    = true,
                IsPartial  = true,
                Attributes = MemberAttributes.Public
            };

            c.BaseTypes.Add(typeModel.BaseType == null ? "PublishedContentModel" : typeModel.BaseType.ClrName);

            // if it's a missing it implements its own interface
            if (typeModel.IsMixin)
            {
                c.BaseTypes.Add("I" + typeModel.ClrName);
            }

            // write the mixins, if any, as interfaces
            // only if not a mixin because otherwise the interface already has them
            if (typeModel.IsMixin == false)
            {
                foreach (var mixinType in typeModel.DeclaringInterfaces)
                {
                    c.BaseTypes.Add("I" + mixinType.ClrName);
                }
            }

            foreach (var mixin in typeModel.MixinTypes)
            {
                c.BaseTypes.Add("I" + mixin.ClrName);
            }

            c.Comments.Add(new CodeCommentStatement($"Content Type {typeModel.Id} with alias \"{typeModel.Alias}\""));

            foreach (var propertyModel in typeModel.Properties)
            {
                var p = new CodeMemberProperty
                {
                    Name       = propertyModel.ClrName,
                    Type       = new CodeTypeReference(propertyModel.ModelClrType),
                    Attributes = MemberAttributes.Public,
                    HasGet     = true,
                    HasSet     = false
                };
                p.GetStatements.Add(new CodeMethodReturnStatement( // return
                                        new CodeMethodInvokeExpression(
                                            new CodeMethodReferenceExpression(
                                                new CodeThisReferenceExpression(), // this
                                                "Value",                           // .Value
                                                new[]                              // <T>
                {
                    new CodeTypeReference(propertyModel.ModelClrType)
                }),
                                            new CodeExpression[] // ("alias")
                {
                    new CodePrimitiveExpression(propertyModel.Alias)
                })));
                c.Members.Add(p);
            }
        }
        private static IList<TypeModel> GetTypes(PublishedItemType itemType, IContentTypeBase[] contentTypes)
        {
            var typeModels = new List<TypeModel>();

            // get the types and the properties
            foreach (var contentType in contentTypes)
            {
                var typeModel = new TypeModel
                {
                    Id = contentType.Id,
                    Alias = contentType.Alias,
                    ClrName = contentType.Alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase),
                    ParentId = contentType.ParentId,

                    Name = contentType.Name,
                    Description = contentType.Description
                };

                switch (itemType)
                {
                    case PublishedItemType.Content:
                        typeModel.ItemType = TypeModel.ItemTypes.Content;
                        break;
                    case PublishedItemType.Media:
                        typeModel.ItemType = TypeModel.ItemTypes.Media;
                        break;
                    case PublishedItemType.Member:
                        typeModel.ItemType = TypeModel.ItemTypes.Member;
                        break;
                    default:
                        throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType));
                }

                typeModels.Add(typeModel);

                var publishedContentType = PublishedContentType.Get(itemType, contentType.Alias);

                foreach (var propertyType in contentType.PropertyTypes)
                {
                    var propertyModel = new PropertyModel
                    {
                        Alias = propertyType.Alias,
                        ClrName = propertyType.Alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase),

                        Name = propertyType.Name,
                        Description = propertyType.Description
                    };

                    var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias);
                    propertyModel.ClrType = publishedPropertyType.ClrType;

                    typeModel.Properties.Add(propertyModel);
                }
            }

            // wire the base types
            foreach (var typeModel in typeModels.Where(x => x.ParentId > 0))
            {
                typeModel.BaseType = typeModels.SingleOrDefault(x => x.Id == typeModel.ParentId);
                // Umbraco 7.4 introduces content types containers, so even though ParentId > 0, the parent might
                // not be a content type - here we assume that BaseType being null while ParentId > 0 means that 
                // the parent is a container (and we don't check).
                typeModel.IsParent = typeModel.BaseType != null;
            }

            // discover mixins
            foreach (var contentType in contentTypes)
            {
                var typeModel = typeModels.SingleOrDefault(x => x.Id == contentType.Id);
                if (typeModel == null) throw new Exception("Panic: no type model matching content type.");

                IEnumerable<IContentTypeComposition> compositionTypes;
                var contentTypeAsMedia = contentType as IMediaType;
                var contentTypeAsContent = contentType as IContentType;
                var contentTypeAsMember = contentType as IMemberType;
                if (contentTypeAsMedia != null) compositionTypes = contentTypeAsMedia.ContentTypeComposition;
                else if (contentTypeAsContent != null) compositionTypes = contentTypeAsContent.ContentTypeComposition;
                else if (contentTypeAsMember != null) compositionTypes = contentTypeAsMember.ContentTypeComposition;
                else throw new Exception(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName));

                foreach (var compositionType in compositionTypes)
                {
                    var compositionModel = typeModels.SingleOrDefault(x => x.Id == compositionType.Id);
                    if (compositionModel == null) throw new Exception("Panic: composition type does not exist.");

                    if (compositionType.Id == contentType.ParentId) continue;

                    // add to mixins
                    typeModel.MixinTypes.Add(compositionModel);

                    // mark as mixin - as well as parents
                    compositionModel.IsMixin = true;
                    while ((compositionModel = compositionModel.BaseType) != null)
                        compositionModel.IsMixin = true;
                }
            }

            return typeModels;
        }
        private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
        {
            var mixinStatic = mixinClrName != null;

            sb.Append("\n");
            
            // Adds xml summary to each property containing
            // property name and property description
            if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
            {
                sb.Append("\t\t///<summary>\n");
                
                if (!string.IsNullOrWhiteSpace(property.Description))
                    sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
                else
                    sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));

                sb.Append("\t\t///</summary>\n");
            }

            sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);

            if (mixinStatic)
            {
                sb.Append("\t\tpublic ");
                WriteClrType(sb, property.ClrType);
                sb.AppendFormat(" {0}\n\t\t{{\n\t\t\tget {{ return {1}(this); }}\n\t\t}}\n",
                    property.ClrName, MixinStaticGetterName(property.ClrName));
            }
            else
            {
                sb.Append("\t\tpublic ");
                WriteClrType(sb, property.ClrType);
                sb.AppendFormat(" {0}\n\t\t{{\n\t\t\tget {{ return this.GetPropertyValue",
                    property.ClrName);
                if (property.ClrType != typeof(object))
                {
                    sb.Append("<");
                    WriteClrType(sb, property.ClrType);
                    sb.Append(">");
                }
                sb.AppendFormat("(\"{0}\"); }}\n\t\t}}\n",
                    property.Alias);
            }

            if (!mixinStatic) return;

            var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);

            if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;

            sb.Append("\n");

            if (!string.IsNullOrWhiteSpace(property.Name))
                sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));

            sb.Append("\t\tpublic static ");
            WriteClrType(sb, property.ClrType);
            sb.AppendFormat(" {0}(I{1} that) {{ return that.GetPropertyValue",
                mixinStaticGetterName, mixinClrName);
            if (property.ClrType != typeof(object))
            {
                sb.Append("<");
                WriteClrType(sb, property.ClrType);
                sb.Append(">");
            }
            sb.AppendFormat("(\"{0}\"); }}\n",
                property.Alias);
        }
        public void MixinPropertyStatic()
        {
            Configuration.Config.StaticMixinGetters = true; // try the new way

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
                IsMixin = true,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1a",
                ClrName = "Prop1a",
                ClrType = typeof(string),
            });
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1b",
                ClrName = "Prop1b",
                ClrType = typeof(string),
            });

            var type2 = new TypeModel
            {
                Id = 2,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type2.MixinTypes.Add(type1);
            type2.Properties.Add(new PropertyModel
            {
                Alias = "prop2",
                ClrName = "Prop2",
                ClrType = typeof(int),
            });

            var types = new[] { type1, type2 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
using Test;
namespace Test
{
    public partial class Type1
    {
        public static int GetProp1a(IType1 that)
        {
            return that.GetPropertyValue<int>(""prop1a"");
        }
    }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            var sb = new StringBuilder();
            foreach (var model in builder.GetModelsToGenerate())
                builder.Generate(sb, model);
            var gen = sb.ToString();

            var version = typeof(Builder).Assembly.GetName().Version;
            var expected = @"//------------------------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//
//    Umbraco.ModelsBuilder v" + version + @"
//
//   Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.ModelsBuilder;
using Umbraco.ModelsBuilder.Umbraco;

namespace Umbraco.Web.PublishedContentModels
{
	// Mixin content Type 1 with alias ""type1""
	public partial interface IType1 : IPublishedContent
	{
		string Prop1a { get; }

		string Prop1b { get; }
	}

	[PublishedContentModel(""type1"")]
	public partial class Type1 : PublishedContentModel, IType1
	{
#pragma warning disable 0109 // new is redundant
		public new const string ModelTypeAlias = ""type1"";
		public new const PublishedItemType ModelItemType = PublishedItemType.Content;
#pragma warning restore 0109

		public Type1(IPublishedContent content)
			: base(content)
		{ }

#pragma warning disable 0109 // new is redundant
		public new static PublishedContentType GetModelContentType()
		{
			return PublishedContentType.Get(ModelItemType, ModelTypeAlias);
		}
#pragma warning restore 0109

		public static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<Type1, TValue>> selector)
		{
			return PublishedContentModelUtility.GetModelPropertyType(GetModelContentType(), selector);
		}

		[ImplementPropertyType(""prop1a"")]
		public string Prop1a
		{
			get { return GetProp1a(this); }
		}

		[ImplementPropertyType(""prop1b"")]
		public string Prop1b
		{
			get { return GetProp1b(this); }
		}

		public static string GetProp1b(IType1 that) { return that.GetPropertyValue<string>(""prop1b""); }
	}
}
//------------------------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//
//    Umbraco.ModelsBuilder v" + version + @"
//
//   Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.ModelsBuilder;
using Umbraco.ModelsBuilder.Umbraco;

namespace Umbraco.Web.PublishedContentModels
{
	[PublishedContentModel(""type2"")]
	public partial class Type2 : PublishedContentModel, IType1
	{
#pragma warning disable 0109 // new is redundant
		public new const string ModelTypeAlias = ""type2"";
		public new const PublishedItemType ModelItemType = PublishedItemType.Content;
#pragma warning restore 0109

		public Type2(IPublishedContent content)
			: base(content)
		{ }

#pragma warning disable 0109 // new is redundant
		public new static PublishedContentType GetModelContentType()
		{
			return PublishedContentType.Get(ModelItemType, ModelTypeAlias);
		}
#pragma warning restore 0109

		public static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<Type2, TValue>> selector)
		{
			return PublishedContentModelUtility.GetModelPropertyType(GetModelContentType(), selector);
		}

		[ImplementPropertyType(""prop2"")]
		public int Prop2
		{
			get { return this.GetPropertyValue<int>(""prop2""); }
		}

		[ImplementPropertyType(""prop1a"")]
		public string Prop1a
		{
			get { return Type1.GetProp1a(this); }
		}

		[ImplementPropertyType(""prop1b"")]
		public string Prop1b
		{
			get { return Type1.GetProp1b(this); }
		}
	}
}
";

            //Console.WriteLine(gen);
            Assert.AreEqual(expected.ClearLf(), gen);
        }
        public void PropertyTypeRenameOnClass()
        {
            // Umbraco returns nice, pascal-cased names

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1",
                ClrName = "Prop1",
                ClrType = typeof(string),
            });

            var types = new[] { type1 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
namespace Models
{
    [RenamePropertyType(""prop1"", ""Renamed1"")]
    [RenamePropertyType(""prop2"", ""Renamed2"")]
    public partial class Type1
    {
    }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.AreEqual("Renamed1", parseResult.PropertyClrName("Type1", "prop1"));
            Assert.AreEqual("Renamed2", parseResult.PropertyClrName("Type1", "prop2"));

            Assert.AreEqual(1, btypes.Count);
            Assert.IsTrue(btypes[0].Properties[0].ClrName == "Renamed1");
        }
        public void ReferencedAssemblies()
        {
            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };

            var types = new[] { type1 };

            var code1 = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
public partial class Type1
{}
"}
            };

            Assert.IsFalse(new CodeParser().Parse(code1).HasContentBase("Type1"));

            var code2 = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
public partial class Type1 : IHasXmlNode
{}
"}
            };

            // assumes base is IHasXmlNode (cannot be verified...)
            Assert.IsTrue(new CodeParser().Parse(code2).HasContentBase("Type1"));

            var code3 = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
using System.Xml;
public partial class Type1 : IHasXmlNode
{}
"}
            };

            // figures out that IHasXmlNode is an interface, not base
            // because of using + reference
            var asms = new[] {typeof(System.Xml.IHasXmlNode).Assembly};
            Assert.IsFalse(new CodeParser().Parse(code3, asms).HasContentBase("Type1"));
        }
        public void PropertyTypeImplementOnClass()
        {
            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1",
                ClrName = "Prop1",
                ClrType = typeof(string),
            });
            var types = new[] { type1 };

            var code = new Dictionary<string, string>
            {
                {"assembly", @"
using Umbraco.ModelsBuilder;
using Dang;
namespace Dang
{
    public partial class Type1
    {
        [ImplementPropertyType(""prop1"")]
        public string Foo { get { return string.Empty; } }
    }
}
"}
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            Assert.AreEqual(1, btypes.Count);
            var btype1 = btypes[0];
            Assert.AreEqual("Type1", btype1.ClrName);

            var sb = new StringBuilder();
            builder.Generate(sb, btype1);
            var gen = sb.ToString();
            Console.WriteLine(gen);

            Assert.Greater(0, gen.IndexOf("string Prop1"));
        }
        public void PartialConstructorWithRename()
        {
            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1",
                ClrName = "Prop1",
                ClrType = typeof(string),
            });

            var types = new[] { type1 };

            var code = new Dictionary<string, string>
            {
                { "assembly", @"
using Umbraco.ModelsBuilder;
using Umbraco.Core.Models;

[assembly:RenameContentType(""type1"", ""Type2"")]

public partial class Type2
{
    public Type2(IPublishedContent content)
        : base(content)
    {
        // do our own stuff
    }
}
" }
            };

            var parseResult = new CodeParser().Parse(code);

            Assert.IsFalse(parseResult.HasCtor("Type1"));
            Assert.IsTrue(parseResult.HasCtor("Type2"));

            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            var sb = new StringBuilder();
            builder.Generate(sb, builder.GetModelsToGenerate().First());
            var gen = sb.ToString();

            var version = typeof(Builder).Assembly.GetName().Version;
            var expected = @"//------------------------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//
//    Umbraco.ModelsBuilder v" + version + @"
//
//   Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.ModelsBuilder;
using Umbraco.ModelsBuilder.Umbraco;

namespace Umbraco.Web.PublishedContentModels
{
	// Content Type 1 with alias ""type1""
	[PublishedContentModel(""type1"")]
	public partial class Type2 : PublishedContentModel
	{
#pragma warning disable 0109 // new is redundant
		public new const string ModelTypeAlias = ""type1"";
		public new const PublishedItemType ModelItemType = PublishedItemType.Content;
#pragma warning restore 0109

#pragma warning disable 0109 // new is redundant
		public new static PublishedContentType GetModelContentType()
		{
			return PublishedContentType.Get(ModelItemType, ModelTypeAlias);
		}
#pragma warning restore 0109

		public static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<Type2, TValue>> selector)
		{
			return PublishedContentModelUtility.GetModelPropertyType(GetModelContentType(), selector);
		}

		[ImplementPropertyType(""prop1"")]
		public string Prop1
		{
			get { return this.GetPropertyValue<string>(""prop1""); }
		}
	}
}
";
            Console.WriteLine(gen);
            Assert.AreEqual(expected.ClearLf(), gen);
        }
Exemplo n.º 20
0
        /// <summary>
        /// Prepares generation by processing the result of code parsing.
        /// </summary>
        /// <remarks>
        ///     Preparation includes figuring out from the existing code which models or properties should
        ///     be ignored or renamed, etc. -- anything that comes from the attributes in the existing code.
        /// </remarks>
        private void Prepare()
        {
            // mark IsContentIgnored models that we discovered should be ignored
            // then propagate / ignore children of ignored contents
            // ignore content = don't generate a class for it, don't generate children
            foreach (var typeModel in _typeModels.Where(x => ParseResult.IsIgnored(x.Alias)))
            {
                typeModel.IsContentIgnored = true;
            }
            foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored && x.EnumerateBaseTypes().Any(xx => xx.IsContentIgnored)))
            {
                typeModel.IsContentIgnored = true;
            }

            // handle model renames
            foreach (var typeModel in _typeModels.Where(x => ParseResult.IsContentRenamed(x.Alias)))
            {
                typeModel.ClrName   = ParseResult.ContentClrName(typeModel.Alias);
                typeModel.IsRenamed = true;
            }

            // handle implement
            foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentImplement(x.Alias)))
            {
                typeModel.HasImplement = true;
            }

            // mark OmitBase models that we discovered already have a base class
            foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentBase(ParseResult.ContentClrName(x.Alias) ?? x.ClrName)))
            {
                typeModel.HasBase = true;
            }

            foreach (var typeModel in _typeModels)
            {
                // mark IsRemoved properties that we discovered should be ignored
                // ie is marked as ignored on type, or on any parent type
                var tm = typeModel;
                foreach (var property in typeModel.Properties
                         .Where(property => tm.EnumerateBaseTypes(true).Any(x => ParseResult.IsPropertyIgnored(ParseResult.ContentClrName(x.Alias) ?? x.ClrName, property.Alias))))
                {
                    property.IsIgnored = true;
                }

                // handle property renames
                foreach (var property in typeModel.Properties)
                {
                    property.ClrName = ParseResult.PropertyClrName(ParseResult.ContentClrName(typeModel.Alias) ?? typeModel.ClrName, property.Alias) ?? property.ClrName;
                }
            }

            // ensure we have no duplicates type names
            foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
            {
                throw new InvalidOperationException($"Type name \"{xx.Key}\" is used"
                                                    + $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique."
                                                    + " Consider using an attribute to assign different names to conflicting types.");
            }

            // ensure we have no duplicates property names
            foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
            {
                foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
                {
                    throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\""
                                                        + $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique."
                                                        + " Consider using an attribute to assign different names to conflicting properties.");
                }
            }

            // ensure content & property type don't have identical name (csharp hates it)
            foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
            {
                foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored && x.ClrName == typeModel.ClrName))
                {
                    throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"."
                                                        + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"."
                                                        + " Consider using an attribute to assign a different name to the property.");
                }
            }

            // ensure we have no collision between base types
            // NO: we may want to define a base class in a partial, on a model that has a parent
            // we are NOT checking that the defined base type does maintain the inheritance chain
            //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase))
            //    throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.",
            //        xx.Alias));

            // discover interfaces that need to be declared / implemented
            foreach (var typeModel in _typeModels)
            {
                // collect all the (non-removed) types implemented at parent level
                // ie the parent content types and the mixins content types, recursively
                var parentImplems = new List <TypeModel>();
                if (typeModel.BaseType != null && !typeModel.BaseType.IsContentIgnored)
                {
                    TypeModel.CollectImplems(parentImplems, typeModel.BaseType);
                }

                // interfaces we must declare we implement (initially empty)
                // ie this type's mixins, except those that have been removed,
                // and except those that are already declared at the parent level
                // in other words, DeclaringInterfaces is "local mixins"
                var declaring = typeModel.MixinTypes
                                .Where(x => !x.IsContentIgnored)
                                .Except(parentImplems);
                typeModel.DeclaringInterfaces.AddRange(declaring);

                // interfaces we must actually implement (initially empty)
                // if we declare we implement a mixin interface, we must actually implement
                // its properties, all recursively (ie if the mixin interface implements...)
                // so, starting with local mixins, we collect all the (non-removed) types above them
                var mixinImplems = new List <TypeModel>();
                foreach (var i in typeModel.DeclaringInterfaces)
                {
                    TypeModel.CollectImplems(mixinImplems, i);
                }
                // and then we remove from that list anything that is already declared at the parent level
                typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems));
            }

            // register using types
            foreach (var usingNamespace in ParseResult.UsingNamespaces)
            {
                if (!TypesUsing.Contains(usingNamespace))
                {
                    TypesUsing.Add(usingNamespace);
                }
            }

            // discover static mixin methods
            foreach (var typeModel in _typeModels)
            {
                typeModel.StaticMixinMethods.AddRange(ParseResult.StaticMixinMethods(typeModel.ClrName));
            }

            // handle ctor
            foreach (var typeModel in _typeModels.Where(x => ParseResult.HasCtor(x.ClrName)))
            {
                typeModel.HasCtor = true;
            }
        }
        public void GenerateMixinType()
        {
            // Umbraco returns nice, pascal-cased names

            Configuration.Config.StaticMixinGetters = true; // try the new way

            var type1 = new TypeModel
            {
                Id = 1,
                Alias = "type1",
                ClrName = "Type1",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
                IsMixin =  true,
            };
            type1.Properties.Add(new PropertyModel
            {
                Alias = "prop1",
                ClrName = "Prop1",
                ClrType = typeof(string),
            });

            var type2 = new TypeModel
            {
                Id = 2,
                Alias = "type2",
                ClrName = "Type2",
                ParentId = 0,
                BaseType = null,
                ItemType = TypeModel.ItemTypes.Content,
            };
            type2.MixinTypes.Add(type1);
            type2.Properties.Add(new PropertyModel
            {
                Alias = "prop2",
                ClrName = "Prop2",
                ClrType = typeof(int),
            });

            var types = new[] { type1, type2 };

            var code = new Dictionary<string, string>
            {
            };

            var parseResult = new CodeParser().Parse(code);
            var builder = new TextBuilder(types, parseResult);
            var btypes = builder.TypeModels;

            var sb = new StringBuilder();
            foreach (var model in builder.GetModelsToGenerate())
                builder.Generate(sb, model);
            var gen = sb.ToString();

            var version = typeof(Builder).Assembly.GetName().Version;

            var expected = @"//------------------------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//
//    Umbraco.ModelsBuilder v" + version + @"
//
//   Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
";
            Console.WriteLine(gen);
            return;

            //Assert.AreEqual(expected.ClearLf(), gen);
        }
        /// <summary>
        /// Outputs a generated model to a code namespace.
        /// </summary>
        /// <param name="ns">The code namespace.</param>
        /// <param name="typeModel">The model to generate.</param>
        public void Generate(CodeNamespace ns, TypeModel typeModel)
        {
            // what about USING?
            // what about references?

            if (typeModel.IsMixin)
            {
                var i = new CodeTypeDeclaration("I" + typeModel.ClrName)
                {
                    IsInterface = true,
                    IsPartial = true,
                    Attributes = MemberAttributes.Public
                };
                i.BaseTypes.Add(typeModel.BaseType == null ? "IPublishedContent" : "I" + typeModel.BaseType.ClrName);

                foreach (var mixinType in typeModel.DeclaringInterfaces)
                    i.BaseTypes.Add(mixinType.ClrName);

                i.Comments.Add(new CodeCommentStatement(
                    string.Format("Mixin content Type {0} with alias \"{1}\"", typeModel.Id, typeModel.Alias)));

                foreach (var propertyModel in typeModel.Properties)
                {
                    var p = new CodeMemberProperty();
                    p.Name = propertyModel.ClrName;
                    p.Type = new CodeTypeReference(propertyModel.ClrType);
                    p.Attributes = MemberAttributes.Public;
                    p.HasGet = true;
                    p.HasSet = false;
                    i.Members.Add(p);
                }
            }

            var c = new CodeTypeDeclaration(typeModel.ClrName)
            {
                IsClass = true,
                IsPartial = true,
                Attributes = MemberAttributes.Public
            };

            c.BaseTypes.Add(typeModel.BaseType == null ? "PublishedContentModel" : typeModel.BaseType.ClrName);

            // if it's a missing it implements its own interface
            if (typeModel.IsMixin)
                c.BaseTypes.Add("I" + typeModel.ClrName);

            // write the mixins, if any, as interfaces
            // only if not a mixin because otherwise the interface already has them
            if (typeModel.IsMixin == false)
                foreach (var mixinType in typeModel.DeclaringInterfaces)
                    c.BaseTypes.Add("I" + mixinType.ClrName);

            foreach (var mixin in typeModel.MixinTypes)
                c.BaseTypes.Add("I" + mixin.ClrName);

            c.Comments.Add(new CodeCommentStatement(
                string.Format("Content Type {0} with alias \"{1}\"", typeModel.Id, typeModel.Alias)));

            foreach (var propertyModel in typeModel.Properties)
            {
                var p = new CodeMemberProperty();
                p.Name = propertyModel.ClrName;
                p.Type = new CodeTypeReference(propertyModel.ClrType);
                p.Attributes = MemberAttributes.Public;
                p.HasGet = true;
                p.HasSet = false;
                p.GetStatements.Add(new CodeMethodReturnStatement( // return
                    new CodeMethodInvokeExpression(
                        new CodeMethodReferenceExpression(
                            new CodeThisReferenceExpression(), // this
                            "GetPropertyValue", // .GetPropertyValue
                            new[] // <T>
                            {
                                new CodeTypeReference(propertyModel.ClrType)
                            }),
                            new CodeExpression[] // ("alias")
                            {
                                new CodePrimitiveExpression(propertyModel.Alias)
                            })));
                c.Members.Add(p);
            }
        }
        private void WriteContentType(StringBuilder sb, TypeModel type)
        {
            string sep;

            if (type.IsMixin)
            {
                // write the interface declaration
                sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
                if (!string.IsNullOrWhiteSpace(type.Name))
                    sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
                sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
                var implements = type.BaseType == null || type.BaseType.IsContentIgnored
                    ? (type.HasBase ? null : "PublishedContent") 
                    : type.BaseType.ClrName;
                if (implements != null)
                    sb.AppendFormat(" : I{0}", implements);

                // write the mixins
                sep = implements == null ? ":" : ",";
                foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
                {
                    sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
                    sep = ",";
                }

                sb.Append("\n\t{\n");

                // write the properties - only the local (non-ignored) ones, we're an interface
                var more = false;
                foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
                {
                    if (more) sb.Append("\n");
                    more = true;
                    WriteInterfaceProperty(sb, prop);
                }

                sb.Append("\t}\n\n");
            }

            // write the class declaration
            if (type.IsRenamed)
                sb.AppendFormat("\t// Content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
            if (!string.IsNullOrWhiteSpace(type.Name))
                sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
            // cannot do it now. see note in ImplementContentTypeAttribute
            //if (!type.HasImplement)
            //    sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias);
            sb.AppendFormat("\t[PublishedContentModel(\"{0}\")]\n", type.Alias);
            sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
            var inherits = type.HasBase 
                ? null // has its own base already
                : (type.BaseType == null || type.BaseType.IsContentIgnored
                    ? GetModelsBaseClassName()
                    : type.BaseType.ClrName);
            if (inherits != null)
                sb.AppendFormat(" : {0}", inherits);

            sep = inherits == null ? ":" : ",";
            if (type.IsMixin)
            {
                // if it's a mixin it implements its own interface
                sb.AppendFormat("{0} I{1}", sep, type.ClrName);
            }
            else
            {
                // write the mixins, if any, as interfaces
                // only if not a mixin because otherwise the interface already has them already
                foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
                {
                    sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
                    sep = ",";
                }
            }

            // begin class body
            sb.Append("\n\t{\n");

            // write the constants
            // as 'new' since parent has its own - or maybe not - disable warning
            sb.Append("#pragma warning disable 0109 // new is redundant\n");
            sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
                type.Alias);
            sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
                type.ItemType);
            sb.Append("#pragma warning restore 0109\n\n");

            // write the ctor
            if (!type.HasCtor)
                sb.AppendFormat("\t\tpublic {0}(IPublishedContent content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n",
                    type.ClrName);

            // write the static methods
            // as 'new' since parent has its own - or maybe not - disable warning
            sb.Append("#pragma warning disable 0109 // new is redundant\n");
            sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n");
            sb.Append("\t\t{\n\t\t\treturn PublishedContentType.Get(ModelItemType, ModelTypeAlias);\n\t\t}\n");
            sb.Append("#pragma warning restore 0109\n\n");
            sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<{0}, TValue>> selector)\n",
                type.ClrName);
            sb.Append("\t\t{\n\t\t\treturn PublishedContentModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n\t\t}\n");

            // write the properties
            WriteContentTypeProperties(sb, type);

            // close the class declaration
            sb.Append("\t}\n");
        }