Skip to content

Commit 167d2f0

Browse files
committed
Merge pull request #9 from sharwell/initial-analyzers
Initial analyzers
2 parents 24fde81 + d535c1c commit 167d2f0

17 files changed

+2439
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
namespace OpenStackNetAnalyzers
2+
{
3+
using System;
4+
using System.Collections.Immutable;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
11+
public class AssertNullAnalyzer : DiagnosticAnalyzer
12+
{
13+
public const string DiagnosticId = "AssertNull";
14+
internal const string Title = "Assert.IsNull and Assert.IsNotNull should only be used with nullable types";
15+
internal const string MessageFormat = "'Assert.{0}' should not be used with the value type '{1}'";
16+
internal const string Category = "OpenStack.Maintainability";
17+
internal const string Description = "Assert.IsNull and Assert.IsNotNull should only be used with nullable types";
18+
19+
private static DiagnosticDescriptor Descriptor =
20+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
21+
22+
private static readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics =
23+
ImmutableArray.Create(Descriptor);
24+
25+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
26+
{
27+
get
28+
{
29+
return _supportedDiagnostics;
30+
}
31+
}
32+
33+
public override void Initialize(AnalysisContext context)
34+
{
35+
context.RegisterSyntaxNodeAction(HandleInvocationExpression, SyntaxKind.InvocationExpression);
36+
}
37+
38+
private void HandleInvocationExpression(SyntaxNodeAnalysisContext context)
39+
{
40+
InvocationExpressionSyntax syntax = (InvocationExpressionSyntax)context.Node;
41+
if (syntax.Expression == null || !(syntax.ArgumentList?.Arguments.Count > 0))
42+
return;
43+
44+
SymbolInfo symbolInfo = context.SemanticModel.GetSymbolInfo(syntax.Expression, context.CancellationToken);
45+
IMethodSymbol methodSymbol = symbolInfo.Symbol as IMethodSymbol;
46+
if (methodSymbol == null)
47+
return;
48+
49+
if (!string.Equals("IsNotNull", methodSymbol.Name, StringComparison.Ordinal)
50+
&& !string.Equals("IsNull", methodSymbol.Name, StringComparison.Ordinal))
51+
{
52+
return;
53+
}
54+
55+
var containingType = methodSymbol.ContainingType;
56+
if (!string.Equals("Assert", containingType?.Name, StringComparison.Ordinal))
57+
return;
58+
59+
if (syntax.ArgumentList.Arguments[0].NameColon != null)
60+
return;
61+
62+
ExpressionSyntax argumentExpression = syntax.ArgumentList.Arguments[0]?.Expression;
63+
if (argumentExpression == null)
64+
return;
65+
66+
TypeInfo typeInfo = context.SemanticModel.GetTypeInfo(argumentExpression);
67+
INamedTypeSymbol namedType = typeInfo.Type as INamedTypeSymbol;
68+
if (namedType == null)
69+
return;
70+
71+
if (!namedType.IsValueType)
72+
{
73+
// don't report the diagnostic for reference types
74+
return;
75+
}
76+
77+
INamedTypeSymbol originalDefinition = namedType.OriginalDefinition;
78+
if (originalDefinition == null
79+
|| originalDefinition.SpecialType == SpecialType.System_Nullable_T
80+
|| originalDefinition.SpecialType == SpecialType.System_ValueType
81+
|| originalDefinition.SpecialType == SpecialType.System_Enum)
82+
{
83+
// don't report the diagnostic for "special" and nullable value types
84+
return;
85+
}
86+
87+
string typeName = namedType.ToMinimalDisplayString(context.SemanticModel, argumentExpression.SpanStart, SymbolDisplayFormat.CSharpErrorMessageFormat);
88+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.GetLocation(), methodSymbol.Name, typeName));
89+
}
90+
}
91+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace OpenStackNetAnalyzers
2+
{
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public class DocumentDelegatingApiCallAnalyzer : DiagnosticAnalyzer
11+
{
12+
public const string DiagnosticId = "DocumentDelegatingApiCall";
13+
internal const string Title = "Document delegating HTTP API call";
14+
internal const string MessageFormat = "Document delegating HTTP API call";
15+
internal const string Category = "OpenStack.Documentation";
16+
internal const string Description = "Document delegating HTTP API call";
17+
18+
private static DiagnosticDescriptor Descriptor =
19+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
20+
21+
private static readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics =
22+
ImmutableArray.Create(Descriptor);
23+
24+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
25+
{
26+
get
27+
{
28+
return _supportedDiagnostics;
29+
}
30+
}
31+
32+
public override void Initialize(AnalysisContext context)
33+
{
34+
context.RegisterSymbolAction(HandleNamedType, SymbolKind.NamedType);
35+
}
36+
37+
private void HandleNamedType(SymbolAnalysisContext context)
38+
{
39+
INamedTypeSymbol symbol = (INamedTypeSymbol)context.Symbol;
40+
if (symbol.TypeKind != TypeKind.Class)
41+
return;
42+
43+
if (!IsDelegatingHttpApiCall(context, symbol))
44+
return;
45+
46+
if (!string.IsNullOrEmpty(symbol.GetDocumentationCommentXml(cancellationToken: context.CancellationToken)))
47+
return;
48+
49+
var locations = symbol.Locations;
50+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, locations.FirstOrDefault(), locations.Skip(1)));
51+
}
52+
53+
private bool IsDelegatingHttpApiCall(SymbolAnalysisContext context, INamedTypeSymbol symbol)
54+
{
55+
while (symbol != null && symbol.SpecialType != SpecialType.System_Object)
56+
{
57+
if (symbol.IsGenericType)
58+
{
59+
var originalDefinition = symbol.OriginalDefinition;
60+
string fullyQualifiedName = originalDefinition.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
61+
if (string.Equals("global::OpenStack.Net.DelegatingHttpApiCall<T>", fullyQualifiedName, StringComparison.Ordinal))
62+
return true;
63+
}
64+
65+
symbol = symbol.BaseType;
66+
}
67+
68+
return false;
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)