public override InjectableMethod Rate(InjectableType t, MethodDef m) { if (!m.HasBody || m.IsConstructor || m.IsStatic || !m.IsPublic || m.HasGenericParameters) { return(null); } var mask = t.TypeMapping.Source.Attributes & (TypeAttributes.Abstract | TypeAttributes.Interface | TypeAttributes.Sealed); // static class is Abstract|Sealed, but we allow non-abstract Sealed if (mask != 0 && mask != TypeAttributes.Sealed) { return(null); } var td = TD.Get(this, t); var rating = td.Score; var parlist = new List <int>(); foreach (var p in m.Parameters) { if (p.IsNormalMethodParameter) { if (IsSuitableInput(p.Type)) { parlist.Add(p.Index); } } } return(new Injectable(t, m, rating + parlist.Count, parlist)); }
private static MethodArgumentDescriptor[] GetArgumentDescriptors(List <ParameterSyntax> argumentList, SemanticModel semanticModel, MockFramework mockFramework) { MethodArgumentDescriptor[] argumentDescriptors = new MethodArgumentDescriptor[argumentList.Count()]; for (int i = 0; i < argumentDescriptors.Count(); i++) { string argumentName = argumentList[i].Identifier.Text; ParameterModifier modifier = GetArgumentModifier(argumentList[i]); var namedTypeSymbol = InjectableType.TryCreateInjectableTypeFromParameterNode(argumentList[i], semanticModel, mockFramework); if (namedTypeSymbol != null) { argumentDescriptors[i] = new MethodArgumentDescriptor(namedTypeSymbol, argumentName, modifier); continue; } var argumentType = argumentList[i].Type; string typeName = argumentType.ToString(); argumentDescriptors[i] = new MethodArgumentDescriptor(new TypeDescriptor(typeName, null), argumentName, modifier); } return(argumentDescriptors); }
private void WriteMockFieldInitializations(StringBuilder builder, TestGenerationContext context) { string template = context.Settings.GetTemplate(context.TestFramework, context.MockFramework, TemplateType.MockFieldInitialization); IDictionary <string, string> customMocks = context.Settings.CustomMocks; for (int i = 0; i < context.InjectedTypes.Count; i++) { InjectableType injectedType = context.InjectedTypes[i]; string line; if (customMocks.TryGetValue(injectedType.FullName, out string customMockClassFull)) { string customMockClassName = StringUtilities.GetShortNameFromFullTypeName(customMockClassFull); line = ReplaceInterfaceTokensCustom(context.Settings.CustomMockFieldInitializationTemplate, injectedType, context, customMockClassName); } else { line = ReplaceInterfaceTokens(template, injectedType, context); } builder.Append(line); if (i < context.InjectedTypes.Count - 1) { builder.AppendLine(); } } }
private static string ReplaceInterfaceTokens(string template, InjectableType injectableType) { return(StringUtilities.ReplaceTokens( template, (tokenName, propertyIndex, builder) => { switch (tokenName) { case "InterfaceName": builder.Append(injectableType.TypeName); break; case "InterfaceNameBase": builder.Append(injectableType.TypeBaseName); break; case "InterfaceType": builder.Append(injectableType.ToString()); break; case "InterfaceMockName": builder.Append(injectableType.MockName); break; default: // We didn't recognize it, just pass through. builder.Append($"${tokenName}$"); break; } })); }
private static bool WriteInterfaceContentToken(InjectableType injectableType, string tokenName, StringBuilder builder) { switch (tokenName) { case "InterfaceName": builder.Append(injectableType.TypeName); break; case "InterfaceNameBase": builder.Append(injectableType.TypeBaseName); break; case "InterfaceType": builder.Append(injectableType.ToString()); break; case "InterfaceMockName": builder.Append(injectableType.MockName); break; default: return(false); } return(true); }
private void WriteExplicitConstructor(StringBuilder builder, TestGenerationContext context, string currentIndent) { builder.Append($"new {context.ClassName}"); if (context.ConstructorTypes.Count > 0) { builder.AppendLine("("); for (int i = 0; i < context.ConstructorTypes.Count; i++) { string mockReferenceStatement; InjectableType constructorType = context.ConstructorTypes[i]; if (constructorType == null) { mockReferenceStatement = "TODO"; } else { string template = this.Settings.GetTemplate(context.TestFramework, context.MockFramework, TemplateType.MockObjectReference); mockReferenceStatement = ReplaceInterfaceTokens(template, constructorType, context); } builder.Append($"{currentIndent} {mockReferenceStatement}"); if (i < context.ConstructorTypes.Count - 1) { builder.AppendLine(","); } } builder.Append(")"); } else if (context.Properties.Count == 0) { builder.Append("()"); } if (context.Properties.Count > 0) { builder.AppendLine(); builder.AppendLine("{"); foreach (InjectableProperty property in context.Properties) { string template = this.Settings.GetTemplate(context.TestFramework, context.MockFramework, TemplateType.MockObjectReference); string mockReferenceStatement = ReplaceInterfaceTokens(template, property, context); builder.AppendLine($"{property.PropertyName} = {mockReferenceStatement},"); } builder.Append(@"}"); } }
private void WriteExplicitConstructor(StringBuilder builder, TestGenerationContext context, string currentIndent) { builder.Append($"new {context.ClassName}"); if (context.ConstructorTypes.Count > 0) { builder.AppendLine("("); for (int i = 0; i < context.ConstructorTypes.Count; i++) { string mockReferenceStatement; InjectableType constructorType = context.ConstructorTypes[i]; if (constructorType == null) { mockReferenceStatement = "TODO"; } else { mockReferenceStatement = CreateMockReferenceStatement(context, constructorType); } builder.Append($"{currentIndent} {mockReferenceStatement}"); if (i < context.ConstructorTypes.Count - 1) { builder.AppendLine(","); } } builder.Append(")"); } else if (context.Properties.Count == 0) { builder.Append("()"); } if (context.Properties.Count > 0) { builder.AppendLine(); builder.AppendLine("{"); foreach (InjectableProperty property in context.Properties) { builder.AppendLine($"{property.PropertyName} = {CreateMockReferenceStatement(context, property)},"); } builder.Append(@"}"); } }
// Works for both field declarations and initializations. private static void WriteFieldLines(StringBuilder builder, TestGenerationContext context, string template) { for (int i = 0; i < context.InjectedTypes.Count; i++) { InjectableType injectedType = context.InjectedTypes[i]; string line = ReplaceInterfaceTokens(template, injectedType, context); builder.Append(line); if (i < context.InjectedTypes.Count - 1) { builder.AppendLine(); } } }
public TD(ValueManglingFeature f, InjectableType t) { Fields = new List <FieldDef>(); foreach (var field in t.TypeMapping.Source.Fields) { if (!field.IsStatic && !field.IsInitOnly && (field.IsPublic || field.IsFamilyOrAssembly)) { var ft = field.FieldType; if (f.IsSuitableInput(ft)) { Fields.Add(field); } } } Score = t.Ctors.Count + Fields.Count; }
private InjectableType CheckSuitable(TypeMapping tm) { var t = tm.Source; if (!t.IsPublic && !t.IsNestedPublic && !t.IsNestedAssembly) { return(NonInjectableType); } if (t.IsValueType) { return(NonInjectableType); } if (t.HasGenericParameters) { return(NonInjectableType); } var bt = t.BaseType; if (bt == null) { return(NonInjectableType); } InjectableType btr = null; if (bt.ToTypeSig().ElementType != ElementType.Object) { var k = bt.CreateKey(); if (!Context.MappedTypes.TryGetValue(k, out var btm)) { return(NonInjectableType); } btr = GetInjectableType(btm); if (btr == NonInjectableType) { return(NonInjectableType); } } var ctors = t.FindInstanceConstructors().Where(IsSuitableCtor).ToList(); var result = new InjectableType(tm, btr, ctors); _injectableTypes.Add(result); return(result); }
private static string ReplaceInterfaceTokens(string template, InjectableType injectableType, TestGenerationContext context) { return(StringUtilities.ReplaceTokens( template, (tokenName, propertyIndex, builder) => { if (WriteGlobalToken(tokenName, builder, context)) { return; } if (WriteInterfaceContentToken(injectableType, tokenName, builder)) { return; } WriteTokenPassthrough(tokenName, builder); })); }
private static string CreateMockReferenceStatement(TestGenerationContext context, InjectableType injectedType) { string template; if (context.Settings.CustomMocks.ContainsKey(injectedType.FullName)) { template = context.Settings.CustomMockObjectReferenceTemplate; } else { template = context.Settings.GetTemplate(context.TestFramework, context.MockFramework, TemplateType.MockObjectReference); } return(ReplaceInterfaceTokens(template, injectedType, context)); }
public InjectableId(InjectableType type, uint componentId) { this.type = type; this.componentId = componentId; }
public static TD Get(ValueManglingFeature f, InjectableType t) => t.Details(f, () => new TD(f, t));
public InjectableIdAttribute(InjectableType type, uint componentId) { Id = new InjectableId(type, componentId); }
public Injectable(InjectableType type, MethodDef method, int score, IReadOnlyList <int> arglist) : base(type, method, score) { _arglist = arglist; }
private async Task <TestGenerationContext> CollectTestGenerationContextAsync( ProjectItemSummary selectedFile, string targetProjectNamespace, TestFramework testFramework, MockFramework mockFramework) { Microsoft.CodeAnalysis.Solution solution = CreateUnitTestBoilerplateCommandPackage.VisualStudioWorkspace.CurrentSolution; DocumentId documentId = solution.GetDocumentIdsWithFilePath(selectedFile.FilePath).FirstOrDefault(); if (documentId == null) { throw new InvalidOperationException("Could not find document in solution with file path " + selectedFile.FilePath); } var document = solution.GetDocument(documentId); SyntaxNode root = await document.GetSyntaxRootAsync(); SemanticModel semanticModel = await document.GetSemanticModelAsync(); SyntaxNode firstClassDeclaration = root.DescendantNodes().FirstOrDefault(node => node.Kind() == SyntaxKind.ClassDeclaration); if (firstClassDeclaration == null) { throw new InvalidOperationException("Could not find class declaration."); } if (firstClassDeclaration.ChildTokens().Any(node => node.Kind() == SyntaxKind.AbstractKeyword)) { throw new InvalidOperationException("Cannot unit test an abstract class."); } SyntaxToken classIdentifierToken = firstClassDeclaration.ChildTokens().FirstOrDefault(n => n.Kind() == SyntaxKind.IdentifierToken); if (classIdentifierToken == default(SyntaxToken)) { throw new InvalidOperationException("Could not find class identifier."); } NamespaceDeclarationSyntax namespaceDeclarationSyntax = null; if (!TypeUtilities.TryGetParentSyntax(firstClassDeclaration, out namespaceDeclarationSyntax)) { throw new InvalidOperationException("Could not find class namespace."); } // Find property injection types var injectableProperties = new List <InjectableProperty>(); string classFullName = namespaceDeclarationSyntax.Name + "." + classIdentifierToken; INamedTypeSymbol classType = semanticModel.Compilation.GetTypeByMetadataName(classFullName); foreach (ISymbol member in classType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers())) { if (member.Kind == SymbolKind.Property) { IPropertySymbol property = (IPropertySymbol)member; foreach (AttributeData attribute in property.GetAttributes()) { if (PropertyInjectionAttributeNames.Contains(attribute.AttributeClass.ToString())) { var injectableProperty = InjectableProperty.TryCreateInjectableProperty(property.Name, property.Type.ToString(), mockFramework); if (injectableProperty != null) { injectableProperties.Add(injectableProperty); } } } } } string className = classIdentifierToken.ToString(); // Find constructor injection types List <InjectableType> constructorInjectionTypes = new List <InjectableType>(); SyntaxNode constructorDeclaration = firstClassDeclaration.ChildNodes().FirstOrDefault(n => n.Kind() == SyntaxKind.ConstructorDeclaration); if (constructorDeclaration != null) { constructorInjectionTypes.AddRange( GetParameterListNodes(constructorDeclaration) .Select(node => InjectableType.TryCreateInjectableTypeFromParameterNode(node, semanticModel, mockFramework))); } // Find public method declarations IList <MethodDescriptor> methodDeclarations = new List <MethodDescriptor>(); foreach (MethodDeclarationSyntax methodDeclaration in firstClassDeclaration.ChildNodes().Where( n => n.Kind() == SyntaxKind.MethodDeclaration && ((MethodDeclarationSyntax)n).Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))) { var parameterList = GetParameterListNodes(methodDeclaration).ToList(); var parameterTypes = GetArgumentDescriptors(parameterList, semanticModel, mockFramework); var isAsync = methodDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword)) || DoesReturnTask(methodDeclaration); var hasReturnType = !DoesReturnNonGenericTask(methodDeclaration) && !DoesReturnVoid(methodDeclaration); methodDeclarations.Add(new MethodDescriptor(methodDeclaration.Identifier.Text, parameterTypes, isAsync, hasReturnType)); } string unitTestNamespace; string relativePath = this.GetRelativePath(selectedFile); if (string.IsNullOrEmpty(relativePath)) { unitTestNamespace = targetProjectNamespace; } else { List <string> defaultNamespaceParts = targetProjectNamespace.Split('.').ToList(); List <string> unitTestNamespaceParts = new List <string>(defaultNamespaceParts); unitTestNamespaceParts.AddRange(relativePath.Split('\\')); unitTestNamespace = string.Join(".", unitTestNamespaceParts); } List <InjectableType> injectedTypes = new List <InjectableType>(injectableProperties); injectedTypes.AddRange(constructorInjectionTypes.Where(t => t != null)); GenerateMockNames(injectedTypes); return(new TestGenerationContext( mockFramework, testFramework, document, unitTestNamespace, className, namespaceDeclarationSyntax.Name.ToString(), injectableProperties, constructorInjectionTypes, injectedTypes, methodDeclarations)); }
private async Task <TestGenerationContext> CollectTestGenerationContextAsync( ProjectItemSummary selectedFile, string targetProjectNamespace, TestFramework testFramework, MockFramework mockFramework, IBoilerplateSettings settings) { Microsoft.CodeAnalysis.Solution solution = CreateUnitTestBoilerplateCommandPackage.VisualStudioWorkspace.CurrentSolution; DocumentId documentId = solution.GetDocumentIdsWithFilePath(selectedFile.FilePath).FirstOrDefault(); if (documentId == null) { throw new InvalidOperationException("Could not find document in solution with file path " + selectedFile.FilePath); } var document = solution.GetDocument(documentId); SyntaxNode root = await document.GetSyntaxRootAsync(); SemanticModel semanticModel = await document.GetSemanticModelAsync(); SyntaxNode firstClassLikeDeclaration = root.DescendantNodes().FirstOrDefault(node => { var kind = node.Kind(); return(kind == SyntaxKind.ClassDeclaration || kind == SyntaxKind.StructDeclaration || kind == (SyntaxKind)9063); // record - Cannot update CodeAnalysis library because it's not found in VS 2019 }); if (firstClassLikeDeclaration == null) { throw new InvalidOperationException("Could not find class, struct or record declaration."); } if (firstClassLikeDeclaration.ChildTokens().Any(node => node.Kind() == SyntaxKind.AbstractKeyword)) { throw new InvalidOperationException("Cannot unit test an abstract class."); } SyntaxToken classIdentifierToken = firstClassLikeDeclaration.ChildTokens().FirstOrDefault(n => n.Kind() == SyntaxKind.IdentifierToken); if (classIdentifierToken == default(SyntaxToken)) { throw new InvalidOperationException("Could not find class identifier."); } object namespaceDeclarationSyntax = null; // 8842 is NamespaceDeclaration // 8845 is FileScopedNamespaceDeclaration // We would normally look for a node descended from BaseNamespaceDeclarationSyntax, but we don't have that type defined in the v1 Microsoft.CodeAnalysis DLL. // We can fix this once we are building against a higher version and can drop support for VS 2019. if (!TypeUtilities.TryGetParentSyntax(firstClassLikeDeclaration, (syntaxNode) => { return(syntaxNode.RawKind == 8842 || syntaxNode.RawKind == 8845); }, out namespaceDeclarationSyntax)) { throw new InvalidOperationException("Could not find class namespace."); } // Find property injection types var injectableProperties = new List <InjectableProperty>(); // We need to get the name via reflection since the DLL we are building against does not have the BaseNamespaceDeclarationSyntax or FileScopedNamespaceDeclarationSyntax types. string qualifiedNamespaceString = namespaceDeclarationSyntax.GetType().GetProperty("Name").GetValue(namespaceDeclarationSyntax, null).ToString(); string classFullName = qualifiedNamespaceString + "." + classIdentifierToken; INamedTypeSymbol classType = semanticModel.Compilation.GetTypeByMetadataName(classFullName); foreach (ISymbol member in classType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers())) { if (member.Kind == SymbolKind.Property) { IPropertySymbol property = (IPropertySymbol)member; foreach (AttributeData attribute in property.GetAttributes()) { if (PropertyInjectionAttributeNames.Contains(attribute.AttributeClass.ToString())) { var injectableProperty = InjectableProperty.TryCreateInjectableProperty(property.Name, property.Type.ToString(), mockFramework); if (injectableProperty != null) { injectableProperties.Add(injectableProperty); } } } } } string className = classIdentifierToken.ToString(); // Find constructor injection types List <InjectableType> constructorInjectionTypes = new List <InjectableType>(); SyntaxNode constructorDeclaration = firstClassLikeDeclaration.ChildNodes().FirstOrDefault(n => n.Kind() == SyntaxKind.ConstructorDeclaration); if (constructorDeclaration != null) { constructorInjectionTypes.AddRange( GetParameterListNodes(constructorDeclaration) .Select(node => InjectableType.TryCreateInjectableTypeFromParameterNode(node, semanticModel, mockFramework))); } // Find public method declarations IList <MethodDescriptor> methodDeclarations = new List <MethodDescriptor>(); foreach (MethodDeclarationSyntax methodDeclaration in firstClassLikeDeclaration.ChildNodes().Where( n => n.Kind() == SyntaxKind.MethodDeclaration && ((MethodDeclarationSyntax)n).Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))) { var parameterList = GetParameterListNodes(methodDeclaration).ToList(); var parameterTypes = GetArgumentDescriptors(parameterList, semanticModel, mockFramework); var attributeList = GetAttributeListNodes(methodDeclaration); var isAsync = methodDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword)) || DoesReturnTask(methodDeclaration); var hasReturnType = !DoesReturnNonGenericTask(methodDeclaration) && !DoesReturnVoid(methodDeclaration); string returnType = methodDeclaration.ReturnType.ToFullString(); methodDeclarations.Add(new MethodDescriptor(methodDeclaration.Identifier.Text, parameterTypes, isAsync, hasReturnType, returnType, attributeList)); } string unitTestNamespace; string relativePath = this.GetRelativePath(selectedFile); if (string.IsNullOrEmpty(relativePath)) { unitTestNamespace = targetProjectNamespace; } else { List <string> defaultNamespaceParts = targetProjectNamespace.Split('.').ToList(); List <string> unitTestNamespaceParts = new List <string>(defaultNamespaceParts); unitTestNamespaceParts.AddRange(relativePath.Split('\\')); unitTestNamespace = string.Join(".", unitTestNamespaceParts); } List <InjectableType> injectedTypes = new List <InjectableType>(injectableProperties); injectedTypes.AddRange(constructorInjectionTypes.Where(t => t != null)); GenerateMockNames(injectedTypes); return(new TestGenerationContext( mockFramework, testFramework, document, settings, unitTestNamespace, className, qualifiedNamespaceString, injectableProperties, constructorInjectionTypes, injectedTypes, methodDeclarations)); }