|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | + |
| 4 | +using System; |
| 5 | +using System.Collections.Generic; |
| 6 | +using Microsoft.CodeAnalysis; |
| 7 | +using Microsoft.Gen.Logging.Parsing; |
| 8 | +using Xunit; |
| 9 | + |
| 10 | +namespace Microsoft.Gen.Logging.Test; |
| 11 | + |
| 12 | +public class TypeSymbolExtensionsTests |
| 13 | +{ |
| 14 | + private readonly Action<DiagnosticDescriptor, Location?, object |
| 15 | + ?[]?> _diagCallback = (_, __, ___) => { }; |
| 16 | + |
| 17 | + [Theory] |
| 18 | + [InlineData("TestEnumerableInt : List<int>", "TestEnumerableInt", true)] |
| 19 | + [InlineData("TestEnumerable<T> : List<T>", "TestEnumerable<object>", true)] |
| 20 | + [InlineData("NotUsed", "IEnumerable<string>", true)] |
| 21 | + [InlineData("TestClass", "NonEnumerable", false)] |
| 22 | + [InlineData("TestClassDerived : NonEnumerable", "TestClassDerived", false)] |
| 23 | + [InlineData("NotUsed", "bool", false)] |
| 24 | + public void ValidateIsEnumerable(string classDefinition, string typeReference, bool expectedResult) |
| 25 | + { |
| 26 | + // Generate the code |
| 27 | + string source = $@" |
| 28 | + namespace Test |
| 29 | + {{ |
| 30 | + using System.Collections.Generic; |
| 31 | + using Microsoft.Extensions.Logging; |
| 32 | +
|
| 33 | + class {classDefinition} {{ }} |
| 34 | +
|
| 35 | + class NonEnumerable {{ }} |
| 36 | +
|
| 37 | + partial class C |
| 38 | + {{ |
| 39 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 40 | + static partial void M1(ILogger logger, {typeReference} property); |
| 41 | + }} |
| 42 | + }}"; |
| 43 | + |
| 44 | + // Create compilation and extract symbols |
| 45 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 46 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 47 | + |
| 48 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 49 | + |
| 50 | + // Assert |
| 51 | + Assert.NotNull(symbolHolder); |
| 52 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 53 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 54 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 55 | + |
| 56 | + Assert.Equal(expectedResult, parameterSymbol.Type.IsEnumerable(symbolHolder)); |
| 57 | + } |
| 58 | + |
| 59 | + [Theory] |
| 60 | + [InlineData("TestFormattable", "TestFormattable", true)] |
| 61 | + [InlineData("TestFormattable : IFormattable", "TestFormattable", true)] |
| 62 | + [InlineData("TestFormattable", "NonFormattable", false)] |
| 63 | + public void ValidateImplementsIFormattable(string classDefinition, string typeReference, bool expectedResult) |
| 64 | + { |
| 65 | + // Generate the code |
| 66 | + string source = $@" |
| 67 | + namespace Test |
| 68 | + {{ |
| 69 | + using System; |
| 70 | + using Microsoft.Extensions.Logging; |
| 71 | +
|
| 72 | + class {classDefinition} |
| 73 | + {{ |
| 74 | + public string ToString(string? format, IFormatProvider? formatProvider) |
| 75 | + {{ |
| 76 | + throw new NotImplementedException(); |
| 77 | + }} |
| 78 | + }} |
| 79 | +
|
| 80 | + class NonFormattable {{ }} |
| 81 | +
|
| 82 | + partial class C |
| 83 | + {{ |
| 84 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 85 | + static partial void M1(ILogger logger, {typeReference} property); |
| 86 | + }} |
| 87 | + }}"; |
| 88 | + |
| 89 | + // Create compilation and extract symbols |
| 90 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 91 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 92 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 93 | + |
| 94 | + // Assert |
| 95 | + Assert.NotNull(symbolHolder); |
| 96 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 97 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 98 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 99 | + |
| 100 | + Assert.Equal(expectedResult, parameterSymbol.Type.ImplementsIFormattable(symbolHolder)); |
| 101 | + } |
| 102 | + |
| 103 | + [Theory] |
| 104 | + [InlineData("TestConvertible", "TestConvertible", true)] |
| 105 | + [InlineData("TestConvertible : IConvertible", "TestConvertible", true)] |
| 106 | + [InlineData("TestConvertible", "NonConvertible", false)] |
| 107 | + public void ValidateImplementsIConvertible(string classDefinition, string typeReference, bool expectedResult) |
| 108 | + { |
| 109 | + // Generate the code |
| 110 | + string source = $@" |
| 111 | + namespace Test |
| 112 | + {{ |
| 113 | + using System; |
| 114 | + using Microsoft.Extensions.Logging; |
| 115 | +
|
| 116 | + class {classDefinition} |
| 117 | + {{ |
| 118 | + public string ToString(IFormatProvider? formatProvider) |
| 119 | + {{ |
| 120 | + throw new NotImplementedException(); |
| 121 | + }} |
| 122 | + }} |
| 123 | +
|
| 124 | + class NonConvertible {{ }} |
| 125 | +
|
| 126 | + partial class C |
| 127 | + {{ |
| 128 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 129 | + static partial void M1(ILogger logger, {typeReference} property); |
| 130 | + }} |
| 131 | + }}"; |
| 132 | + |
| 133 | + // Create compilation and extract symbols |
| 134 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 135 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 136 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 137 | + |
| 138 | + // Assert |
| 139 | + Assert.NotNull(symbolHolder); |
| 140 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 141 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 142 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 143 | + |
| 144 | + Assert.Equal(expectedResult, parameterSymbol.Type.ImplementsIConvertible(symbolHolder)); |
| 145 | + } |
| 146 | + |
| 147 | + [Theory] |
| 148 | + [InlineData("TestISpanFormattable : ISpanFormattable", "TestISpanFormattable", true)] |
| 149 | + [InlineData("TestISpanFormattable", "NonConvertible", false)] |
| 150 | + public void ValidateImplementsISpanFormattable(string classDefinition, string typeReference, bool expectedResult) |
| 151 | + { |
| 152 | + // Generate the code |
| 153 | + string source = $@" |
| 154 | + namespace Test |
| 155 | + {{ |
| 156 | + using System; |
| 157 | + using Microsoft.Extensions.Logging; |
| 158 | +
|
| 159 | + class {classDefinition} |
| 160 | + {{ |
| 161 | + public string ToString(string? format, IFormatProvider? formatProvider) |
| 162 | + {{ |
| 163 | + throw new NotImplementedException(); |
| 164 | + }} |
| 165 | +
|
| 166 | + public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider) |
| 167 | + {{ |
| 168 | + throw new NotImplementedException(); |
| 169 | + }} |
| 170 | + }} |
| 171 | +
|
| 172 | + class NonSpanFormattable {{ }} |
| 173 | +
|
| 174 | + partial class C |
| 175 | + {{ |
| 176 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 177 | + static partial void M1(ILogger logger, {typeReference} property); |
| 178 | + }} |
| 179 | + }}"; |
| 180 | + |
| 181 | + // Create compilation and extract symbols |
| 182 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 183 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 184 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 185 | + |
| 186 | + // Assert |
| 187 | + Assert.NotNull(symbolHolder); |
| 188 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 189 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 190 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 191 | + |
| 192 | + Assert.Equal(expectedResult, parameterSymbol.Type.ImplementsISpanFormattable(symbolHolder)); |
| 193 | + } |
| 194 | + |
| 195 | + [Theory] |
| 196 | + [InlineData("string", true)] |
| 197 | + [InlineData("bool", true)] |
| 198 | + [InlineData("int", true)] |
| 199 | + [InlineData("NonSpecialType", false)] |
| 200 | + [InlineData("TestClassDerived", false)] |
| 201 | + [InlineData("TimeSpan", false)] |
| 202 | + [InlineData("Uri", false)] |
| 203 | + public void ValidateIsSpecialType(string typeReference, bool expectedResult) |
| 204 | + { |
| 205 | + // Generate the code |
| 206 | + string source = $@" |
| 207 | + namespace Test |
| 208 | + {{ |
| 209 | + using System.Collections.Generic; |
| 210 | + using Microsoft.Extensions.Logging; |
| 211 | +
|
| 212 | + class NonSpecialType {{ }} |
| 213 | +
|
| 214 | + class TestClassDerived: NonSpecialType {{ }} |
| 215 | +
|
| 216 | + partial class C |
| 217 | + {{ |
| 218 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 219 | + static partial void M1(ILogger logger, {typeReference} property); |
| 220 | + }} |
| 221 | + }}"; |
| 222 | + |
| 223 | + // Create compilation and extract symbols |
| 224 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 225 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 226 | + |
| 227 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 228 | + |
| 229 | + // Assert |
| 230 | + Assert.NotNull(symbolHolder); |
| 231 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 232 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 233 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 234 | + |
| 235 | + Assert.Equal(expectedResult, parameterSymbol.Type.IsSpecialType(symbolHolder)); |
| 236 | + } |
| 237 | + |
| 238 | + [Theory] |
| 239 | + [InlineData("ToString", true)] |
| 240 | + [InlineData("RandomMethod", false)] |
| 241 | + [InlineData("ToooooString", false)] |
| 242 | + public void ValidateHasCustomToString(string methodName, bool expectedResult) |
| 243 | + { |
| 244 | + // Generate the code |
| 245 | + string source = $@" |
| 246 | + namespace Test |
| 247 | + {{ |
| 248 | + using System; |
| 249 | + using Microsoft.Extensions.Logging; |
| 250 | +
|
| 251 | + class Test |
| 252 | + {{ |
| 253 | + public override string {methodName}() |
| 254 | + {{ |
| 255 | + throw new NotImplementedException(); |
| 256 | + }} |
| 257 | + }} |
| 258 | +
|
| 259 | + class NonConvertible {{ }} |
| 260 | +
|
| 261 | + partial class C |
| 262 | + {{ |
| 263 | + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] |
| 264 | + static partial void M1(ILogger logger, Test property); |
| 265 | + }} |
| 266 | + }}"; |
| 267 | + |
| 268 | + // Create compilation and extract symbols |
| 269 | + Compilation compilation = CompilationHelper.CreateCompilation(source); |
| 270 | + SymbolHolder? symbolHolder = SymbolLoader.LoadSymbols(compilation, _diagCallback); |
| 271 | + IEnumerable<ISymbol> methodSymbols = compilation.GetSymbolsWithName("M1", SymbolFilter.Member); |
| 272 | + |
| 273 | + // Assert |
| 274 | + Assert.NotNull(symbolHolder); |
| 275 | + ISymbol symbol = Assert.Single(methodSymbols); |
| 276 | + var methodSymbol = Assert.IsAssignableFrom<IMethodSymbol>(symbol); |
| 277 | + var parameterSymbol = Assert.Single(methodSymbol.Parameters, p => p.Name == "property"); |
| 278 | + |
| 279 | + Assert.Equal(expectedResult, parameterSymbol.Type.HasCustomToString()); |
| 280 | + } |
| 281 | + |
| 282 | + [Fact] |
| 283 | + public void GetPossiblyNullWrappedType_NullableT_ReturnsT() |
| 284 | + { |
| 285 | + Compilation compilation = CompilationHelper.CreateCompilation("public class TestClass { }"); |
| 286 | + INamedTypeSymbol nullableType = compilation.GetSpecialType(SpecialType.System_Nullable_T); |
| 287 | + INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32); |
| 288 | + INamedTypeSymbol nullableIntType = nullableType.Construct(intType); |
| 289 | + var result = nullableIntType.GetPossiblyNullWrappedType(); |
| 290 | + Assert.Equal(intType, result); |
| 291 | + } |
| 292 | + |
| 293 | + [Fact] |
| 294 | + public void GetPossiblyNullWrappedType_ListT_ReturnsListT() |
| 295 | + { |
| 296 | + Compilation compilation = CompilationHelper.CreateCompilation("using System.Collections.Generic; public class TestClass { }"); |
| 297 | + INamedTypeSymbol listType = compilation.GetTypeByMetadataName("System.Collections.Generic.List`1")!; |
| 298 | + INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32); |
| 299 | + INamedTypeSymbol listIntType = listType.Construct(intType); |
| 300 | + var result = listIntType.GetPossiblyNullWrappedType(); |
| 301 | + Assert.Equal(listIntType, result); |
| 302 | + } |
| 303 | + |
| 304 | + [Fact] |
| 305 | + public void GetPossiblyNullWrappedType_T_ReturnsT() |
| 306 | + { |
| 307 | + Compilation compilation = CompilationHelper.CreateCompilation("public class TestClass { }"); |
| 308 | + INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32); |
| 309 | + var result = intType.GetPossiblyNullWrappedType(); |
| 310 | + Assert.Equal(intType, result); |
| 311 | + } |
| 312 | +} |
0 commit comments