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