Skip to content

Commit 202ed8d

Browse files
committed
Add ImplementBuilderPatternAnalyzer analyzer and code fix
This analyzer acts as a refactoring operation to add a "builder" method for a property of a type that extends ExtensibleJsonObject. Fixes #2
1 parent 3445611 commit 202ed8d

3 files changed

Lines changed: 385 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
namespace OpenStackNetAnalyzers
2+
{
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
12+
public class ImplementBuilderPatternAnalyzer : DiagnosticAnalyzer
13+
{
14+
public const string DiagnosticId = "ImplementBuilderPattern";
15+
internal const string Title = "Implement builder pattern (refactoring)";
16+
internal const string MessageFormat = "ImplementBuilderPattern";
17+
internal const string Category = "OpenStack.Refactoring";
18+
internal const string Description = "Implement builder pattern (refactoring)";
19+
20+
private static DiagnosticDescriptor Descriptor =
21+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Hidden, isEnabledByDefault: true, description: Description);
22+
23+
private static readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics =
24+
ImmutableArray.Create(Descriptor);
25+
26+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
27+
{
28+
get
29+
{
30+
return _supportedDiagnostics;
31+
}
32+
}
33+
34+
public override void Initialize(AnalysisContext context)
35+
{
36+
context.RegisterSymbolAction(HandleNamedType, SymbolKind.NamedType);
37+
}
38+
39+
private void HandleNamedType(SymbolAnalysisContext context)
40+
{
41+
INamedTypeSymbol symbol = (INamedTypeSymbol)context.Symbol;
42+
if (symbol.TypeKind != TypeKind.Class)
43+
return;
44+
45+
if (!IsExtensibleJsonObject(context, symbol))
46+
return;
47+
48+
foreach (var propertySymbol in symbol.GetMembers().OfType<IPropertySymbol>())
49+
{
50+
if (propertySymbol.SetMethod != null)
51+
continue;
52+
53+
var locations = propertySymbol.Locations;
54+
if (locations.IsDefaultOrEmpty)
55+
continue;
56+
57+
var tree = locations[0].SourceTree;
58+
if (tree == null)
59+
continue;
60+
61+
var root = tree.GetRoot(context.CancellationToken);
62+
var node = root.FindNode(locations[0].SourceSpan, getInnermostNodeForTie: true);
63+
var propertySyntax = node.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
64+
var getter = propertySyntax.AccessorList?.Accessors.FirstOrDefault(i => i.Keyword.IsKind(SyntaxKind.GetKeyword));
65+
if (getter?.Body?.Statements.Count == 1)
66+
{
67+
ReturnStatementSyntax returnStatement = getter?.Body?.Statements.FirstOrDefault() as ReturnStatementSyntax;
68+
ExpressionSyntax returnExpression = returnStatement.Expression;
69+
if (returnExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
70+
{
71+
MemberAccessExpressionSyntax memberAccess = (MemberAccessExpressionSyntax)returnExpression;
72+
if (!memberAccess.Expression.IsKind(SyntaxKind.ThisExpression))
73+
continue;
74+
}
75+
76+
SymbolInfo symbolInfo = context.Compilation.GetSemanticModel(tree).GetSymbolInfo(returnExpression, context.CancellationToken);
77+
IFieldSymbol fieldSymbol = symbolInfo.Symbol as IFieldSymbol;
78+
if (fieldSymbol.ContainingType != propertySymbol.ContainingType)
79+
continue;
80+
81+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, locations.FirstOrDefault(), locations.Skip(1)));
82+
}
83+
}
84+
}
85+
86+
private bool IsExtensibleJsonObject(SymbolAnalysisContext context, INamedTypeSymbol symbol)
87+
{
88+
while (symbol != null && symbol.SpecialType != SpecialType.System_Object)
89+
{
90+
if (string.Equals("global::OpenStack.ObjectModel.ExtensibleJsonObject", symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), StringComparison.Ordinal))
91+
return true;
92+
93+
symbol = symbol.BaseType;
94+
}
95+
96+
return false;
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)