/// <summary> /// Inspects the property values of the <paramref name="node"/> object using Reflection and /// creates API call descriptions for the property values recursively. Properties that are not /// essential to the shape of the syntax tree (such as Span) are ignored. /// </summary> private APIList QuotePropertyValues(SyntaxNode node) { var result = APIList.Create(); var properties = node.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); // Filter out non-essential properties listed in nonStructuralProperties result.Add(properties.Where(propertyInfo => !nonStructuralProperties.Contains(propertyInfo.Name)) .Select(propertyInfo => QuotePropertyValue(node, propertyInfo)) .Where(apiCall => apiCall != null).ToArray( )); // HACK: factory methods for the following node types accept back the first "kind" parameter // that we filter out above. Add an artificial "property value" that can be later used to // satisfy the first parameter of type SyntaxKind. if (node is AccessorDeclarationSyntax || node is BinaryExpressionSyntax || node is ClassOrStructConstraintSyntax || node is CheckedExpressionSyntax || node is CheckedStatementSyntax || node is ConstructorInitializerSyntax || node is GotoStatementSyntax || node is InitializerExpressionSyntax || node is LiteralExpressionSyntax || node is MemberAccessExpressionSyntax || node is OrderingSyntax || node is PostfixUnaryExpressionSyntax || node is PrefixUnaryExpressionSyntax || node is DocumentationCommentTriviaSyntax || node is SwitchLabelSyntax || node is YieldStatementSyntax) { result.Add(new ApiCall("Kind", "SyntaxKind." + node.CSharpKind( ).ToString( ))); } return(result); }
/// <summary> /// The main recursive method that given a SyntaxNode recursively quotes the entire subtree. /// </summary> private ApiCall QuoteNode(SyntaxNode node, string name) { APIList quotedPropertyValues = QuotePropertyValues(node); MethodInfo factoryMethod = PickFactoryMethodToCreateNode(node); var factoryMethodCall = new MethodCall(factoryMethod.DeclaringType.Name + "." + factoryMethod.Name); var codeBlock = new ApiCall(name, factoryMethodCall); APIList.AddFactoryMethodArguments(factoryMethod, factoryMethodCall, quotedPropertyValues); ApiCall.AddModifyingCalls(node, codeBlock, quotedPropertyValues); return(codeBlock); }
/// <summary> /// Adds information about subsequent modifying fluent interface style calls on an object (like /// foo.With(...).With(...)) /// </summary> public static void AddModifyingCalls(object treeElement, ApiCall apiCall, APIList values) { var methods = treeElement.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance); foreach (var value in values) { var methodName = "With" + value.Name.ProperCase(); if (!methods.Any(m => m.Name == methodName)) { throw new NotSupportedException( ); } methodName = "." + methodName; apiCall.AddModifyingCall(new MethodCall(methodName, ArgList.Create(value))); } }
public static void AddFactoryMethodArguments(MethodInfo factory, MethodCall factoryMethodCall, APIList quotedValues) { foreach (var FMP in factory.GetParameters( )) { var pName = FMP.Name; var pType = FMP.ParameterType; ApiCall quotedCodeBlock = quotedValues.FindValue(pName); // special case to prefer Syntax.IdentifierName("C") to // Syntax.IdentifierName(Syntax.Identifier("C")) if (pName == "name" && pType == typeof(string)) { quotedCodeBlock = quotedValues.First(a => a.Name == "Identifier"); var methodCall = quotedCodeBlock.FactoryMethodCall as MethodCall; if (methodCall != null && methodCall.Name == "SyntaxFactory.Identifier") { if (methodCall.Arguments.Count == 1) { factoryMethodCall.Arguments.Add(methodCall.Arguments [0]); } else { factoryMethodCall.Arguments.Add(quotedCodeBlock); } quotedValues.Remove(quotedCodeBlock); continue; } } // special case to prefer Syntax.ClassDeclarationSyntax(string) instead of // Syntax.ClassDeclarationSyntax(SyntaxToken) if (pName == "identifier" && pType == typeof(string)) { var methodCall = quotedCodeBlock.FactoryMethodCall as MethodCall; if (methodCall != null && methodCall.Name == "SyntaxFactory.Identifier" && methodCall.Arguments.Count == 1) { factoryMethodCall.Arguments.Add(methodCall.Arguments [0]); quotedValues.Remove(quotedCodeBlock); continue; } } if (quotedCodeBlock != null) { factoryMethodCall.Arguments.Add(quotedCodeBlock); quotedValues.Remove(quotedCodeBlock); } else if (!FMP.IsOptional) { throw new InvalidOperationException( string.Format( "Couldn't find value for parameter '{0}' of method '{1}'. Go to QuotePropertyValues and add your node type to the exception list.", pName, factory)); } } }