Support generating log methods in nested classes (#54180)
authorMaryam Ariyan <maryam.ariyan@microsoft.com>
Tue, 15 Jun 2021 22:36:02 +0000 (18:36 -0400)
committerGitHub <noreply@github.com>
Tue, 15 Jun 2021 22:36:02 +0000 (18:36 -0400)
22 files changed:
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/DiagnosticDescriptors.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/Strings.resx
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.cs.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.de.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.es.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.fr.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.it.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.ja.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.ko.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.pl.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.pt-BR.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.ru.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.tr.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithNestedClass.generated.txt [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratedCodeTests.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/NestedClassTestsExtensions.cs [new file with mode: 0644]

index 2e7e0ac..2e3f31a 100644 (file)
@@ -32,14 +32,6 @@ namespace Microsoft.Extensions.Logging.Generators
             DiagnosticSeverity.Error,
             isEnabledByDefault: true);
 
-        public static DiagnosticDescriptor LoggingMethodInNestedType { get; } = new DiagnosticDescriptor(
-            id: "SYSLIB1004",
-            title: new LocalizableResourceString(nameof(SR.LoggingMethodInNestedTypeMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
-            messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodInNestedTypeMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
-            category: "LoggingGenerator",
-            DiagnosticSeverity.Error,
-            isEnabledByDefault: true);
-
         public static DiagnosticDescriptor MissingRequiredType { get; } = new DiagnosticDescriptor(
             id: "SYSLIB1005",
             title: new LocalizableResourceString(nameof(SR.MissingRequiredTypeTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
index 748bfd3..2b0bc4e 100644 (file)
@@ -65,6 +65,7 @@ namespace Microsoft.Extensions.Logging.Generators
 
             private void GenType(LoggerClass lc)
             {
+                string nestedIndentation = "";
                 if (!string.IsNullOrWhiteSpace(lc.Namespace))
                 {
                     _builder.Append($@"
@@ -72,22 +73,49 @@ 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.Constraints}");
+                    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($@"
-    partial class {lc.Name} {lc.Constraints}
-    {{");
+    {nestedIndentation}partial {lc.Keyword} {lc.Name} {lc.Constraints}
+    {nestedIndentation}{{");
 
                 foreach (LoggerMethod lm in lc.Methods)
                 {
                     if (!UseLoggerMessageDefine(lm))
                     {
-                        GenStruct(lm);
+                        GenStruct(lm, nestedIndentation);
                     }
 
-                    GenLogMethod(lm);
+                    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))
                 {
@@ -96,83 +124,83 @@ namespace {lc.Namespace}
                 }
             }
 
-            private void GenStruct(LoggerMethod lm)
+            private void GenStruct(LoggerMethod lm, string nestedIndentation)
             {
                 _builder.AppendLine($@"
-        [{s_generatedCodeAttribute}]
-        private readonly struct __{lm.Name}Struct : global::System.Collections.Generic.IReadOnlyList<global::System.Collections.Generic.KeyValuePair<string, object?>>
-        {{");
-                GenFields(lm);
+        {nestedIndentation}[{s_generatedCodeAttribute}]
+        {nestedIndentation}private readonly struct __{lm.Name}Struct : global::System.Collections.Generic.IReadOnlyList<global::System.Collections.Generic.KeyValuePair<string, object?>>
+        {nestedIndentation}{{");
+                GenFields(lm, nestedIndentation);
 
                 if (lm.TemplateParameters.Count > 0)
                 {
                     _builder.Append($@"
-            public __{lm.Name}Struct(");
+            {nestedIndentation}public __{lm.Name}Struct(");
                     GenArguments(lm);
                     _builder.Append($@")
-            {{");
+            {nestedIndentation}{{");
                     _builder.AppendLine();
-                    GenFieldAssignments(lm);
+                    GenFieldAssignments(lm, nestedIndentation);
                     _builder.Append($@"
-            }}
+            {nestedIndentation}}}
 ");
                 }
 
                 _builder.Append($@"
-            public override string ToString()
-            {{
+            {nestedIndentation}public override string ToString()
+            {nestedIndentation}{{
 ");
-                GenVariableAssignments(lm);
+                GenVariableAssignments(lm, nestedIndentation);
                 _builder.Append($@"
-                return $""{lm.Message}"";
-            }}
+                {nestedIndentation}return $""{lm.Message}"";
+            {nestedIndentation}}}
 ");
                 _builder.Append($@"
-            public static string Format(__{lm.Name}Struct state, global::System.Exception? ex) => state.ToString();
+            {nestedIndentation}public static string Format(__{lm.Name}Struct state, global::System.Exception? ex) => state.ToString();
 
-            public int Count => {lm.TemplateParameters.Count + 1};
+            {nestedIndentation}public int Count => {lm.TemplateParameters.Count + 1};
 
-            public global::System.Collections.Generic.KeyValuePair<string, object?> this[int index]
-            {{
-                get => index switch
-                {{
+            {nestedIndentation}public global::System.Collections.Generic.KeyValuePair<string, object?> this[int index]
+            {nestedIndentation}{{
+                {nestedIndentation}get => index switch
+                {nestedIndentation}{{
 ");
-                GenCases(lm);
+                GenCases(lm, nestedIndentation);
                 _builder.Append($@"
-                    _ => throw new global::System.IndexOutOfRangeException(nameof(index)),  // return the same exception LoggerMessage.Define returns in this case
-                }};
+                    {nestedIndentation}_ => throw new global::System.IndexOutOfRangeException(nameof(index)),  // return the same exception LoggerMessage.Define returns in this case
+                {nestedIndentation}}};
             }}
 
-            public global::System.Collections.Generic.IEnumerator<global::System.Collections.Generic.KeyValuePair<string, object?>> GetEnumerator()
-            {{
-                for (int i = 0; i < {lm.TemplateParameters.Count + 1}; i++)
-                {{
-                    yield return this[i];
-                }}
-            }}
+            {nestedIndentation}public global::System.Collections.Generic.IEnumerator<global::System.Collections.Generic.KeyValuePair<string, object?>> GetEnumerator()
+            {nestedIndentation}{{
+                {nestedIndentation}for (int i = 0; i < {lm.TemplateParameters.Count + 1}; i++)
+                {nestedIndentation}{{
+                    {nestedIndentation}yield return this[i];
+                {nestedIndentation}}}
+            {nestedIndentation}}}
 
-            global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
-        }}
+            {nestedIndentation}global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
+        {nestedIndentation}}}
 ");
             }
 
-            private void GenFields(LoggerMethod lm)
+            private void GenFields(LoggerMethod lm, string nestedIndentation)
             {
                 foreach (LoggerParameter p in lm.TemplateParameters)
                 {
-                    _builder.AppendLine($"            private readonly {p.Type} _{p.Name};");
+                    _builder.AppendLine($"            {nestedIndentation}private readonly {p.Type} _{p.Name};");
                 }
             }
 
-            private void GenFieldAssignments(LoggerMethod lm)
+            private void GenFieldAssignments(LoggerMethod lm, string nestedIndentation)
             {
                 foreach (LoggerParameter p in lm.TemplateParameters)
                 {
-                    _builder.AppendLine($"                this._{p.Name} = {p.Name};");
+                    _builder.AppendLine($"                {nestedIndentation}this._{p.Name} = {p.Name};");
                 }
             }
 
-            private void GenVariableAssignments(LoggerMethod lm)
+            private void GenVariableAssignments(LoggerMethod lm, string nestedIndentation)
             {
                 foreach (KeyValuePair<string, string> t in lm.TemplateMap)
                 {
@@ -192,20 +220,20 @@ namespace {lc.Namespace}
                     {
                         if (lm.TemplateParameters[index].IsEnumerable)
                         {
-                            _builder.AppendLine($"                var {t.Key} = "
+                            _builder.AppendLine($"                {nestedIndentation}var {t.Key} = "
                                 + $"global::__LoggerMessageGenerator.Enumerate((global::System.Collections.IEnumerable ?)this._{lm.TemplateParameters[index].Name});");
 
                             _needEnumerationHelper = true;
                         }
                         else
                         {
-                            _builder.AppendLine($"                var {t.Key} = this._{lm.TemplateParameters[index].Name};");
+                            _builder.AppendLine($"                {nestedIndentation}var {t.Key} = this._{lm.TemplateParameters[index].Name};");
                         }
                     }
                 }
             }
 
-            private void GenCases(LoggerMethod lm)
+            private void GenCases(LoggerMethod lm, string nestedIndentation)
             {
                 int index = 0;
                 foreach (LoggerParameter p in lm.TemplateParameters)
@@ -217,10 +245,10 @@ namespace {lc.Namespace}
                         name = lm.TemplateMap[name];
                     }
 
-                    _builder.AppendLine($"                    {index++} => new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{name}\", this._{p.Name}),");
+                    _builder.AppendLine($"                    {nestedIndentation}{index++} => new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{name}\", this._{p.Name}),");
                 }
 
-                _builder.AppendLine($"                    {index++} => new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{{OriginalFormat}}\", \"{ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message)}\"),");
+                _builder.AppendLine($"                    {nestedIndentation}{index++} => new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{{OriginalFormat}}\", \"{ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message)}\"),");
             }
 
             private void GenCallbackArguments(LoggerMethod lm)
@@ -321,7 +349,7 @@ namespace {lc.Namespace}
                 _builder.Append(')');
             }
 
-            private void GenLogMethod(LoggerMethod lm)
+            private void GenLogMethod(LoggerMethod lm, string nestedIndentation)
             {
                 string level = GetLogLevel(lm);
                 string extension = lm.IsExtensionMethod ? "this " : string.Empty;
@@ -332,13 +360,13 @@ namespace {lc.Namespace}
                 if (UseLoggerMessageDefine(lm))
                 {
                     _builder.Append($@"
-        [{s_generatedCodeAttribute}]
-        private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, ");
+        {nestedIndentation}[{s_generatedCodeAttribute}]
+        {nestedIndentation}private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, ");
 
                     GenDefineTypes(lm, brackets: false);
 
-                    _builder.Append(@$"global::System.Exception?> __{lm.Name}Callback =
-            global::Microsoft.Extensions.Logging.LoggerMessage.Define");
+                    _builder.Append($@"global::System.Exception?> __{lm.Name}Callback =
+            {nestedIndentation}global::Microsoft.Extensions.Logging.LoggerMessage.Define");
 
                     GenDefineTypes(lm, brackets: true);
 
@@ -347,20 +375,20 @@ namespace {lc.Namespace}
                 }
 
                 _builder.Append($@"
-        [{s_generatedCodeAttribute}]
-        {lm.Modifiers} void {lm.Name}({extension}");
+        {nestedIndentation}[{s_generatedCodeAttribute}]
+        {nestedIndentation}{lm.Modifiers} void {lm.Name}({extension}");
 
                 GenParameters(lm);
 
                 _builder.Append($@")
-        {{
-            if ({logger}.IsEnabled({level}))
-            {{");
+        {nestedIndentation}{{
+            {nestedIndentation}if ({logger}.IsEnabled({level}))
+            {nestedIndentation}{{");
 
                 if (UseLoggerMessageDefine(lm))
                 {
                     _builder.Append($@"
-                __{lm.Name}Callback({logger}, ");
+                {nestedIndentation}__{lm.Name}Callback({logger}, ");
 
                     GenCallbackArguments(lm);
 
@@ -369,7 +397,7 @@ namespace {lc.Namespace}
                 else
                 {
                     _builder.Append($@"
-                {logger}.Log(
+                {nestedIndentation}{logger}.Log(
                     {level},
                     new global::Microsoft.Extensions.Logging.EventId({lm.EventId}, {eventName}),
                     ");
@@ -380,8 +408,8 @@ namespace {lc.Namespace}
                 }
 
                 _builder.Append($@"
-            }}
-        }}");
+            {nestedIndentation}}}
+        {nestedIndentation}}}");
 
                 static string GetException(LoggerMethod lm)
                 {
index 8542778..534249c 100644 (file)
@@ -28,7 +28,7 @@ namespace Microsoft.Extensions.Logging.Generators
             }
 
             /// <summary>
-            /// Gets the set of logging classes containing methods to output.
+            /// Gets the set of logging classes or structs containing methods to output.
             /// </summary>
             public IReadOnlyList<LoggerClass> GetLogClasses(IEnumerable<ClassDeclarationSyntax> classes)
             {
@@ -331,28 +331,23 @@ namespace Microsoft.Extensions.Logging.Generators
                                         if (lc == null)
                                         {
                                             // determine the namespace the class is declared in, if any
-                                            var ns = classDec.Parent as NamespaceDeclarationSyntax;
-                                            if (ns == null)
+                                            SyntaxNode? potentialNamespaceParent = classDec.Parent;
+                                            while (potentialNamespaceParent != null && potentialNamespaceParent is not NamespaceDeclarationSyntax)
                                             {
-                                                if (classDec.Parent is not CompilationUnitSyntax)
-                                                {
-                                                    // since this generator doesn't know how to generate a nested type...
-                                                    Diag(DiagnosticDescriptors.LoggingMethodInNestedType, classDec.Identifier.GetLocation());
-                                                    keepMethod = false;
-                                                }
+                                                potentialNamespaceParent = potentialNamespaceParent.Parent;
                                             }
-                                            else
+                                            if (potentialNamespaceParent is NamespaceDeclarationSyntax namespaceParent)
                                             {
-                                                nspace = ns.Name.ToString();
+                                                nspace = namespaceParent.Name.ToString();
                                                 while (true)
                                                 {
-                                                    ns = ns.Parent as NamespaceDeclarationSyntax;
-                                                    if (ns == null)
+                                                    namespaceParent = namespaceParent.Parent as NamespaceDeclarationSyntax;
+                                                    if (namespaceParent == null)
                                                     {
                                                         break;
                                                     }
 
-                                                    nspace = $"{ns.Name}.{nspace}";
+                                                    nspace = $"{namespaceParent.Name}.{nspace}";
                                                 }
                                             }
                                         }
@@ -361,11 +356,36 @@ namespace Microsoft.Extensions.Logging.Generators
                                         {
                                             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);
                                         }
                                     }
@@ -568,9 +588,11 @@ namespace Microsoft.Extensions.Logging.Generators
         internal class LoggerClass
         {
             public readonly List<LoggerMethod> Methods = new ();
+            public string Keyword = string.Empty;
             public string Namespace = string.Empty;
             public string Name = string.Empty;
             public string Constraints = string.Empty;
+            public LoggerClass? ParentClass;
         }
 
         /// <summary>
index ed06151..66a9a1e 100644 (file)
   <data name="InvalidLoggingMethodParameterNameMessage" xml:space="preserve">
     <value>Logging method parameter names cannot start with _</value>
   </data>
-  <data name="LoggingMethodInNestedTypeMessage" xml:space="preserve">
-    <value>Logging class cannot be in nested types</value>
-  </data>
   <data name="MissingRequiredTypeTitle" xml:space="preserve">
     <value>Could not find a required type definition</value>
   </data>
index 980f5e2..7e5140b 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 3c5bd3f..c0b1d1b 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 3678d12..f18fd61 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index aa1939b..ae9e608 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index cf54d51..241484f 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 70ccaa7..3854417 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index c7248aa..1fec30a 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 4752235..3bfdf3e 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index da1942b..4417c68 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index ed2de8c..5263092 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 4b86ee0..539f7ec 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index 87f8d9b..cbc17ee 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
index a34749c..297a39f 100644 (file)
         <target state="new">Logging methods cannot have a body</target>
         <note />
       </trans-unit>
-      <trans-unit id="LoggingMethodInNestedTypeMessage">
-        <source>Logging class cannot be in nested types</source>
-        <target state="new">Logging class cannot be in nested types</target>
-        <note />
-      </trans-unit>
       <trans-unit id="LoggingMethodIsGenericMessage">
         <source>Logging methods cannot be generic</source>
         <target state="new">Logging methods cannot be generic</target>
diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithNestedClass.generated.txt b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithNestedClass.generated.txt
new file mode 100644 (file)
index 0000000..024e046
--- /dev/null
@@ -0,0 +1,36 @@
+// <auto-generated/>
+#nullable enable
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses.NestedNamespace
+{
+    partial class MultiLevelNestedClass 
+    {
+        partial struct NestedStruct 
+        {
+            partial record NestedRecord 
+            {
+                partial class NestedClassTestsExtensions<T1> where T1 : Class1
+                {
+                    partial class NestedMiddleParentClass 
+                    {
+                        partial class Nested<T2> where T2 : Class2
+                        {
+                            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+                            private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.Exception?> __M9Callback =
+                                global::Microsoft.Extensions.Logging.LoggerMessage.Define(global::Microsoft.Extensions.Logging.LogLevel.Debug, new global::Microsoft.Extensions.Logging.EventId(9, nameof(M9)), "M9", true); 
+
+                            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+                            public static partial void M9(global::Microsoft.Extensions.Logging.ILogger logger)
+                            {
+                                if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug))
+                                {
+                                    __M9Callback(logger, null);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
index 7e0661d..c9180b7 100644 (file)
@@ -346,6 +346,47 @@ namespace Microsoft.Extensions.Logging.Generators.Tests
         }
 
         [Fact]
+        public void NestedClassTests()
+        {
+            var logger = new MockLogger();
+
+            logger.Reset();
+            NestedClassTestsExtensions<ABC>.NestedMiddleParentClass.NestedClass.M8(logger);
+            Assert.Null(logger.LastException);
+            Assert.Equal("M8", logger.LastFormattedString);
+            Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+            Assert.Equal(1, logger.CallCount);
+
+            logger.Reset();
+            NonStaticNestedClassTestsExtensions<ABC>.NonStaticNestedMiddleParentClass.NestedClass.M9(logger);
+            Assert.Null(logger.LastException);
+            Assert.Equal("M9", logger.LastFormattedString);
+            Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+            Assert.Equal(1, logger.CallCount);
+
+            logger.Reset();
+            NestedStruct.Logger.M10(logger);
+            Assert.Null(logger.LastException);
+            Assert.Equal("M10", logger.LastFormattedString);
+            Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+            Assert.Equal(1, logger.CallCount);
+
+            logger.Reset();
+            NestedRecord.Logger.M11(logger);
+            Assert.Null(logger.LastException);
+            Assert.Equal("M11", logger.LastFormattedString);
+            Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+            Assert.Equal(1, logger.CallCount);
+
+            logger.Reset();
+            MultiLevelNestedClass.NestedStruct.NestedRecord.Logger.M12(logger);
+            Assert.Null(logger.LastException);
+            Assert.Equal("M12", logger.LastFormattedString);
+            Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+            Assert.Equal(1, logger.CallCount);
+        }
+
+        [Fact]
         public void TemplateTests()
         {
             var logger = new MockLogger();
index d727420..433d601 100644 (file)
@@ -80,6 +80,41 @@ namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
             await VerifyAgainstBaselineUsingFile("TestWithDynamicLogLevel.generated.txt", testSourceCode);
         }
 
+        [Fact]
+        public async Task TestBaseline_TestWithNestedClass_Success()
+        {
+            string testSourceCode = @"
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+    namespace NestedNamespace
+    {
+        public static partial class MultiLevelNestedClass
+        {
+            public partial struct NestedStruct
+            {
+                internal partial record NestedRecord(string Name, string Address)
+                { 
+                    internal static partial class NestedClassTestsExtensions<T1> where T1 : Class1
+                    {
+                        internal static partial class NestedMiddleParentClass
+                        {
+                            internal static partial class Nested<T2> where T2 : Class2
+                            {
+                                [LoggerMessage(EventId = 9, Level = LogLevel.Debug, Message = ""M9"")]
+                                public static partial void M9(ILogger logger);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        internal class Class1 { }
+        internal class Class2 { }
+    }
+}";
+            await VerifyAgainstBaselineUsingFile("TestWithNestedClass.generated.txt", testSourceCode);
+        }
+
         private async Task VerifyAgainstBaselineUsingFile(string filename, string testSourceCode)
         {
             string[] expectedLines = await File.ReadAllLinesAsync(Path.Combine("Baselines", filename)).ConfigureAwait(false);
index bb158e9..4991e41 100644 (file)
@@ -250,7 +250,7 @@ namespace Microsoft.Extensions.Logging.Generators.Tests
         }
 
         [Fact]
-        public async Task NestedType()
+        public async Task NestedTypeOK()
         {
             IReadOnlyList<Diagnostic> diagnostics = await RunGenerator(@"
                 partial class C
@@ -263,8 +263,7 @@ namespace Microsoft.Extensions.Logging.Generators.Tests
                 }
             ");
 
-            Assert.Single(diagnostics);
-            Assert.Equal(DiagnosticDescriptors.LoggingMethodInNestedType.Id, diagnostics[0].Id);
+            Assert.Empty(diagnostics);
         }
 
         [Fact]
diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/NestedClassTestsExtensions.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/NestedClassTestsExtensions.cs
new file mode 100644 (file)
index 0000000..4b5b6cd
--- /dev/null
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+    internal static partial class NestedClassTestsExtensions<T> where T : ABC
+    {
+        internal static partial class NestedMiddleParentClass
+        {
+            internal static partial class NestedClass
+            {
+                [LoggerMessage(EventId = 8, Level = LogLevel.Debug, Message = "M8")]
+                public static partial void M8(ILogger logger);
+            }
+        }
+    }
+    
+    internal partial class NonStaticNestedClassTestsExtensions<T> where T : ABC
+    {
+        internal partial class NonStaticNestedMiddleParentClass
+        {
+            internal static partial class NestedClass
+            {
+                [LoggerMessage(EventId = 9, Level = LogLevel.Debug, Message = "M9")]
+                public static partial void M9(ILogger logger);
+            }
+        }
+    }
+    public class ABC {}
+
+    public partial struct NestedStruct
+    {
+        internal static partial class Logger
+        {
+            [LoggerMessage(EventId = 10, Level = LogLevel.Debug, Message = "M10")]
+            public static partial void M10(ILogger logger);
+        }
+    }
+
+    public partial record NestedRecord(string Name, string Address)
+    {
+        internal static partial class Logger
+        {
+            [LoggerMessage(EventId = 11, Level = LogLevel.Debug, Message = "M11")]
+            public static partial void M11(ILogger logger);
+        }
+    }
+
+    public static partial class MultiLevelNestedClass
+    {
+        public partial struct NestedStruct
+        {
+            internal partial record NestedRecord(string Name, string Address)
+            {
+                internal static partial class Logger
+                {
+                    [LoggerMessage(EventId = 12, Level = LogLevel.Debug, Message = "M12")]
+                    public static partial void M12(ILogger logger);
+                }
+            }
+        }
+    }
+}