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

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

            var numericSubstitute = 0;

            if (subProperties.Any())
            {
                Logger?.RecordInfo($"Property {property.Name} has {subProperties.Count} sub-properties.");

                foreach (var subprop in subProperties)
                {
                    Logger?.RecordInfo($"Getting sub-property output for {subprop.Name}.");

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

                    numericSubstitute = counter;
                    result.Add(output);
                }
            }
            else
            {
                Logger?.RecordInfo($"{property.Name} is of type '{property.PropertyType}' which has has no sub-properties.");

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

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

            return(result, numericSubstitute);
        }
Example #2
0
        public static (List <string> strings, int count) GetSubPropertyOutput(PropertyDetails property, Profile profile, SemanticModel semModel, int indent)
        {
            var result = new List <string>();

            var subProperties = 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) = GetSubPropertyOutputAndCounter(profile, subprop.Name, numericSubstitute: numericSubstitute, indent: indent);

                    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) = GetSubPropertyOutputAndCounter(profile, string.Empty, numericSubstitute: numericSubstitute, indent: indent);

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

            return(result, numericSubstitute);
        }
        public static 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 = 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,
            };

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

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

            pd.Symbol = typeSymbol;

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

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

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

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

                    typeSymbol = semModel.GetTypeInfo(t).Type;
                }
                else
                {
                    Logger?.RecordInfo(StringRes.Info_PropertyTypeNotRecognizedAsGeneric.WithParams(propDetails.PropertyType));
                }
            }

            if (typeSymbol == null)
            {
                try
                {
                    typeSymbol = semModel.GetTypeInfo(prop.Type).Type;
                }
                catch (Exception)
                {
                    // 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(prop.SyntaxTree).GetSemanticModel(prop.SyntaxTree, ignoreAccessibility: true);

                    typeSymbol = localSemModel.GetTypeInfo(prop.Type).Type;
                }
            }

            return(typeSymbol);
        }
        private static (string output, string name, int counter) GetOutputToAdd(SemanticModel semModel, Profile profileOverload, PropertyDetails prop, int numericCounter = 0)
        {
            var(output, counter) = profileOverload == null
                ? GetPropertyOutputAndCounterForActiveProfile(prop, numericCounter, () => GetSubPropertyOutput(prop, GetSettings().GetActiveProfile(), semModel))
                : GetPropertyOutputAndCounter(profileOverload, prop, numericCounter, () => GetSubPropertyOutput(prop, profileOverload, semModel));

            return(output, prop.Name, counter);
        }
Example #6
0
 public static (string output, int counter) GetPropertyOutputAndCounterForActiveProfile(PropertyDetails property, int numericSubstitute, Func <(List <string> strings, int count)> getSubPropertyOutput = null)
        public static PropertyDetails GetPropertyDetails(SyntaxNode propertyNode, SemanticModel semModel)
        {
            var propertyName = GetIdentifier(propertyNode);
            var propertyType = Unknown;
            bool? propIsReadOnly = null;

            var descendantNodes = propertyNode.DescendantNodes().ToList();

            if (descendantNodes.Any(n => n is SimpleAsClauseSyntax))
            {
                propertyType = descendantNodes.OfType<SimpleAsClauseSyntax>().FirstOrDefault()?.Type.ToString();
            }
            else if (descendantNodes.Any(n => n is ObjectCreationExpressionSyntax))
            {
                propertyType = descendantNodes.OfType<ObjectCreationExpressionSyntax>().FirstOrDefault()?.Type.ToString();
            }
            else if (descendantNodes.Any(n => n is GenericNameSyntax))
            {
                propertyType = descendantNodes.OfType<GenericNameSyntax>().FirstOrDefault()?.ToString();
            }
            else if (descendantNodes.Any(n => n is PredefinedTypeSyntax))
            {
                propertyType = descendantNodes.OfType<PredefinedTypeSyntax>().FirstOrDefault()?.Keyword.ValueText;
            }

            if (descendantNodes.FirstOrDefault() is ParameterListSyntax)
            {
                propertyType += "()";
            }

            // Handle nullable types where the '?' is on the property name
            if (propertyType == Unknown && propertyNode is PropertyStatementSyntax propss)
            {
                if (propss.HasTrailingTrivia)
                {
                    var triviaList = propss.GetTrailingTrivia().ToList();
                    triviaList.Reverse();

                    foreach (var trivia in triviaList)
                    {
                        var triviaStr = trivia.ToString();

                        if (triviaStr.Contains("As "))
                        {
                            propertyType = triviaStr.Substring(triviaStr.IndexOf("As ") + 3);
                        }

                        if (triviaStr == "?")
                        {
                            propertyType += triviaStr;
                        }
                    }
                }
            }

            // Remove any namespace qualifications as we match class names as strings
            if (propertyType?.Contains(".") == true)
            {
                if (propertyType.Contains("Of "))
                {
                    var ofPos = propertyType.IndexOf("Of ");
                    var dotBeforeOfPos = propertyType.Substring(0, ofPos).LastIndexOf(".");

                    if (dotBeforeOfPos > -1)
                    {
                        propertyType = propertyType.Substring(dotBeforeOfPos + 1);
                    }

                    if (propertyType.Contains("."))
                    {
                        propertyType = propertyType.Substring(0, propertyType.IndexOf("Of ") + 3) +
                                       propertyType.Substring(propertyType.LastIndexOf(".") + 1);
                    }
                }
                else
                {
                    propertyType = propertyType.Substring(propertyType.LastIndexOf(".") + 1);
                }
            }

            if (propertyNode is PropertyStatementSyntax pss)
            {
                if (pss.Parent is PropertyBlockSyntax)
                {
                    propertyNode = pss.Parent;
                }
                else
                {
                    propIsReadOnly = pss.Modifiers.Any(m => m.RawKind == (int)SyntaxKind.ReadOnlyKeyword);
                }
            }

            if (propertyNode is PropertyBlockSyntax pbs)
            {
                var setter = pbs.ChildNodes().FirstOrDefault(n => n.RawKind == (int)SyntaxKind.SetAccessorBlock);

                propIsReadOnly = setter != null
                    ? (setter as AccessorBlockSyntax)?.AccessorStatement.Modifiers.Any(m => m.RawKind == (int)SyntaxKind.PrivateKeyword)
                    : pbs.PropertyStatement.Modifiers.Any(m => m.RawKind == (int)SyntaxKind.ReadOnlyKeyword);
            }

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

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

            ITypeSymbol typeSymbol = GetTypeSymbol(semModel, propertyNode, pd);

            pd.Symbol = typeSymbol;

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

            if (propDetails.PropertyType.IsGenericTypeName())
            {
                Logger?.RecordInfo(StringRes.Info_GettingGenericType);
                TypeSyntax typeSyntax = null;

                if (prop is PropertyStatementSyntax pss)
                {
                    if (pss.AsClause is SimpleAsClauseSyntax sacs)
                    {
                        if (sacs.Type is GenericNameSyntax gns)
                        {
                            typeSyntax = gns.TypeArgumentList.Arguments.First();
                        }
                        else if (sacs.Type is QualifiedNameSyntax qns)
                        {
                            typeSyntax = ((GenericNameSyntax)qns.Right).TypeArgumentList.Arguments.First();
                        }
                    }

                    if (typeSyntax == null)
                    {
                        Logger?.RecordInfo(StringRes.Info_PropertyTypeNotRecognizedAsGeneric.WithParams(propDetails.PropertyType));
                    }
                }

                if (prop is PropertyBlockSyntax pbs)
                {
                    typeSyntax = ((GenericNameSyntax)((SimpleAsClauseSyntax)pbs.PropertyStatement.AsClause).Type).TypeArgumentList.Arguments.First();
                }

                try
                {
                    typeSymbol = semModel.GetTypeInfo(typeSyntax).Type;
                }
                catch (Exception)
                {
                    // 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 = VisualBasicCompilation.Create(string.Empty).AddSyntaxTrees(prop.SyntaxTree).GetSemanticModel(prop.SyntaxTree, ignoreAccessibility: true);

                    typeSymbol = localSemModel.GetTypeInfo(typeSyntax).Type;
                }
            }
            else
            {
                if (prop is PropertyStatementSyntax pss)
                {
                    if (pss.AsClause != null)
                    {
                        try
                        {
                            typeSymbol = semModel.GetTypeInfo(((SimpleAsClauseSyntax)pss.AsClause).Type).Type;
                        }
                        catch (Exception)
                        {
                            // 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 = VisualBasicCompilation.Create(string.Empty)
                                                                      .AddSyntaxTrees(prop.SyntaxTree)
                                                                      .GetSemanticModel(prop.SyntaxTree, ignoreAccessibility: true);

                            typeSymbol = localSemModel.GetTypeInfo(((SimpleAsClauseSyntax)pss.AsClause).Type).Type;
                        }
                    }
                    else
                    {
                        try
                        {
                            if (pss.Identifier.TrailingTrivia.ToString().StartsWith("?"))
                            {
                                var propSyn = VisualBasicSyntaxTree.ParseText(prop.ToFullString().Replace("?", string.Empty).Trim() + "?");

                                var propType = ((SimpleAsClauseSyntax)((CompilationUnitSyntax)propSyn.GetRoot()).Members.OfType<PropertyStatementSyntax>().FirstOrDefault()?.AsClause)?.Type;
                                var propSemModel = VisualBasicCompilation.Create(string.Empty).AddSyntaxTrees(propSyn).GetSemanticModel(propSyn, true);

                                typeSymbol = propSemModel.GetTypeInfo(propType).Type;
                            }
                        }
                        catch (Exception)
                        {
                            Logger?.RecordInfo(StringRes.Info_FailedToGetNullableType.WithParams(propDetails.Name));
                        }
                    }
                }
                else if (prop is PropertyBlockSyntax pbs)
                {
                    try
                    {
                        typeSymbol = semModel.GetTypeInfo(((SimpleAsClauseSyntax)pbs.PropertyStatement.AsClause).Type).Type;
                    }
                    catch (Exception)
                    {
                        // 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 = VisualBasicCompilation.Create(string.Empty).AddSyntaxTrees(prop.SyntaxTree).GetSemanticModel(prop.SyntaxTree, ignoreAccessibility: true);

                        typeSymbol = localSemModel.GetTypeInfo(((SimpleAsClauseSyntax)pbs.PropertyStatement.AsClause).Type).Type;
                    }
                }
            }

            return typeSymbol;
        }