Example #1
0
        /// <summary>
        /// Prepares generation by processing the result of code parsing.
        /// </summary>
        private void Prepare()
        {
            TypeModel.MapModelTypes(TypeModels, ModelsNamespace);

            var isInMemoryMode = Config.ModelsMode == ModelsMode.InMemoryAuto;

            // for the first two of these two tests,
            //  always throw, even in InMemory mode: cannot happen unless ppl start fidling with attributes to rename
            //  things, and then they should pay attention to the generation error log - there's no magic here
            // for the last one, don't throw in InMemory mode, see comment

            // ensure we have no duplicates type names
            foreach (var xx in TypeModels.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)
            {
                foreach (var xx in typeModel.Properties.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)
            {
                foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName))
                {
                    if (!isInMemoryMode)
                    {
                        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.");
                    }

                    // in InMemory mode we generate commented out properties with an error message,
                    // instead of throwing, because then it kills the sites and ppl don't understand why
                    xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because"
                                + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"."
                                + " Consider using an attribute to assign a different name to the property.");

                    // will not be implemented on interface nor class
                    // note: we will still create the static getter, and implement the property on other classes...
                }
            }

            // 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.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
                                .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));
            }

            // ensure elements don't inherit from non-elements
            foreach (var typeModel in TypeModels.Where(x => x.IsElement))
            {
                if (typeModel.BaseType != null && !typeModel.BaseType.IsElement)
                {
                    throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not.");
                }

                var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList();
                if (errs.Count > 0)
                {
                    throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not.");
                }
            }
        }
        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");
            }

            WriteGeneratedCodeAttribute(sb, "\t\t");
            if (!property.ModelClrType.IsValueType)
            {
                WriteMaybeNullAttribute(sb, "\t\t");
            }
            sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);

            if (mixinStatic)
            {
                sb.Append("\t\tpublic virtual ");
                WriteClrType(sb, property.ClrTypeName);
                sb.AppendFormat(" {0} => {1}(this, _publishedValueFallback);\n",
                                property.ClrName, MixinStaticGetterName(property.ClrName));
            }
            else
            {
                sb.Append("\t\tpublic virtual ");
                WriteClrType(sb, property.ClrTypeName);
                sb.AppendFormat(" {0} => this.Value",
                                property.ClrName);
                if (property.ModelClrType != typeof(object))
                {
                    sb.Append("<");
                    WriteClrType(sb, property.ClrTypeName);
                    sb.Append(">");
                }
                sb.AppendFormat("(_publishedValueFallback, \"{0}\");\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));
            }

            WriteGeneratedCodeAttribute(sb, "\t\t");
            if (!property.ModelClrType.IsValueType)
            {
                WriteMaybeNullAttribute(sb, "\t\t", true);
            }
            sb.Append("\t\tpublic static ");
            WriteClrType(sb, property.ClrTypeName);
            sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value",
                            mixinStaticGetterName, mixinClrName);
            if (property.ModelClrType != typeof(object))
            {
                sb.Append("<");
                WriteClrType(sb, property.ClrTypeName);
                sb.Append(">");
            }
            sb.AppendFormat("(publishedValueFallback, \"{0}\");\n",
                            property.Alias);
        }
Example #3
0
 protected string GetModelsBaseClassName(TypeModel type)
 {
     // default
     return(type.IsElement ? "PublishedElementModel" : "PublishedContentModel");
 }
        private void WriteContentType(StringBuilder sb, TypeModel type)
        {
            string sep;

            if (type.IsMixin)
            {
                // write the interface declaration
                sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", 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.HasBase ? null : (type.IsElement ? "PublishedElement" : "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.OrderBy(x => x.ClrName))
                {
                    if (more)
                    {
                        sb.Append("\n");
                    }
                    more = true;
                    WriteInterfaceProperty(sb, prop);
                }

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

            // write the class declaration
            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[PublishedModel(\"{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
                    ? GetModelsBaseClassName(type)
                    : 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 & static methods
            // as 'new' since parent has its own - or maybe not - disable warning
            sb.Append("\t\t// helpers\n");
            sb.Append("#pragma warning disable 0109 // new is redundant\n");
            WriteGeneratedCodeAttribute(sb, "\t\t");
            sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
                            type.Alias);
            var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme

            WriteGeneratedCodeAttribute(sb, "\t\t");
            sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
                            itemType);
            WriteGeneratedCodeAttribute(sb, "\t\t");
            WriteMaybeNullAttribute(sb, "\t\t", true);
            sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)\n");
            sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);\n");
            WriteGeneratedCodeAttribute(sb, "\t\t");
            WriteMaybeNullAttribute(sb, "\t\t", true);
            sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<{0}, TValue>> selector)\n",
                            type.ClrName);
            sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);\n");
            sb.Append("#pragma warning restore 0109\n\n");
            sb.Append("\t\tprivate IPublishedValueFallback _publishedValueFallback;");

            // write the ctor
            sb.AppendFormat("\n\n\t\t// ctor\n\t\tpublic {0}(IPublished{1} content, IPublishedValueFallback publishedValueFallback)\n\t\t\t: base(content, publishedValueFallback)\n\t\t{{\n\t\t\t_publishedValueFallback = publishedValueFallback;\n\t\t}}\n\n",
                            type.ClrName, type.IsElement ? "Element" : "Content");

            // write the properties
            sb.Append("\t\t// properties\n");
            WriteContentTypeProperties(sb, type);

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