Esempio n. 1
0
        public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
        {
            if (context.Node is ClassDeclarationSyntax cls)
            {
                ISymbol currentType = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, cls);
                if (HasNamedAttribute(
                        currentType,
                        "Microsoft.DotNet.Internal.Testing.DependencyInjection.Abstractions.TestDependencyInjectionSetupAttribute"
                        ))
                {
                    bool error  = false;
                    var  nested = cls.Parent as ClassDeclarationSyntax;
                    if (nested == null)
                    {
                        Diagnostics.Add(Error(DiagnosticIds.NestedClassRequired, $"Class {cls.Identifier.Text} must be nested inside class to use [TestDependencyInjectionSetupAttribute]", cls));
                        error = true;
                    }

                    if (cls.Modifiers.All(m => m.Kind() != SyntaxKind.StaticKeyword))
                    {
                        Diagnostics.Add(Error(DiagnosticIds.StaticClassRequired, $"Class {cls.Identifier.Text} must be declared static to use [TestDependencyInjectionSetupAttribute]", nested));
                        error = true;
                    }

                    if (!HasDependencyInjectionReference(context))
                    {
                        Diagnostics.Add(
                            Error(
                                DiagnosticIds.DependencyInjectionRequired,
                                "Missing reference to Microsoft.Extensions.DependencyInjection to use [TestDependencyInjectionSetupAttribute]",
                                cls
                                )
                            );
                        error = true;
                    }

                    if (error)
                    {
                        return;
                    }

                    if (nested.Modifiers.All(m => m.Kind() != SyntaxKind.PartialKeyword))
                    {
                        Diagnostics.Add(Error(DiagnosticIds.PartialClassRequired, $"Class {nested.Identifier.Text} must be declared partial to use [TestDependencyInjectionSetupAttribute]", nested));
                        error = true;
                    }

                    if (!(nested.Parent is NamespaceDeclarationSyntax))
                    {
                        Diagnostics.Add(Error(DiagnosticIds.NamespaceRequired, $"Class {nested.Identifier.Text} must be inside a namespace to use [TestDependencyInjectionSetupAttribute]", nested));
                        error = true;
                    }

                    if (error)
                    {
                        return;
                    }

                    ISymbol parentType = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, nested);
                    Declarations.Add(
                        new TestConfigDeclaration(
                            parentType.ContainingNamespace.FullName(),
                            nested.Identifier.Text,
                            currentType,
                            cls
                            )
                        );
                }
            }

            if (context.Node is MethodDeclarationSyntax method)
            {
                var sMethod = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, method) as IMethodSymbol;
                TestConfigDeclaration decl = Declarations.FirstOrDefault(
                    d => d.DeclaringClassSymbol.Equals(sMethod.ContainingType, SymbolEqualityComparer.Default)
                    );

                if (decl == null)
                {
                    return;
                }

                if (sMethod.Parameters.Length == 0)
                {
                    return;
                }

                if (sMethod.Parameters[0].Type.FullName() !=
                    "Microsoft.Extensions.DependencyInjection.IServiceCollection")
                {
                    return;
                }

                string configType;
                bool   isConfigurationAsync;
                bool   hasFetch;
                bool   isFetchAsync;
                if (sMethod.ReturnsVoid)
                {
                    configType           = null;
                    isConfigurationAsync = false;
                    isFetchAsync         = false;
                    hasFetch             = false;
                }
                else if (!(sMethod.ReturnType is INamedTypeSymbol returnType))
                {
                    return;
                }
                else if (returnType.FullName() == "System.Threading.Tasks.Task")
                {
                    configType           = null;
                    isConfigurationAsync = true;
                    isFetchAsync         = false;
                    hasFetch             = false;
                }
                else if (returnType.IsGenericType)
                {
                    if (returnType.ConstructedFrom.FullName() == "System.Action<T>" && returnType.TypeArguments[0].FullName() == "System.IServiceProvider")
                    {
                        configType           = null;
                        hasFetch             = true;
                        isFetchAsync         = false;
                        isConfigurationAsync = false;
                    }
                    else if (returnType.ConstructedFrom.FullName() == "System.Func<T,TResult>")
                    {
                        isConfigurationAsync = false;
                        hasFetch             = true;

                        if (returnType.TypeArguments[0].FullName() != "System.IServiceProvider")
                        {
                            return;
                        }


                        ITypeSymbol funcReturn = returnType.TypeArguments[1];
                        if (funcReturn.FullName() == "System.Threading.Tasks.Task")
                        {
                            configType   = null;
                            isFetchAsync = true;
                        }
                        else if (funcReturn is INamedTypeSymbol {
                            IsGenericType : true
                        } namedFuncReturn&&
                                 namedFuncReturn.ConstructedFrom.FullName() == "System.Threading.Tasks.Task<TResult>")
                        {
                            configType   = namedFuncReturn.TypeArguments[0].FullName();
                            isFetchAsync = true;
                        }
                        else
                        {
                            configType   = funcReturn.FullName();
                            isFetchAsync = false;
                        }
                    }
                    else if (returnType.ConstructedFrom.FullName() == "System.Threading.Tasks.Task<TResult>")
                    {
                        isConfigurationAsync = true;

                        if (returnType.TypeArguments[0] is INamedTypeSymbol {
                            IsGenericType : true
                        } asyncReturn)
                        {
                            if (asyncReturn.ConstructedFrom.FullName() == "System.Action<T>" && asyncReturn.TypeArguments[0].FullName() == "System.IServiceProvider")
                            {
                                configType   = null;
                                hasFetch     = true;
                                isFetchAsync = false;
                            }
                            else if (asyncReturn.ConstructedFrom.FullName() == "System.Func<T,TResult>" && asyncReturn.TypeArguments[0].FullName() == "System.IServiceProvider")
                            {
                                hasFetch = true;
                                ITypeSymbol funcReturn = asyncReturn.TypeArguments[1];
                                if (funcReturn.FullName() == "System.Threading.Tasks.Task")
                                {
                                    configType   = null;
                                    isFetchAsync = true;
                                }
                                else if (funcReturn is INamedTypeSymbol {
                                    IsGenericType : true
                                } namedFuncReturn&&
                                         namedFuncReturn.ConstructedFrom.FullName() == "System.Threading.Tasks.Task<TResult>")
                                {
                                    configType   = namedFuncReturn.TypeArguments[0].FullName();
                                    isFetchAsync = true;
                                }
                                else
                                {
                                    configType   = funcReturn.FullName();
                                    isFetchAsync = false;
                                }
                            }
                            else
                            {
                                return;
                            }
                        }
        public static string BuildTestData(TestConfigDeclaration decl)
        {
            Dictionary <string, int> parameterNameCounts = null;

            bool IsParameterNameUnique(string name)
            {
                if (parameterNameCounts == null)
                {
                    parameterNameCounts =
                        new Dictionary <string, int>();
                    foreach (ConfigMethod method in decl.Methods)
                    {
                        foreach (ConfigParameters parameter in method.Parameters)
                        {
                            if (!parameterNameCounts.TryGetValue(parameter.Name, out int value))
                            {
                                value = 0;
                            }

                            parameterNameCounts[parameter.Name] = value + 1;
                        }
                    }
                }

                return(parameterNameCounts[name] == 1);
            }

            string SafeParameterName(string name, ConfigMethod method)
            {
                if (IsParameterNameUnique(name))
                {
                    return(name);
                }

                return(method.GetItemName(NameFormat.Property) + FormatName(NameFormat.Property, name));
            }

            string parameterSection = "";

            if (decl.Methods.Any(m => m.Parameters.Count > 0))
            {
                parameterSection = $@"
                private Builder(
                    {BuilderParameters()})
                {{{BuilderCtorAssignments()}
                }}

                private Builder With(
                    {WithParameters()})
                {{
                    return new Builder(
                        {CtorCall()}
                    );
                }}

{BuilderModificationMethods()}
";
            }

            return($@"#pragma warning disable

namespace {decl.NamespaceName}
{{
    public partial class {decl.ParentName}
    {{
        private class TestData : System.IDisposable, System.IAsyncDisposable
        {{
            private Microsoft.Extensions.DependencyInjection.ServiceProvider _serviceProvider;

            private TestData(
                Microsoft.Extensions.DependencyInjection.ServiceProvider __serviceProvider{TestDataParameters()})
            {{
                _serviceProvider = __serviceProvider;{TestDataAssignment()}
            }}

{TestDataProperties()}

            public void Dispose() => _serviceProvider.Dispose();
            public System.Threading.Tasks.ValueTask DisposeAsync() => _serviceProvider.DisposeAsync();

            public static Builder Default => new Builder();

            public class Builder
            {{{BuilderFields()}

                public Builder () {{ }}

{parameterSection}

                public async System.Threading.Tasks.Task<TestData> BuildAsync()
                {{
                    var collection = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
                    
{ConfigurationCallbacks()}
                    var provider = Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(collection);
{FetchCallbacks()}

                    return new TestData(
                        provider{TestDataArgumentsFromProvider()}
                    );
                }}

                public TestData Build() => BuildAsync().GetAwaiter().GetResult();
            }}
        }}
    }}
}}
");

            string TestDataProperties()
            {
                return(BuildMethodList(
                           (b, m) => b.AppendFormat(
                               "\n            public {0} {1} {{ get; }}",
                               m.ReturnTypeSymbol,
                               m.GetItemName(NameFormat.Property)
                               )
                           ));
            }

            string TestDataParameters()
            {
                return(BuildMethodList(
                           (b, m) => b.AppendFormat(
                               ",\n                {0} {1}",
                               m.ReturnTypeSymbol,
                               m.GetItemName(NameFormat.Parameter)
                               )
                           ));
            }

            string TestDataAssignment()
            {
                return(BuildMethodList(
                           (b, m) => b.AppendFormat(
                               "\n                {0} = {1};",
                               m.GetItemName(NameFormat.Property),
                               m.GetItemName(NameFormat.Parameter)
                               )
                           ));
            }

            string BuilderFields()
            {
                return(BuildParameterList(
                           "",
                           (b, t, n) => b.AppendFormat(
                               "\n                private readonly {0} {1};",
                               t,
                               FormatName(NameFormat.Field, n)
                               )
                           ));
            }

            string BuilderParameters()
            {
                return(BuildParameterList(
                           ",\n                    ",
                           (b, t, n) => b.AppendFormat("{0} {1}", t, FormatName(NameFormat.Parameter, n))
                           ));
            }

            string BuilderCtorAssignments()
            {
                return(BuildParameterList(
                           "",
                           (b, t, n) => b.AppendFormat(
                               "\n                    {0} = {1};",
                               FormatName(NameFormat.Field, n),
                               FormatName(NameFormat.Parameter, n)
                               )
                           ));
            }

            string WithParameters()
            {
                return(BuildParameterListWithNullable(
                           ",\n                    ",
                           (builder, type, name, nullable) => builder.AppendFormat(
                               "{0}{1} {2} = default",
                               type,
                               nullable ? "" : "?",
                               FormatName(NameFormat.Parameter, name)
                               )
                           ));
            }

            string CtorCall()
            {
                return(BuildParameterList(
                           ",\n                        ",
                           (builder, _, name) => builder.AppendFormat(
                               "{0}:{1} ?? {2}",
                               FormatName(NameFormat.Parameter, name),
                               FormatName(NameFormat.Parameter, name),
                               FormatName(NameFormat.Field, name)
                               )
                           ));
            }

            string BuilderModificationMethods()
            {
                void WithParameterList(StringBuilder b, ConfigMethod m)
                {
                    b.Append("                public Builder With");
                    b.Append(m.Name);
                    b.Append("(");
                    for (int i = 0; i < m.Parameters.Count; i++)
                    {
                        ConfigParameters p = m.Parameters[i];
                        if (i != 0)
                        {
                            b.Append(", ");
                        }

                        b.Append(p.Type);
                        b.Append(" ");
                        string formatName = FormatName(NameFormat.Parameter, p.Name);
                        b.Append(formatName);
                    }

                    b.Append(") => With(");
                    for (int i = 0; i < m.Parameters.Count; i++)
                    {
                        ConfigParameters p = m.Parameters[i];
                        if (i != 0)
                        {
                            b.Append(", ");
                        }

                        string pName = FormatName(NameFormat.Parameter, p.Name);
                        b.Append(pName);
                        b.Append(": ");
                        b.Append(pName);
                    }

                    b.AppendLine(");");
                    b.AppendLine();
                }

                void IndividualParameters(StringBuilder b, ConfigMethod m)
                {
                    foreach (ConfigParameters p in m.Parameters)
                    {
                        string safeName = SafeParameterName(p.Name, m);
                        b.AppendFormat(
                            "                public Builder With{0}({1} {2}) => With({3}: {2});\n",
                            FormatName(NameFormat.Property, safeName),
                            p.Type,
                            FormatName(NameFormat.Parameter, p.Name),
                            FormatName(NameFormat.Parameter, safeName)
                            );
                    }
                }

                var builder = new StringBuilder();

                foreach (ConfigMethod method in decl.Methods)
                {
                    if (method.Parameters.Count == 0)
                    {
                        // These are default configurations
                        continue;
                    }

                    if (method.ConfigureAllParameters)
                    {
                        WithParameterList(builder, method);
                    }
                    else
                    {
                        IndividualParameters(builder, method);
                    }
                }

                return(builder.ToString());
            }

            string ConfigurationCallbacks()
            {
                var builder = new StringBuilder();

                foreach (ConfigMethod method in decl.Methods)
                {
                    builder.Append("                    ");
                    if (method.HasFetch)
                    {
                        builder.Append("var callback_");
                        builder.Append(method.GetItemName(NameFormat.Local));
                        builder.Append(" = ");
                    }

                    if (method.IsConfigurationAsync)
                    {
                        builder.Append("await ");
                    }

                    builder.Append(decl.ConfigClassName);
                    builder.Append('.');
                    builder.Append(method.Name);
                    builder.Append("(collection");
                    foreach (ConfigParameters parameter in method.Parameters)
                    {
                        builder.Append(", ");
                        builder.Append((string)FormatName(NameFormat.Field, parameter.Name));
                    }

                    builder.AppendLine(");");
                    builder.AppendLine();
                }

                return(builder.ToString());
            }

            string FetchCallbacks()
            {
                var builder = new StringBuilder();

                foreach (ConfigMethod method in decl.Methods)
                {
                    if (!method.HasFetch)
                    {
                        continue;
                    }

                    builder.Append("                    ");
                    if (!string.IsNullOrEmpty(method.ReturnTypeSymbol))
                    {
                        builder.Append("var value_");
                        builder.Append(method.GetItemName(NameFormat.Local));
                        builder.Append(" = ");
                    }

                    if (method.IsFetchAsync)
                    {
                        builder.Append("await ");
                    }

                    builder.Append("callback_");
                    builder.Append(method.GetItemName(NameFormat.Local));
                    builder.Append("(provider);");
                    builder.AppendLine();
                }

                return(builder.ToString());
            }

            string TestDataArgumentsFromProvider()
            {
                return(BuildMethodList(
                           (b, m) => b.AppendFormat(
                               ",\n                        value_{0}",
                               m.GetItemName(NameFormat.Local)
                               )
                           ));
            }

            string BuildMethodList(Action <StringBuilder, ConfigMethod> format)
            {
                var builder = new StringBuilder();

                foreach (ConfigMethod method in decl.Methods)
                {
                    if (method.ReturnTypeSymbol == null)
                    {
                        continue;
                    }

                    format(builder, method);
                }

                return(builder.ToString());
            }

            string BuildParameterListWithNullable(
                string between,
                Action <StringBuilder, string, string, bool> formatField)
            {
                var builder = new StringBuilder();

                foreach (ConfigMethod method in decl.Methods)
                {
                    foreach (ConfigParameters parameter in method.Parameters)
                    {
                        if (builder.Length != 0)
                        {
                            builder.Append(between);
                        }

                        formatField(
                            builder,
                            parameter.Type,
                            SafeParameterName(parameter.Name, method),
                            parameter.IsNullable
                            );
                    }
                }

                return(builder.ToString());
            }

            string BuildParameterList(string between, Action <StringBuilder, string, string> formatField)
            {
                return(BuildParameterListWithNullable(between, (b, t, m, n) => formatField(b, t, m)));
            }
        }