private ModelClass ProcessClass([NotNull] ClassDeclarationSyntax classDecl, NamespaceDeclarationSyntax namespaceDecl = null) { ModelClass result; if (classDecl == null) { throw new ArgumentNullException(nameof(classDecl)); } ModelRoot modelRoot = Store.ModelRoot(); string className = classDecl.Identifier.Text.Split(':').LastOrDefault(); if (!ValidateInput()) { return(null); } Transaction tx = Store.TransactionManager.CurrentTransaction == null ? Store.TransactionManager.BeginTransaction() : null; List <string> customInterfaces = new List <string>(); try { result = Store.Get <ModelClass>().FirstOrDefault(c => c.Name == className); if (result == null) { result = new ModelClass(Store , new PropertyAssignment(ModelClass.NameDomainPropertyId, className) , new PropertyAssignment(ModelClass.NamespaceDomainPropertyId, namespaceDecl?.Name?.ToString() ?? modelRoot.Namespace) , new PropertyAssignment(ModelClass.IsAbstractDomainPropertyId, classDecl.DescendantNodes().Any(n => n.Kind() == SyntaxKind.AbstractKeyword))); modelRoot.Classes.Add(result); } ModelClass superClass = FindSuperClass(); if (superClass != null) { result.Superclass = superClass; } if (result.CustomInterfaces != null) { customInterfaces.AddRange(result.CustomInterfaces .Split(',') .Where(i => !string.IsNullOrEmpty(i)) .Select(i => i.Trim())); } if (customInterfaces.Contains("INotifyPropertyChanged")) { result.ImplementNotify = true; customInterfaces.Remove("INotifyPropertyChanged"); } if (result.Superclass != null && customInterfaces.Contains(result.Superclass.Name)) { customInterfaces.Remove(result.Superclass.Name); } result.CustomInterfaces = customInterfaces.Any() ? string.Join(",", customInterfaces.Distinct()) : null; AttributeSyntax tableAttribute = classDecl.GetAttribute("Table"); if (tableAttribute != null) { result.TableName = tableAttribute.GetAttributeArguments().First().Expression.ToString().Trim('"'); string schemaName = tableAttribute.GetNamedArgumentValue("Schema"); if (schemaName != null) { result.DatabaseSchema = schemaName; } } XMLDocumentation xmlDocumentation = new XMLDocumentation(classDecl); result.Summary = xmlDocumentation.Summary; result.Description = xmlDocumentation.Description; tx?.Commit(); } catch { tx?.Rollback(); throw; } return(result); ModelClass FindSuperClass() { ModelClass superClass = null; // Base classes and interfaces // Check these first. If we need to add new models, we want the base class already in the store IEnumerable <BaseTypeSyntax> baseTypes = (classDecl.BaseList?.Types ?? Enumerable.Empty <BaseTypeSyntax>()); foreach (string baseName in baseTypes.Select(type => type.ToString().Split(':').Last())) { // Do we know this is an interface? if (KnownInterfaces.Contains(baseName) || superClass != null || result.Superclass != null) { customInterfaces.Add(baseName); if (!KnownInterfaces.Contains(baseName)) { KnownInterfaces.Add(baseName); } continue; } // is it inheritance or an interface? superClass = modelRoot.Classes.FirstOrDefault(c => c.Name == baseName); // if it's not in the model, we just don't know. Ask the user if (superClass == null && (KnownClasses.Contains(baseName) || QuestionDisplay.Show($"For class {className}, is {baseName} the base class?") == true)) { string[] nameparts = baseName.Split('.'); superClass = nameparts.Length == 1 ? new ModelClass(Store, new PropertyAssignment(ModelClass.NameDomainPropertyId, nameparts.Last())) : new ModelClass(Store , new PropertyAssignment(ModelClass.NameDomainPropertyId, nameparts.Last()) , new PropertyAssignment(ModelClass.NamespaceDomainPropertyId, string.Join(".", nameparts.Take(nameparts.Length - 1)))); modelRoot.Classes.Add(superClass); } else { customInterfaces.Add(baseName); KnownInterfaces.Add(baseName); } } return(superClass); } bool ValidateInput() { if (className == null) { ErrorDisplay.Show("Can't find class name"); return(false); } if (namespaceDecl == null && classDecl.Parent is NamespaceDeclarationSyntax classDeclParent) { namespaceDecl = classDeclParent; } if (Store.Get <ModelEnum>().Any(c => c.Name == className)) { ErrorDisplay.Show($"'{className}' already exists in model as an Enum."); return(false); } if (classDecl.TypeParameterList != null) { ErrorDisplay.Show($"Can't add generic class '{className}'."); return(false); } return(true); } }
private void ProcessProperties([NotNull] ClassDeclarationSyntax classDecl) { if (classDecl == null) { throw new ArgumentNullException(nameof(classDecl)); } Transaction tx = Store.TransactionManager.CurrentTransaction == null ? Store.TransactionManager.BeginTransaction() : null; try { string className = classDecl.Identifier.Text; ModelRoot modelRoot = Store.ModelRoot(); ModelClass modelClass = Store.Get <ModelClass>().FirstOrDefault(c => c.Name == className); modelClass.Attributes.Clear(); foreach (PropertyDeclarationSyntax propertyDecl in classDecl.DescendantNodes().OfType <PropertyDeclarationSyntax>()) { // if the property has a fat arrow expression as its direct descendent, it's a readonly calculated property // TODO: we should handle this // but for this release, ignore it if (propertyDecl.ChildNodes().OfType <ArrowExpressionClauseSyntax>().Any()) { continue; } AccessorDeclarationSyntax getAccessor = (AccessorDeclarationSyntax)propertyDecl.DescendantNodes().FirstOrDefault(node => node.IsKind(SyntaxKind.GetAccessorDeclaration)); AccessorDeclarationSyntax setAccessor = (AccessorDeclarationSyntax)propertyDecl.DescendantNodes().FirstOrDefault(node => node.IsKind(SyntaxKind.SetAccessorDeclaration)); // if there's no getAccessor, why are we bothering? if (getAccessor == null) { continue; } string propertyName = propertyDecl.Identifier.ToString(); string propertyType = propertyDecl.Type.ToString(); ModelClass target = modelRoot.Classes.FirstOrDefault(t => t.Name == propertyType); // is the property type a generic? // assume it's a list // TODO: this really isn't a good assumption. Fix later if (propertyDecl.ChildNodes().OfType <GenericNameSyntax>().Any()) { ProcessAsList(propertyDecl, className, propertyName, propertyType, modelRoot, modelClass); continue; } // is the property type an existing ModelClass? if (target != null) { ProcessAssociation(modelClass, target, propertyDecl); continue; } bool propertyShowsNullable = propertyDecl.DescendantNodes().OfType <NullableTypeSyntax>().Any(); // is the property type something we don't know about? if (!modelRoot.IsValidCLRType(propertyType) && ProcessUnknownType(propertyType, propertyShowsNullable, modelRoot, modelClass, propertyDecl)) { continue; } // if we're here, it's just a property (CLR or enum) try { // ReSharper disable once UseObjectOrCollectionInitializer ModelAttribute modelAttribute = new ModelAttribute(Store, new PropertyAssignment(ModelAttribute.NameDomainPropertyId, propertyName)) { Type = ModelAttribute.ToCLRType(propertyDecl.Type.ToString()).Trim('?'), Required = propertyDecl.HasAttribute("RequiredAttribute") || !propertyShowsNullable, Indexed = propertyDecl.HasAttribute("IndexedAttribute"), IsIdentity = propertyDecl.HasAttribute("KeyAttribute"), Virtual = propertyDecl.DescendantTokens().Any(t => t.IsKind(SyntaxKind.VirtualKeyword)) }; if (modelAttribute.Type.ToLower() == "string") { AttributeSyntax maxLengthAttribute = propertyDecl.GetAttribute("MaxLengthAttribute") ?? propertyDecl.GetAttribute("StringLengthAttribute"); AttributeArgumentSyntax maxLength = maxLengthAttribute?.GetAttributeArguments()?.FirstOrDefault(); if (maxLength != null) { modelAttribute.MaxLength = int.TryParse(maxLength.Expression.ToString(), out int _max) ? (int?)_max : null; } AttributeSyntax minLengthAttribute = propertyDecl.GetAttribute("MinLengthAttribute"); AttributeArgumentSyntax minLength = minLengthAttribute?.GetAttributeArguments()?.FirstOrDefault(); if (minLength != null) { modelAttribute.MinLength = int.TryParse(minLength.Expression.ToString(), out int _min) ? _min : 0; } } else { modelAttribute.MaxLength = null; modelAttribute.MinLength = 0; } // if no setAccessor, it's a calculated readonly property if (setAccessor == null) { modelAttribute.Persistent = false; modelAttribute.ReadOnly = true; } modelAttribute.AutoProperty = !getAccessor.DescendantNodes().Any(node => node.IsKind(SyntaxKind.Block)) && !setAccessor.DescendantNodes().Any(node => node.IsKind(SyntaxKind.Block)); modelAttribute.SetterVisibility = setAccessor.Modifiers.Any(m => m.ToString() == "protected") ? SetterAccessModifier.Protected : setAccessor.Modifiers.Any(m => m.ToString() == "internal") ? SetterAccessModifier.Internal : SetterAccessModifier.Public; AttributeSyntax columnAttribute = propertyDecl.GetAttribute("Column"); if (columnAttribute != null) { modelAttribute.ColumnName = columnAttribute.GetAttributeArguments().First().Expression.ToString().Trim('"'); string columnType = columnAttribute.GetNamedArgumentValue("TypeName"); if (columnType != null) { modelAttribute.ColumnType = columnType; } } XMLDocumentation xmlDocumentation = new XMLDocumentation(propertyDecl); modelAttribute.Summary = xmlDocumentation.Summary; modelAttribute.Description = xmlDocumentation.Description; modelClass.Attributes.Add(modelAttribute); } catch { WarningDisplay.Show($"Could not parse '{className}.{propertyDecl.Identifier}'."); } } } catch { tx = null; throw; } finally { tx?.Commit(); } void ProcessAsList(PropertyDeclarationSyntax propertyDecl, string className, string propertyName, string propertyType, ModelRoot modelRoot, ModelClass modelClass) { GenericNameSyntax genericDecl = propertyDecl.ChildNodes().OfType <GenericNameSyntax>().FirstOrDefault(); List <string> contentTypes = genericDecl.DescendantNodes().OfType <IdentifierNameSyntax>().Select(i => i.Identifier.ToString()).ToList(); // there can only be one generic argument if (contentTypes.Count == 1) { propertyType = contentTypes[0]; ModelClass target = modelRoot.Classes.FirstOrDefault(t => t.Name == propertyType); if (target == null) { target = new ModelClass(Store, new PropertyAssignment(ModelClass.NameDomainPropertyId, propertyType)); modelRoot.Classes.Add(target); } ProcessAssociation(modelClass, target, propertyDecl, true); } else { WarningDisplay.Show($"Found {className}.{propertyName}, but its type ({genericDecl.Identifier}<{string.Join(", ", contentTypes)}>) isn't anything expected. Ignoring..."); } } bool ProcessUnknownType(string propertyType, bool propertyShowsNullable, ModelRoot modelRoot, ModelClass modelClass, PropertyDeclarationSyntax propertyDecl) { // might be an enum. If so, we'll handle it like a CLR type // if it's nullable, it's definitely an enum, but if we don't know about it, it could be an enum or a class if (!KnownEnums.Contains(propertyType) && !propertyShowsNullable) { // assume it's a class and create the class ModelClass target = new ModelClass(Store, new PropertyAssignment(ModelClass.NameDomainPropertyId, propertyType)); modelRoot.Classes.Add(target); ProcessAssociation(modelClass, target, propertyDecl); return(true); } return(false); } }