/// <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() { var pureLive = UmbracoConfig.For.ModelsBuilder().ModelsMode == ModelsMode.PureLive; // 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; } } // for the first two of these two tests, // always throw, even in purelive: 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 purelive, see comment // 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)) { if (!pureLive) { 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."); } // for purelive, will we generate a 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.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; } }