private static string GenType(LoggerClass lc)
        {
            var methods = new StringBuilder();

            foreach (var lm in lc.Methods)
            {
                if (lm.Parameters.Count > 0)
                {
                    methods.Append(GenStruct(lm));
                }
                methods.Append(GenEventId(lm));
                methods.Append(GenExtensionLogMethod(lm));
            }

            var namespaceStart = string.Empty;
            var namespaceEnd   = string.Empty;

            if (lc.Namespace != null)
            {
                namespaceStart = $"namespace {lc.Namespace}\n{{\n";
                namespaceEnd   = "}\n";
            }

            return($@"
{namespaceStart}
    partial class {lc.Name}
    {{
        {methods}
    }}
{namespaceEnd}
");
        }
Пример #2
0
            private void GenType(LoggerClass lc)
            {
                if (!string.IsNullOrWhiteSpace(lc.Namespace))
                {
                    _builder.Append($@"
namespace {lc.Namespace}
{{");
                }

                _builder.Append($@"
    partial class {lc.Name} {lc.Constraints}
    {{");

                foreach (LoggerMethod lm in lc.Methods)
                {
                    if (!UseLoggerMessageDefine(lm))
                    {
                        GenStruct(lm);
                    }

                    GenLogMethod(lm);
                }

                _builder.Append($@"
    }}");

                if (!string.IsNullOrWhiteSpace(lc.Namespace))
                {
                    _builder.Append($@"
}}");
                }
            }
        private static string GenType(LoggerClass lc)
        {
            var methods = new StringBuilder();

            foreach (var lm in lc.Methods)
            {
                methods.Append(GenStruct(lm));
                methods.Append(GenEventId(lm));
                methods.Append(GenExtensionLogMethod(lm));
            }

            var namespaceStart = string.Empty;
            var namespaceEnd   = string.Empty;

            if (lc.Namespace != null)
            {
                namespaceStart = $"namespace {lc.Namespace}\n{{\n";
                namespaceEnd   = "}\n";
            }

            return($@"
{namespaceStart}
    static class {lc.Name}
    {{
        {methods}
        public static {lc.OriginalInterfaceName} Wrap(this ILogger logger) => new __Wrapper__(logger);
        {GenWrapper(lc)}
    }}
{namespaceEnd}
");
        }
            private void GenEnumerationHelper(LoggerClass lc)
            {
                foreach (LoggerMethod lm in lc.Methods)
                {
                    if (UseLoggerMessageDefine(lm))
                    {
                        foreach (LoggerParameter p in lm.TemplateParameters)
                        {
                            if (p.IsEnumerable)
                            {
                                _builder.Append($@"
        [{_generatedCodeAttribute}]
        private static string __Enumerate(global::System.Collections.IEnumerable? enumerable)
        {{
            if (enumerable == null)
            {{
                return ""(null)"";
            }}

            var sb = new global::System.Text.StringBuilder();
            _ = sb.Append('[');

            bool first = true;
            foreach (object e in enumerable)
            {{
                if (!first)
                {{
                    _ = sb.Append("", "");
                }}

                if (e == null)
                {{
                    _ = sb.Append(""(null)"");
                }}
                else
                {{
                    if (e is global::System.IFormattable fmt)
                    {{
                        _ = sb.Append(fmt.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture));
                    }}
                    else
                    {{
                        _ = sb.Append(e);
                    }}
                }}

                first = false;
            }}

            _ = sb.Append(']');

            return sb.ToString();
        }}
");
                            }
                        }
                    }
                }
            }
            private string GenType(LoggerClass lc)
            {
                var sb = GetStringBuilder();

                try
                {
                    foreach (var lm in lc.Methods)
                    {
                        if (!UseLoggerMessageDefine(lm))
                        {
                            _ = sb.Append(GenStruct(lm));
                        }

                        _ = sb.Append(GenLogMethod(lm));
                    }

                    _ = sb.Append(GenEnumerationHelper(lc));

                    if (string.IsNullOrWhiteSpace(lc.Namespace))
                    {
                        return($@"
                        partial class {lc.Name} {lc.Constraints}
                        {{
                            {sb}
                        }}
                        ");
                    }

                    return($@"
                    namespace {lc.Namespace}
                    {{
                        partial class {lc.Name} {lc.Constraints}
                        {{
                            {sb}
                        }}
                    }}
                    ");
                }
                finally
                {
                    ReturnStringBuilder(sb);
                }
            }
        private static string GenWrapper(LoggerClass lc)
        {
            var methods = new StringBuilder();

            foreach (var lm in lc.Methods)
            {
                methods.Append(GenInstanceLogMethod(lm));
            }

            return($@"
        private sealed class __Wrapper__ : {lc.OriginalInterfaceName}
        {{
            private readonly ILogger __logger;

            public __Wrapper__(ILogger logger) => __logger = logger;
            {methods}
        }}
");
        }
            /// <summary>
            /// Gets the set of logging classes containing methods to output.
            /// </summary>
            public IReadOnlyList <LoggerClass> GetLogClasses(IEnumerable <ClassDeclarationSyntax> classes)
            {
                INamedTypeSymbol loggerMessageAttribute = _compilation.GetBestTypeByMetadataName(LoggerMessageAttribute);

                if (loggerMessageAttribute == null)
                {
                    // nothing to do if this type isn't available
                    return(Array.Empty <LoggerClass>());
                }

                INamedTypeSymbol loggerSymbol = _compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.ILogger");

                if (loggerSymbol == null)
                {
                    // nothing to do if this type isn't available
                    return(Array.Empty <LoggerClass>());
                }

                INamedTypeSymbol logLevelSymbol = _compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LogLevel");

                if (logLevelSymbol == null)
                {
                    // nothing to do if this type isn't available
                    return(Array.Empty <LoggerClass>());
                }

                INamedTypeSymbol exceptionSymbol = _compilation.GetBestTypeByMetadataName("System.Exception");

                if (exceptionSymbol == null)
                {
                    Diag(DiagnosticDescriptors.MissingRequiredType, null, "System.Exception");
                    return(Array.Empty <LoggerClass>());
                }

                INamedTypeSymbol enumerableSymbol = _compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable);
                INamedTypeSymbol stringSymbol     = _compilation.GetSpecialType(SpecialType.System_String);

                var results = new List <LoggerClass>();
                var ids     = new HashSet <int>();

                // we enumerate by syntax tree, to minimize the need to instantiate semantic models (since they're expensive)
                foreach (var group in classes.GroupBy(x => x.SyntaxTree))
                {
                    SemanticModel?sm = null;
                    foreach (ClassDeclarationSyntax classDec in group)
                    {
                        // stop if we're asked to
                        _cancellationToken.ThrowIfCancellationRequested();

                        LoggerClass?lc                   = null;
                        string      nspace               = string.Empty;
                        string?     loggerField          = null;
                        bool        multipleLoggerFields = false;

                        ids.Clear();
                        foreach (MemberDeclarationSyntax member in classDec.Members)
                        {
                            var method = member as MethodDeclarationSyntax;
                            if (method == null)
                            {
                                // we only care about methods
                                continue;
                            }

                            sm ??= _compilation.GetSemanticModel(classDec.SyntaxTree);
                            IMethodSymbol logMethodSymbol = sm.GetDeclaredSymbol(method, _cancellationToken) as IMethodSymbol;
                            Debug.Assert(logMethodSymbol != null, "log method is present.");
                            (int eventId, int?level, string message, string?eventName, bool skipEnabledCheck) = (-1, null, string.Empty, null, false);

                            foreach (AttributeListSyntax mal in method.AttributeLists)
                            {
                                foreach (AttributeSyntax ma in mal.Attributes)
                                {
                                    IMethodSymbol attrCtorSymbol = sm.GetSymbolInfo(ma, _cancellationToken).Symbol as IMethodSymbol;
                                    if (attrCtorSymbol == null || !loggerMessageAttribute.Equals(attrCtorSymbol.ContainingType, SymbolEqualityComparer.Default))
                                    {
                                        // badly formed attribute definition, or not the right attribute
                                        continue;
                                    }

                                    bool hasMisconfiguredInput = false;
                                    ImmutableArray <AttributeData>?boundAttrbutes = logMethodSymbol?.GetAttributes();

                                    if (boundAttrbutes == null)
                                    {
                                        continue;
                                    }

                                    foreach (AttributeData attributeData in boundAttrbutes)
                                    {
                                        // supports: [LoggerMessage(0, LogLevel.Warning, "custom message")]
                                        // supports: [LoggerMessage(eventId: 0, level: LogLevel.Warning, message: "custom message")]
                                        if (attributeData.ConstructorArguments.Any())
                                        {
                                            foreach (TypedConstant typedConstant in attributeData.ConstructorArguments)
                                            {
                                                if (typedConstant.Kind == TypedConstantKind.Error)
                                                {
                                                    hasMisconfiguredInput = true;
                                                }
                                            }

                                            ImmutableArray <TypedConstant> items = attributeData.ConstructorArguments;
                                            Debug.Assert(items.Length == 3);

                                            eventId = items[0].IsNull ? -1 : (int)GetItem(items[0]);
                                            level   = items[1].IsNull ? null : (int?)GetItem(items[1]);
                                            message = items[2].IsNull ? "" : (string)GetItem(items[2]);
                                        }

                                        // argument syntax takes parameters. e.g. EventId = 0
                                        // supports: e.g. [LoggerMessage(EventId = 0, Level = LogLevel.Warning, Message = "custom message")]
                                        if (attributeData.NamedArguments.Any())
                                        {
                                            foreach (KeyValuePair <string, TypedConstant> namedArgument in attributeData.NamedArguments)
                                            {
                                                TypedConstant typedConstant = namedArgument.Value;
                                                if (typedConstant.Kind == TypedConstantKind.Error)
                                                {
                                                    hasMisconfiguredInput = true;
                                                }
                                                else
                                                {
                                                    TypedConstant value = namedArgument.Value;
                                                    switch (namedArgument.Key)
                                                    {
                                                    case "EventId":
                                                        eventId = (int)GetItem(value);
                                                        break;

                                                    case "Level":
                                                        level = value.IsNull ? null : (int?)GetItem(value);
                                                        break;

                                                    case "SkipEnabledCheck":
                                                        skipEnabledCheck = (bool)GetItem(value);
                                                        break;

                                                    case "EventName":
                                                        eventName = (string?)GetItem(value);
                                                        break;

                                                    case "Message":
                                                        message = value.IsNull ? "" : (string)GetItem(value);
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    if (hasMisconfiguredInput)
                                    {
                                        // skip further generator execution and let compiler generate the errors
                                        break;
                                    }

                                    IMethodSymbol?methodSymbol = sm.GetDeclaredSymbol(method, _cancellationToken);
                                    if (methodSymbol != null)
                                    {
                                        var lm = new LoggerMethod
                                        {
                                            Name              = methodSymbol.Name,
                                            Level             = level,
                                            Message           = message,
                                            EventId           = eventId,
                                            EventName         = eventName,
                                            IsExtensionMethod = methodSymbol.IsExtensionMethod,
                                            Modifiers         = method.Modifiers.ToString(),
                                            SkipEnabledCheck  = skipEnabledCheck
                                        };

                                        ExtractTemplates(message, lm.TemplateMap, lm.TemplateList);

                                        bool keepMethod = true;   // whether or not we want to keep the method definition or if it's got errors making it so we should discard it instead
                                        if (lm.Name[0] == '_')
                                        {
                                            // can't have logging method names that start with _ since that can lead to conflicting symbol names
                                            // because the generated symbols start with _
                                            Diag(DiagnosticDescriptors.InvalidLoggingMethodName, method.Identifier.GetLocation());
                                            keepMethod = false;
                                        }

                                        if (!methodSymbol.ReturnsVoid)
                                        {
                                            // logging methods must return void
                                            Diag(DiagnosticDescriptors.LoggingMethodMustReturnVoid, method.ReturnType.GetLocation());
                                            keepMethod = false;
                                        }

                                        if (method.Arity > 0)
                                        {
                                            // we don't currently support generic methods
                                            Diag(DiagnosticDescriptors.LoggingMethodIsGeneric, method.Identifier.GetLocation());
                                            keepMethod = false;
                                        }

                                        bool isStatic  = false;
                                        bool isPartial = false;
                                        foreach (SyntaxToken mod in method.Modifiers)
                                        {
                                            if (mod.IsKind(SyntaxKind.PartialKeyword))
                                            {
                                                isPartial = true;
                                            }
                                            else if (mod.IsKind(SyntaxKind.StaticKeyword))
                                            {
                                                isStatic = true;
                                            }
                                        }

                                        if (!isPartial)
                                        {
                                            Diag(DiagnosticDescriptors.LoggingMethodMustBePartial, method.GetLocation());
                                            keepMethod = false;
                                        }

                                        if (method.Body != null)
                                        {
                                            Diag(DiagnosticDescriptors.LoggingMethodHasBody, method.Body.GetLocation());
                                            keepMethod = false;
                                        }

                                        // ensure there are no duplicate ids.
                                        if (ids.Contains(lm.EventId))
                                        {
                                            Diag(DiagnosticDescriptors.ShouldntReuseEventIds, ma.GetLocation(), lm.EventId, classDec.Identifier.Text);
                                        }
                                        else
                                        {
                                            _ = ids.Add(lm.EventId);
                                        }

                                        string msg = lm.Message;
                                        if (msg.StartsWith("INFORMATION:", StringComparison.OrdinalIgnoreCase) ||
                                            msg.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase) ||
                                            msg.StartsWith("WARNING:", StringComparison.OrdinalIgnoreCase) ||
                                            msg.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase) ||
                                            msg.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase) ||
                                            msg.StartsWith("ERR:", StringComparison.OrdinalIgnoreCase))
                                        {
                                            Diag(DiagnosticDescriptors.RedundantQualifierInMessage, ma.GetLocation(), method.Identifier.ToString());
                                        }

                                        bool foundLogger    = false;
                                        bool foundException = false;
                                        bool foundLogLevel  = level != null;
                                        foreach (IParameterSymbol paramSymbol in methodSymbol.Parameters)
                                        {
                                            string paramName = paramSymbol.Name;
                                            if (string.IsNullOrWhiteSpace(paramName))
                                            {
                                                // semantic problem, just bail quietly
                                                keepMethod = false;
                                                break;
                                            }

                                            ITypeSymbol paramTypeSymbol = paramSymbol !.Type;
                                            if (paramTypeSymbol is IErrorTypeSymbol)
                                            {
                                                // semantic problem, just bail quietly
                                                keepMethod = false;
                                                break;
                                            }

                                            string typeName = paramTypeSymbol.ToDisplayString(
                                                SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
                                                    SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier));

                                            var lp = new LoggerParameter
                                            {
                                                Name         = paramName,
                                                Type         = typeName,
                                                IsLogger     = !foundLogger && IsBaseOrIdentity(paramTypeSymbol !, loggerSymbol),
                                                IsException  = !foundException && IsBaseOrIdentity(paramTypeSymbol !, exceptionSymbol),
                                                IsLogLevel   = !foundLogLevel && IsBaseOrIdentity(paramTypeSymbol !, logLevelSymbol),
                                                IsEnumerable = IsBaseOrIdentity(paramTypeSymbol !, enumerableSymbol) && !IsBaseOrIdentity(paramTypeSymbol !, stringSymbol),
                                            };

                                            foundLogger    |= lp.IsLogger;
                                            foundException |= lp.IsException;
                                            foundLogLevel  |= lp.IsLogLevel;

                                            bool forceAsTemplateParams = false;
                                            if (lp.IsLogger && lm.TemplateMap.ContainsKey(paramName))
                                            {
                                                Diag(DiagnosticDescriptors.ShouldntMentionLoggerInMessage, paramSymbol.Locations[0], paramName);
                                                forceAsTemplateParams = true;
                                            }
                                            else if (lp.IsException && lm.TemplateMap.ContainsKey(paramName))
                                            {
                                                Diag(DiagnosticDescriptors.ShouldntMentionExceptionInMessage, paramSymbol.Locations[0], paramName);
                                                forceAsTemplateParams = true;
                                            }
                                            else if (lp.IsLogLevel && lm.TemplateMap.ContainsKey(paramName))
                                            {
                                                Diag(DiagnosticDescriptors.ShouldntMentionLogLevelInMessage, paramSymbol.Locations[0], paramName);
                                                forceAsTemplateParams = true;
                                            }
                                            else if (lp.IsLogLevel && level != null && !lm.TemplateMap.ContainsKey(paramName))
                                            {
                                                Diag(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate, paramSymbol.Locations[0], paramName);
                                            }
                                            else if (lp.IsTemplateParameter && !lm.TemplateMap.ContainsKey(paramName))
                                            {
                                                Diag(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate, paramSymbol.Locations[0], paramName);
                                            }

                                            if (paramName[0] == '_')
                                            {
                                                // can't have logging method parameter names that start with _ since that can lead to conflicting symbol names
                                                // because all generated symbols start with _
                                                Diag(DiagnosticDescriptors.InvalidLoggingMethodParameterName, paramSymbol.Locations[0]);
                                            }

                                            lm.AllParameters.Add(lp);
                                            if (lp.IsTemplateParameter || forceAsTemplateParams)
                                            {
                                                lm.TemplateParameters.Add(lp);
                                            }
                                        }

                                        if (keepMethod)
                                        {
                                            if (isStatic && !foundLogger)
                                            {
                                                Diag(DiagnosticDescriptors.MissingLoggerArgument, method.GetLocation(), lm.Name);
                                                keepMethod = false;
                                            }
                                            else if (!isStatic && foundLogger)
                                            {
                                                Diag(DiagnosticDescriptors.LoggingMethodShouldBeStatic, method.GetLocation());
                                            }
                                            else if (!isStatic && !foundLogger)
                                            {
                                                if (loggerField == null)
                                                {
                                                    (loggerField, multipleLoggerFields) = FindLoggerField(sm, classDec, loggerSymbol);
                                                }

                                                if (multipleLoggerFields)
                                                {
                                                    Diag(DiagnosticDescriptors.MultipleLoggerFields, method.GetLocation(), classDec.Identifier.Text);
                                                    keepMethod = false;
                                                }
                                                else if (loggerField == null)
                                                {
                                                    Diag(DiagnosticDescriptors.MissingLoggerField, method.GetLocation(), classDec.Identifier.Text);
                                                    keepMethod = false;
                                                }
                                                else
                                                {
                                                    lm.LoggerField = loggerField;
                                                }
                                            }

                                            if (level == null && !foundLogLevel)
                                            {
                                                Diag(DiagnosticDescriptors.MissingLogLevel, method.GetLocation());
                                                keepMethod = false;
                                            }

                                            foreach (KeyValuePair <string, string> t in lm.TemplateMap)
                                            {
                                                bool found = false;
                                                foreach (LoggerParameter p in lm.AllParameters)
                                                {
                                                    if (t.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
                                                    {
                                                        found = true;
                                                        break;
                                                    }
                                                }

                                                if (!found)
                                                {
                                                    Diag(DiagnosticDescriptors.TemplateHasNoCorrespondingArgument, ma.GetLocation(), t.Key);
                                                }
                                            }
                                        }

                                        if (lc == null)
                                        {
                                            // determine the namespace the class is declared in, if any
                                            SyntaxNode?potentialNamespaceParent = classDec.Parent;
                                            while (potentialNamespaceParent != null &&
                                                   potentialNamespaceParent is not NamespaceDeclarationSyntax
#if ROSLYN4_0_OR_GREATER
                                                   && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax
#endif
                                                   )
                                            {
                                                potentialNamespaceParent = potentialNamespaceParent.Parent;
                                            }

#if ROSLYN4_0_OR_GREATER
                                            if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent)
#else
                                            if (potentialNamespaceParent is NamespaceDeclarationSyntax namespaceParent)
#endif
                                            {
                                                nspace = namespaceParent.Name.ToString();
                                                while (true)
                                                {
                                                    namespaceParent = namespaceParent.Parent as NamespaceDeclarationSyntax;
                                                    if (namespaceParent == null)
                                                    {
                                                        break;
                                                    }

                                                    nspace = $"{namespaceParent.Name}.{nspace}";
                                                }
                                            }
                                        }

                                        if (keepMethod)
                                        {
                                            lc ??= new LoggerClass
                                            {
                                                Keyword     = classDec.Keyword.ValueText,
                                                Namespace   = nspace,
                                                Name        = classDec.Identifier.ToString() + classDec.TypeParameterList,
                                                Constraints = classDec.ConstraintClauses.ToString(),
                                                ParentClass = null,
                                            };

                                            LoggerClass currentLoggerClass = lc;
                                            var         parentLoggerClass  = (classDec.Parent as TypeDeclarationSyntax);

                                            bool IsAllowedKind(SyntaxKind kind) =>
                                            kind == SyntaxKind.ClassDeclaration ||
                                            kind == SyntaxKind.StructDeclaration ||
                                            kind == SyntaxKind.RecordDeclaration;

                                            while (parentLoggerClass != null && IsAllowedKind(parentLoggerClass.Kind()))
                                            {
                                                currentLoggerClass.ParentClass = new LoggerClass
                                                {
                                                    Keyword     = parentLoggerClass.Keyword.ValueText,
                                                    Namespace   = nspace,
                                                    Name        = parentLoggerClass.Identifier.ToString() + parentLoggerClass.TypeParameterList,
                                                    Constraints = parentLoggerClass.ConstraintClauses.ToString(),
                                                    ParentClass = null,
                                                };

                                                currentLoggerClass = currentLoggerClass.ParentClass;
                                                parentLoggerClass  = (parentLoggerClass.Parent as TypeDeclarationSyntax);
                                            }

                                            lc.Methods.Add(lm);
                                        }
                                    }
                                }
                            }
                        }

                        if (lc != null)
                        {
                            results.Add(lc);
                        }
                    }
                }

                return(results);
            }
Пример #8
0
            private void GenType(LoggerClass lc)
            {
                string nestedIndentation = "";

                if (!string.IsNullOrWhiteSpace(lc.Namespace))
                {
                    _builder.Append($@"
namespace {lc.Namespace}
{{");
                }

                LoggerClass parent        = lc.ParentClass;
                var         parentClasses = new List <string>();

                // loop until you find top level nested class
                while (parent != null)
                {
                    parentClasses.Add($"partial {parent.Keyword} {parent.Name} ");
                    parent = parent.ParentClass;
                }

                // write down top level nested class first
                for (int i = parentClasses.Count - 1; i >= 0; i--)
                {
                    _builder.Append($@"
    {nestedIndentation}{parentClasses[i]}
    {nestedIndentation}{{");
                    nestedIndentation += "    ";
                }

                _builder.Append($@"
    {nestedIndentation}partial {lc.Keyword} {lc.Name} 
    {nestedIndentation}{{");

                foreach (LoggerMethod lm in lc.Methods)
                {
                    if (!UseLoggerMessageDefine(lm))
                    {
                        GenStruct(lm, nestedIndentation);
                    }

                    GenLogMethod(lm, nestedIndentation);
                }

                _builder.Append($@"
    {nestedIndentation}}}");

                parent = lc.ParentClass;
                while (parent != null)
                {
                    nestedIndentation = new String(' ', nestedIndentation.Length - 4);
                    _builder.Append($@"
    {nestedIndentation}}}");
                    parent = parent.ParentClass;
                }

                if (!string.IsNullOrWhiteSpace(lc.Namespace))
                {
                    _builder.Append($@"
}}");
                }
            }