Skip to content

Commit

Permalink
feature: Actionresult<TValue> check and more System.Object checks (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpvreony authored Mar 20, 2024
1 parent db8ccbf commit df95380
Show file tree
Hide file tree
Showing 12 changed files with 887 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,129 @@
// This file is licensed to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Immutable;
using Dhgms.GripeWithRoslyn.Analyzer.CodeCracker.Extensions;
using Dhgms.GripeWithRoslyn.Analyzer.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.AspNetCore
{
internal class ApiShouldUseGenericActionResultAnalyzer
/// <summary>
/// Analyzer for checking an API call uses ActionResult{T} instead of non-generic version, non-descript interfaces, or other return types.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ApiShouldUseGenericActionResultAnalyzer : DiagnosticAnalyzer
{
internal const string Title = "Task<ActionResult<TValue>> should be used as the result type in an API.";

private const string MessageFormat = Title;

private const string Category = SupportedCategories.Design;

private readonly DiagnosticDescriptor _rule;

/// <summary>
/// Initializes a new instance of the <see cref="ApiShouldUseGenericActionResultAnalyzer"/> class.
/// </summary>
public ApiShouldUseGenericActionResultAnalyzer()
{
_rule = new DiagnosticDescriptor(
DiagnosticIdsHelper.ApiShouldUseGenericActionResult,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: DiagnosticResultDescriptionFactory.ApiShouldUseGenericActionResult());
}

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeMethodDeclarationExpression, SyntaxKind.MethodDeclaration);
}

private void AnalyzeMethodDeclarationExpression(SyntaxNodeAnalysisContext context)
{
var node = context.Node;

var methodDeclarationSyntax = (MethodDeclarationSyntax)node;

// Check if the class is an API controller
var classDeclarationSyntax = methodDeclarationSyntax.GetAncestor<ClassDeclarationSyntax>();
if (classDeclarationSyntax == null)
{
return;
}

if (!classDeclarationSyntax.HasImplementedAnyOfType(
["global::System.System.Web.Http.ApiController"],
null,
context.SemanticModel))
{
return;
}

// Check if the method is an API method
foreach (var modifier in methodDeclarationSyntax.Modifiers)
{
var kind = modifier.Kind();

// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (kind)
{
case SyntaxKind.PublicKeyword:
break;
case SyntaxKind.StaticKeyword:
return;
}
}

// Get the return type of the method
var returnType = methodDeclarationSyntax.ReturnType;
var typeInfo = ModelExtensions.GetTypeInfo(context.SemanticModel, returnType);

if (typeInfo.Type == null)
{
return;
}

if (typeInfo.Type is not INamedTypeSymbol namedTypeSymbol)
{
return;
}

var typeFullName = namedTypeSymbol.GetFullName();

if (typeFullName.Equals("global::System.Threading.Task", StringComparison.Ordinal))
{
var typeArguments = namedTypeSymbol.TypeArguments;
var typeArgument = typeArguments[0];
var argFullName = typeArgument.GetFullName();
if (typeArgument is not INamedTypeSymbol typeArgNamedTypeSymbol)
{
return;
}

if (argFullName.Equals("global::Microsoft.AspNetCore.Mvc.ActionResult", StringComparison.Ordinal))
{
if (typeArgNamedTypeSymbol.MetadataName.Equals("ActionResult`1"))
{
return;
}
}
}

context.ReportDiagnostic(Diagnostic.Create(_rule, node.GetLocation()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved.
// This file is licensed to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Immutable;
using Dhgms.GripeWithRoslyn.Analyzer.CodeCracker.Extensions;
using Dhgms.GripeWithRoslyn.Analyzer.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Runtime
{
/// <summary>
/// Analyzer to ensure <see cref="object"/> is not used in a field declaration.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DoNotUseObjectAsFieldTypeAnalyzer : DiagnosticAnalyzer
{
internal const string Title = "Do not use Object in a field declaration.";

private const string MessageFormat = Title;

private const string Category = SupportedCategories.Design;

private readonly DiagnosticDescriptor _rule;

/// <summary>
/// Initializes a new instance of the <see cref="DoNotUseObjectAsFieldTypeAnalyzer"/> class.
/// </summary>
public DoNotUseObjectAsFieldTypeAnalyzer()
{
_rule = new DiagnosticDescriptor(
DiagnosticIdsHelper.DoNotUseObjectAsFieldType,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: DiagnosticResultDescriptionFactory.DoNotUseObjectAsFieldType());
}

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

/// <inheritdoc />
public sealed override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeParameter, SyntaxKind.FieldDeclaration);
}

private void AnalyzeParameter(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
var fieldDeclarationSyntax = (FieldDeclarationSyntax)syntaxNodeAnalysisContext.Node;

var semanticModel = syntaxNodeAnalysisContext.SemanticModel;
var variableDeclarationSyntax = fieldDeclarationSyntax.Declaration;
var type = variableDeclarationSyntax.Type;
if (type == null)
{
return;
}

var baseTypeInfo = semanticModel.GetTypeInfo(type);
var baseTypeSymbol = baseTypeInfo.Type;

if (baseTypeSymbol == null)
{
return;
}

var fullName = baseTypeSymbol.GetFullName(true);
if (fullName == "global::System.Object")
{
syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(_rule, type.GetLocation()));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved.
// This file is licensed to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Immutable;
using Dhgms.GripeWithRoslyn.Analyzer.CodeCracker.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Runtime
{
/// <summary>
/// Analyzer to ensure <see cref="object"/> is not used in a variable declaration.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DoNotUseObjectAsLocalVariableTypeAnalyzer : DiagnosticAnalyzer
{
internal const string Title = "Do not use Object in a variable declaration.";

private const string MessageFormat = Title;

private const string Category = SupportedCategories.Design;

private readonly DiagnosticDescriptor _rule;

/// <summary>
/// Initializes a new instance of the <see cref="DoNotUseObjectAsLocalVariableTypeAnalyzer"/> class.
/// </summary>
public DoNotUseObjectAsLocalVariableTypeAnalyzer()
{
_rule = new DiagnosticDescriptor(
DiagnosticIdsHelper.DoNotUseObjectAsLocalVariableType,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: DiagnosticResultDescriptionFactory.DoNotUseObjectAsLocalVariableType());
}

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

/// <inheritdoc />
public sealed override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeParameter, SyntaxKind.VariableDeclaration);
}

private void AnalyzeParameter(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
var parameterSyntax = (VariableDeclarationSyntax)syntaxNodeAnalysisContext.Node;

var semanticModel = syntaxNodeAnalysisContext.SemanticModel;
var type = parameterSyntax.Type;
if (type == null)
{
return;
}

var baseTypeInfo = semanticModel.GetTypeInfo(type);
var baseTypeSymbol = baseTypeInfo.Type;

if (baseTypeSymbol == null)
{
return;
}

var fullName = baseTypeSymbol.GetFullName(true);
if (fullName == "global::System.Object")
{
syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(_rule, type.GetLocation()));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved.
// This file is licensed to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Immutable;
using Dhgms.GripeWithRoslyn.Analyzer.CodeCracker.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Runtime
{
/// <summary>
/// Analyzer to ensure <see cref="object"/> is not used in a parameter declaration.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DoNotUseObjectAsPropertyTypeAnalyzer : DiagnosticAnalyzer
{
internal const string Title = "Do not use Object in a property declaration.";

private const string MessageFormat = Title;

private const string Category = SupportedCategories.Design;

private readonly DiagnosticDescriptor _rule;

/// <summary>
/// Initializes a new instance of the <see cref="DoNotUseObjectAsPropertyTypeAnalyzer"/> class.
/// </summary>
public DoNotUseObjectAsPropertyTypeAnalyzer()
{
_rule = new DiagnosticDescriptor(
DiagnosticIdsHelper.DoNotUseObjectAsPropertyType,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: DiagnosticResultDescriptionFactory.DoNotUseObjectAsPropertyType());
}

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

/// <inheritdoc />
public sealed override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeParameter, SyntaxKind.PropertyDeclaration);
}

private void AnalyzeParameter(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
var parameterSyntax = (PropertyDeclarationSyntax)syntaxNodeAnalysisContext.Node;

var semanticModel = syntaxNodeAnalysisContext.SemanticModel;
var type = parameterSyntax.Type;
if (type == null)
{
return;
}

var baseTypeInfo = semanticModel.GetTypeInfo(type);
var baseTypeSymbol = baseTypeInfo.Type;

if (baseTypeSymbol == null)
{
return;
}

var fullName = baseTypeSymbol.GetFullName(true);
if (fullName == "global::System.Object")
{
syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(_rule, type.GetLocation()));
}
}
}
}
Loading

0 comments on commit df95380

Please sign in to comment.