private (List <string> strings, int count) GetSubPropertyOutput(PropertyDetails property, SemanticModel semModel)
        {
            var result = new List <string>();

            var subProperties = this.GetAllPublicProperties(property.Symbol, semModel);

            var numericSubstitute = 0;

            if (subProperties.Any())
            {
                Logger?.RecordInfo(StringRes.Info_SubpropertyCount.WithParams(property.Name, subProperties.Count));

                foreach (var subprop in subProperties)
                {
                    Logger?.RecordInfo(StringRes.Info_GettingSubPropertyOutput.WithParams(subprop.Name));

                    var(output, counter) = this.GetSubPropertyOutputAndCounter(subprop.Name, numericSubstitute: numericSubstitute);

                    numericSubstitute = counter;
                    result.Add(output);
                }
            }
            else
            {
                Logger?.RecordInfo(StringRes.Info_PropertyTypeHasNoSubProperties.WithParams(property.Name, property.PropertyType));

                // There are no subproperties so leave blank
                var(output, counter) = this.GetSubPropertyOutputAndCounter(string.Empty, numericSubstitute: numericSubstitute);

                numericSubstitute = counter;
                result.Add(output);
            }

            return(result, numericSubstitute);
        }
        private ITypeSymbol GetTypeSymbol(SemanticModel semModel, PropertyDeclarationSyntax prop, PropertyDetails propDetails)
        {
            ITypeSymbol typeSymbol = null;

            ITypeSymbol GetWithFallback(TypeSyntax ts, SemanticModel sm, SyntaxTree tree)
            {
                ITypeSymbol result;

                try
                {
                    result = sm.GetTypeInfo(ts).Type;
                }
                catch (Exception)
                {
                    // By default, the semanticmodel passed into this method is the one for the active document.
                    // If the type is in another file, generate a new model to use to look up the typeinfo. Don't do this by default as it's expensive.
                    var localSemModel = CSharpCompilation.Create(string.Empty).AddSyntaxTrees(tree).GetSemanticModel(tree, ignoreAccessibility: true);

                    result = localSemModel.GetTypeInfo(ts).Type;
                }

                return(result);
            }

            if (propDetails.PropertyType.IsGenericTypeName())
            {
                Logger?.RecordInfo(StringRes.Info_GettingGenericType);

                if (prop.Type is GenericNameSyntax gns)
                {
                    var t = gns.TypeArgumentList.Arguments.First();

                    typeSymbol = GetWithFallback(t, semModel, prop.SyntaxTree);
                }
                else if (prop.Type is QualifiedNameSyntax qns)
                {
                    var t = ((GenericNameSyntax)qns.Right).TypeArgumentList.Arguments.First();

                    typeSymbol = GetWithFallback(t, semModel, prop.SyntaxTree);
                }
                else
                {
                    Logger?.RecordInfo(StringRes.Info_PropertyTypeNotRecognizedAsGeneric.WithParams(propDetails.PropertyType));
                }
            }

            if (typeSymbol == null)
            {
                typeSymbol = GetWithFallback(prop.Type, semModel, prop.SyntaxTree);
            }

            if (typeSymbol == null)
            {
                Logger?.RecordInfo(StringRes.Info_PropertyCannotBeAnalyzed.WithParams(prop.ToString()));
            }

            return(typeSymbol);
        }
        private (string output, string name, int counter) GetOutputToAdd(SemanticModel semModel, PropertyDetails prop, int numericCounter = 0)
        {
            var(output, counter) = this.GetPropertyOutputAndCounter(prop, numericCounter, semModel, () => this.GetSubPropertyOutput(prop, semModel));

            return(output, prop.Name, counter);
        }
        private PropertyDetails GetPropertyDetails(PropertyDeclarationSyntax propertyDeclaration, SemanticModel semModel)
        {
            var propertyType = Unknown;

            switch (propertyDeclaration.Type)
            {
            case GenericNameSyntax gns:
                propertyType = gns.ToString();     // Lazy way to get generic types
                break;

            case PredefinedTypeSyntax pds:
                propertyType = pds.Keyword.ValueText;
                break;

            case IdentifierNameSyntax ins:
                propertyType = ins.Identifier.ValueText;
                break;

            case QualifiedNameSyntax qns:
                propertyType = qns.Right.Identifier.ValueText;

                if (qns.Right is GenericNameSyntax qgns)
                {
                    propertyType += qgns.TypeArgumentList.ToString();
                }

                break;

            case NullableTypeSyntax nts:
                propertyType = ((PredefinedTypeSyntax)nts.ElementType).Keyword.Text;

                if (!propertyType.ToLowerInvariant().Contains("nullable"))
                {
                    propertyType += nts.QuestionToken.Text;
                }

                break;

            case ArrayTypeSyntax ats:
                propertyType = ats.ToString();
                break;
            }

            var propertyName = this.GetIdentifier(propertyDeclaration);

            bool?propIsReadOnly;
            var  setter = propertyDeclaration?.AccessorList?.Accessors
                          .FirstOrDefault(a => a.RawKind == (ushort)SyntaxKind.SetAccessorDeclaration);

            if (setter == null)
            {
                propIsReadOnly = true;
            }
            else
            {
                var setterModifiers = setter.Modifiers;
                propIsReadOnly = setterModifiers.Any(m => m.Kind() == SyntaxKind.PrivateKeyword);
            }

            var pd = new PropertyDetails
            {
                Name         = propertyName,
                PropertyType = propertyType,
                IsReadOnly   = propIsReadOnly ?? false,
            };

            foreach (var attribList in propertyDeclaration.AttributeLists)
            {
                foreach (var attrib in attribList.Attributes)
                {
                    var att = new AttributeDetails
                    {
                        Name = attrib.Name.ToString(),
                    };

                    var count = 1;
                    foreach (var arg in attrib.ArgumentList.Arguments)
                    {
                        string name  = null;
                        string value = null;

                        if (arg?.NameColon != null)
                        {
                            name  = arg.NameColon.Name.ToString();
                            value = ((arg.NameColon.Parent as AttributeArgumentSyntax).Expression as IdentifierNameSyntax).Identifier.Value.ToString();
                        }
                        else if (arg?.NameEquals != null)
                        {
                            name  = arg.NameEquals.Name.ToString();
                            value = ((arg.NameEquals.Parent as AttributeArgumentSyntax).Expression as IdentifierNameSyntax).Identifier.Value.ToString();
                        }
                        else
                        {
                            value = arg.ToString();
                        }

                        att.Arguments.Add(new AttributeArgumentDetails
                        {
                            Index = count++,
                            Name  = name,
                            Value = value,
                        });
                    }

                    pd.Attributes.Add(att);
                }
            }

            Logger?.RecordInfo(StringRes.Info_IdentifiedPropertySummary.WithParams(pd.Name, pd.PropertyType, pd.IsReadOnly));

            ITypeSymbol typeSymbol = this.GetTypeSymbol(semModel, propertyDeclaration, pd);

            pd.Symbol = typeSymbol;

            return(pd);
        }
        private ITypeSymbol GetTypeSymbol(SemanticModel semModel, BasePropertyDeclarationSyntax prop, PropertyDetails propDetails)
        {
            ITypeSymbol typeSymbol = null;

            if (propDetails.PropertyType.IsGenericTypeName())
            {
                Logger?.RecordInfo(StringRes.Info_GettingGenericType);

                if (prop.Type is GenericNameSyntax gns)
                {
                    typeSymbol = this.GetTypeSymbolWithFallback(gns, semModel, prop.SyntaxTree);
                }
                else if (prop.Type is QualifiedNameSyntax qns)
                {
                    var t = ((GenericNameSyntax)qns.Right).TypeArgumentList.Arguments.First();

                    typeSymbol = this.GetTypeSymbolWithFallback(t, semModel, prop.SyntaxTree);
                }
                else
                {
                    Logger?.RecordInfo(StringRes.Info_PropertyTypeNotRecognizedAsGeneric.WithParams(propDetails.PropertyType));
                }
            }

            if (typeSymbol == null)
            {
                typeSymbol = this.GetTypeSymbolWithFallback(prop.Type, semModel, prop.SyntaxTree);
            }

            if (typeSymbol == null)
            {
                Logger?.RecordInfo(StringRes.Info_PropertyCannotBeAnalyzed.WithParams(prop.ToString()));
            }

            return(typeSymbol);
        }
        protected override PropertyDetails GetPropertyDetails(SyntaxNode propertyDeclaration, SemanticModel semModel)
        {
            var    propertyType = Unknown;
            string propertyName = null;
            AccessorDeclarationSyntax        setter = null;
            SyntaxList <AttributeListSyntax> attributeList;

            if (propertyDeclaration is PropertyDeclarationSyntax propDecSyntax)
            {
                switch (propDecSyntax.Type)
                {
                case GenericNameSyntax gns:
                    propertyType = gns.ToString();     // Lazy way to get generic types
                    break;

                case PredefinedTypeSyntax pds:
                    propertyType = pds.Keyword.ValueText;
                    break;

                case IdentifierNameSyntax ins:
                    propertyType = ins.Identifier.ValueText;
                    break;

                case QualifiedNameSyntax qns:
                    propertyType = qns.ToString();
                    break;

                case NullableTypeSyntax nts:

                    switch (nts.ElementType)
                    {
                    case PredefinedTypeSyntax npts:
                        propertyType = npts.Keyword.Text;
                        break;

                    case IdentifierNameSyntax nins:
                        propertyType = nins.Identifier.ValueText;
                        break;

                    case QualifiedNameSyntax nqns:
                        propertyType = nqns.ToString();

                        if (nqns.Right is GenericNameSyntax qngns)
                        {
                            propertyType += qngns.TypeArgumentList.ToString();
                        }

                        break;
                    }

                    if (!propertyType.ToLowerInvariant().Contains("nullable"))
                    {
                        propertyType += nts.QuestionToken.Text;
                    }

                    break;

                case ArrayTypeSyntax ats:
                    propertyType = ats.ToString();
                    break;

                case TupleTypeSyntax tts:
                    propertyType = "Tuple";
                    break;
                }

                propertyName = this.GetIdentifier(propertyDeclaration);

                setter = propDecSyntax?.AccessorList?.Accessors.FirstOrDefault(a => a.RawKind == (ushort)SyntaxKind.SetAccessorDeclaration);

                attributeList = propDecSyntax.AttributeLists;
            }
            else
            {
                Logger?.RecordInfo(StringRes.Info_UnexpectedPropertyType.WithParams(propertyDeclaration.GetType()));
            }

            bool?propIsReadOnly;

            if (setter == null)
            {
                propIsReadOnly = true;
            }
            else
            {
                var setterModifiers = setter.Modifiers;
                propIsReadOnly = setterModifiers.Any(m => m.Kind() == SyntaxKind.PrivateKeyword);
            }

            var pd = new PropertyDetails
            {
                Name         = propertyName,
                PropertyType = propertyType,
                IsReadOnly   = propIsReadOnly ?? false,
            };

            pd.Attributes.AddRange(this.GetAttributeDetails(attributeList));

            Logger?.RecordInfo(StringRes.Info_IdentifiedPropertySummary.WithParams(pd.Name, pd.PropertyType, pd.IsReadOnly));

            ITypeSymbol typeSymbol = this.GetTypeSymbol(semModel, propertyDeclaration as BasePropertyDeclarationSyntax, pd);

            pd.Symbol = typeSymbol;

            return(pd);
        }