Skip to content

Commit 0504d4b

Browse files
committed
Add anonymous object desctructuring analyzer
1 parent a8060cb commit 0504d4b

10 files changed

+183
-7
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,19 @@ Compliant Solution:
5858
```csharp
5959
Log.Error("Disk quota {Quota} MB exceeded by {User}", quota, user);
6060
```
61+
62+
### Anonymous objects must be destructured
63+
64+
Noncompliant Code Examples:
65+
```csharp
66+
Log.Error("Processed {Position}", new { x = 4, y = 2});
67+
```
68+
69+
Compliant Solution:
70+
```csharp
71+
Log.Error("Processed {@Position}", new { x = 4, y = 2});
72+
```
73+
74+
## Credits
75+
76+
Inpired by [SerilogAnalyzer](https://github.com/Suchiman/SerilogAnalyzer)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.Linq;
2+
3+
using JetBrains.ReSharper.Feature.Services.Daemon;
4+
using JetBrains.ReSharper.Psi;
5+
using JetBrains.ReSharper.Psi.CSharp.Tree;
6+
using JetBrains.ReSharper.Psi.Util;
7+
8+
using ReSharper.Structured.Logging.Extensions;
9+
using ReSharper.Structured.Logging.Highlighting;
10+
using ReSharper.Structured.Logging.Serilog.Parsing;
11+
12+
namespace ReSharper.Structured.Logging.Analyzer
13+
{
14+
[ElementProblemAnalyzer(typeof(IInvocationExpression))]
15+
public class AnonymousTypeDestructureAnalyzer : ElementProblemAnalyzer<IInvocationExpression>
16+
{
17+
private readonly MessageTemplateParser _messageTemplateParser;
18+
19+
public AnonymousTypeDestructureAnalyzer(MessageTemplateParser messageTemplateParser)
20+
{
21+
_messageTemplateParser = messageTemplateParser;
22+
}
23+
24+
protected override void Run(
25+
IInvocationExpression element,
26+
ElementProblemAnalyzerData data,
27+
IHighlightingConsumer consumer)
28+
{
29+
var templateArgument = element.GetTemplateArgument();
30+
if (templateArgument == null)
31+
{
32+
return;
33+
}
34+
35+
var anonymousObjectsArguments = element.ArgumentList.Arguments
36+
.Where(a => a.Value is IAnonymousObjectCreationExpression)
37+
.ToArray();
38+
if (anonymousObjectsArguments.Length == 0)
39+
{
40+
return;
41+
}
42+
43+
var stringLiteral = StringLiteralAltererUtil.TryCreateStringLiteralByExpression(templateArgument.Value);
44+
if (stringLiteral == null)
45+
{
46+
return;
47+
}
48+
49+
var messageTemplate = _messageTemplateParser.Parse(stringLiteral.Expression.GetUnquotedText());
50+
if (messageTemplate.NamedProperties == null)
51+
{
52+
return;
53+
}
54+
55+
var templateArgumentIndex = templateArgument.IndexOf();
56+
foreach (var argument in anonymousObjectsArguments)
57+
{
58+
var index = argument.IndexOf() - templateArgumentIndex - 1;
59+
if (index < messageTemplate.NamedProperties.Length)
60+
{
61+
var namedProperty = messageTemplate.NamedProperties[index];
62+
if (namedProperty.Destructuring != Destructuring.Default)
63+
{
64+
continue;
65+
}
66+
67+
consumer.AddHighlighting(new AnonymousObjectDestructuringWarning(stringLiteral.GetTokenDocumentRange(namedProperty)));
68+
}
69+
}
70+
}
71+
}
72+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using JetBrains.DocumentModel;
2+
using JetBrains.ReSharper.Feature.Services.Daemon;
3+
using JetBrains.ReSharper.Psi.CSharp;
4+
5+
using ReSharper.Structured.Logging.Highlighting;
6+
7+
[assembly:
8+
RegisterConfigurableSeverity(AnonymousObjectDestructuringWarning.SeverityId, null, HighlightingGroupIds.CompilerWarnings,
9+
AnonymousObjectDestructuringWarning.Message, AnonymousObjectDestructuringWarning.Message,
10+
Severity.WARNING)]
11+
12+
namespace ReSharper.Structured.Logging.Highlighting
13+
{
14+
[ConfigurableSeverityHighlighting(
15+
SeverityId,
16+
CSharpLanguage.Name,
17+
OverlapResolve = OverlapResolveKind.WARNING,
18+
ToolTipFormatString = Message)]
19+
public class AnonymousObjectDestructuringWarning : IHighlighting
20+
{
21+
public const string Message = "Anonymous objects must be destructured";
22+
23+
public const string SeverityId = "AnonymousObjectDestructuringProblem";
24+
25+
private readonly DocumentRange _documentRange;
26+
27+
public AnonymousObjectDestructuringWarning(DocumentRange documentRange)
28+
{
29+
_documentRange = documentRange;
30+
}
31+
32+
public string ErrorStripeToolTip => ToolTip;
33+
34+
public string ToolTip => Message;
35+
36+
public DocumentRange CalculateRange()
37+
{
38+
return _documentRange;
39+
}
40+
41+
public bool IsValid()
42+
{
43+
return _documentRange.IsValid();
44+
}
45+
}
46+
}

src/ReSharper.Structured.Logging/Highlighting/TemplateFormatItemAndMatchingArgumentHighlighter.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,6 @@ private static void HighlightByArgument(
142142
consumer.ConsumeHighlighting(
143143
HighlightingAttributeIds.USAGE_OF_ELEMENT_UNDER_CURSOR,
144144
templateArgument.GetTokenDocumentRange(property));
145-
consumer.ConsumeHighlighting(
146-
HighlightingAttributeIds.USAGE_OF_ELEMENT_UNDER_CURSOR,
147-
selectedArgument.GetDocumentRange());
148145
}
149146
else if (positionalProperties != null)
150147
{
@@ -164,11 +161,11 @@ private static void HighlightByArgument(
164161
HighlightingAttributeIds.USAGE_OF_ELEMENT_UNDER_CURSOR,
165162
templateArgument.GetTokenDocumentRange(property));
166163
}
167-
168-
consumer.ConsumeHighlighting(
169-
HighlightingAttributeIds.USAGE_OF_ELEMENT_UNDER_CURSOR,
170-
selectedArgument.GetDocumentRange());
171164
}
165+
166+
consumer.ConsumeHighlighting(
167+
HighlightingAttributeIds.USAGE_OF_ELEMENT_UNDER_CURSOR,
168+
selectedArgument.GetDocumentRange());
172169
}
173170

174171
private static void HighlightByNamedPlaceholder(

src/ReSharper.Structured.Logging/ReSharper.Structured.Logging.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,13 @@
324324
</Reference>
325325
</ItemGroup>
326326
<ItemGroup>
327+
<Compile Include="Analyzer\AnonymousTypeDestructureAnalyzer.cs" />
327328
<Compile Include="Analyzer\CompileTimeConstantTemplateAnalyzer.cs" />
328329
<Compile Include="Analyzer\CorrectExceptionPassingAnalyzer.cs" />
329330
<Compile Include="Analyzer\DuplicatePropertiesTemplateAnalyzer.cs" />
330331
<Compile Include="Analyzer\TemplateFormatAnalyzer.cs" />
331332
<Compile Include="Extensions\PsiExtensions.cs" />
333+
<Compile Include="Highlighting\AnonymousObjectWithoutDestructuringWarning.cs" />
332334
<Compile Include="Highlighting\DuplicateTemplatePropertyWarning.cs" />
333335
<Compile Include="Highlighting\ExceptionPassedAsTemplateArgumentWarning.cs" />
334336
<Compile Include="Highlighting\TemplateFormatStringArgumentIsNotUsedWarning.cs" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Serilog;
2+
3+
namespace ConsoleApp
4+
{
5+
public static class Program
6+
{
7+
public static void Main()
8+
{
9+
Log.Logger.Information("{MyProperty}", new { Test = 1 });
10+
}
11+
}
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Serilog;
2+
3+
namespace ConsoleApp
4+
{
5+
public static class Program
6+
{
7+
public static void Main()
8+
{
9+
Log.Logger.Information("||{MyProperty}|(0)|(1)", new { Test = 1 });
10+
}
11+
}
12+
}
13+
14+
---------------------------------------------------------
15+
(0): ReSharper Format String Item:
16+
(1): ReSharper Warning: Anonymous objects must be destructured
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using NUnit.Framework;
2+
3+
namespace ReSharper.Structured.Logging.Tests.Analyzer
4+
{
5+
public class AnonymousTypeDestructureAnalyzerTests : MessageTemplateAnalyzerTestBase
6+
{
7+
[Test]
8+
public void TestSerilogAnonymousTypeWithoutDestructure()
9+
{
10+
DoNamedTest2();
11+
}
12+
}
13+
}

test/src/Analyzer/MessageTemplateTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ protected override bool HighlightingPredicate(
2424
|| highlighting is TemplateFormatStringUnexistingArgumentWarning
2525
|| highlighting is StringEscapeCharacterHighlighting
2626
|| highlighting is DuplicateTemplatePropertyWarning
27+
|| highlighting is AnonymousObjectDestructuringWarning
2728
|| highlighting is ExceptionPassedAsTemplateArgumentWarning;
2829
}
2930
}

test/src/ReSharper.Structured.Logging.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@
322322
</Reference>
323323
</ItemGroup>
324324
<ItemGroup>
325+
<Compile Include="Analyzer\AnonymousTypeDestructureAnalyzerTests.cs" />
325326
<Compile Include="Analyzer\CorrectExceptionPassingAnalyzerTests.cs" />
326327
<Compile Include="Analyzer\DuplicatePropertiesTemplateAnalyzerTests.cs" />
327328
<Compile Include="Analyzer\MessageTemplateTests.cs" />

0 commit comments

Comments
 (0)