Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TinyValidations.SourceGen.Analysis.Rules;
using TinyValidations.SourceGen.Analysis.RuleInvocations;
using TinyValidations.SourceGen.Model;

namespace TinyValidations.SourceGen.Analysis.Declarations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TinyValidations.SourceGen.Analysis.Rules
namespace TinyValidations.SourceGen.Analysis.RuleInvocations
{
internal sealed class AnalyzedMemberAccess
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TinyValidations.SourceGen.Model;

namespace TinyValidations.SourceGen.Analysis.RuleInvocations
{
internal sealed class CustomRuleAnalyzer
{
public RuleAnalysisResult Analyze(
SemanticModel semanticModel,
SimpleNameSyntax methodName,
INamedTypeSymbol commandType)
{
if (!(methodName is GenericNameSyntax genericName))
{
return RuleAnalysisIssue.InvalidCustomRule(methodName, methodName.ToString());
}

if (!HasSingleTypeArgument(genericName))
{
return RuleAnalysisIssue.InvalidCustomRule(genericName, genericName.ToString());
}

var typeSyntax = genericName.TypeArgumentList.Arguments[0];
var typeSymbol = semanticModel.GetTypeInfo(typeSyntax).Type;
if (!IsValidCustomRule(typeSymbol, commandType))
{
return RuleAnalysisIssue.InvalidCustomRule(typeSyntax, typeSyntax.ToString());
}

var customRuleType = GetTypeName(typeSyntax, typeSymbol);
return CreateRule(customRuleType);
}

private static RuleAnalysisResult CreateRule(string customRuleType)
{
return RuleAnalysisResult.ForRule(new RuleDefinition(
RuleKind.Use,
string.Empty,
string.Empty,
string.Empty,
string.Empty,
customRuleType));
}

private static bool HasSingleTypeArgument(GenericNameSyntax genericName)
{
return genericName.TypeArgumentList.Arguments.Count == 1;
}

private static string GetTypeName(TypeSyntax typeSyntax, ITypeSymbol? typeSymbol)
{
if (typeSymbol == null)
{
return typeSyntax.ToString();
}

return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}

private static bool IsValidCustomRule(ITypeSymbol? typeSymbol, INamedTypeSymbol commandType)
{
if (!(typeSymbol is INamedTypeSymbol namedType))
{
return false;
}

foreach (var candidate in namedType.AllInterfaces)
{
if (IsAsyncValidationRule(candidate, commandType))
{
return true;
}
}

return false;
}

private static bool IsAsyncValidationRule(INamedTypeSymbol candidate, INamedTypeSymbol commandType)
{
if (!IsTinyValidationsRule(candidate))
{
return false;
}

if (!HasSingleTypeArgument(candidate))
{
return false;
}

return SymbolEqualityComparer.Default.Equals(candidate.TypeArguments[0], commandType);
}

private static bool IsTinyValidationsRule(INamedTypeSymbol candidate)
{
if (candidate.ContainingNamespace.ToDisplayString() != "TinyValidations")
{
return false;
}

return candidate.Name == "IAsyncValidationRule";
}

private static bool HasSingleTypeArgument(INamedTypeSymbol candidate)
{
return candidate.TypeArguments.Length == 1;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace TinyValidations.SourceGen.Analysis.Rules
namespace TinyValidations.SourceGen.Analysis.RuleInvocations
{
internal sealed class MemberAccessAnalyzer
{
public AnalyzedMemberAccess? Analyze(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression)
public AnalyzedMemberAccess? Analyze(ExpressionSyntax expression)
{
if (!(expression is LambdaExpressionSyntax lambda))
{
Expand Down Expand Up @@ -58,10 +59,10 @@ private static bool HasSingleParameter(ParenthesizedLambdaExpressionSyntax lambd
return lambda.ParameterList.Parameters.Count == 1;
}

private static List<string> ReadMembers(Microsoft.CodeAnalysis.SyntaxNode body, string parameterName)
private static List<string> ReadMembers(SyntaxNode body, string parameterName)
{
var members = new List<string>();
ExpressionSyntax? current = body as Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax;
ExpressionSyntax? current = body as ExpressionSyntax;

while (current is MemberAccessExpressionSyntax memberAccess)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TinyValidations.SourceGen.Model;

namespace TinyValidations.SourceGen.Analysis.RuleInvocations
{
internal sealed class MemberRuleAnalyzer
{
private readonly MemberAccessAnalyzer _memberAccessAnalyzer = new MemberAccessAnalyzer();
private readonly RuleArgumentAnalyzer _argumentAnalyzer = new RuleArgumentAnalyzer();

public RuleAnalysisResult Analyze(RuleKind kind, InvocationExpressionSyntax invocation)
{
if (!HasSelector(invocation))
{
return RuleAnalysisIssue.UnsupportedSelector(invocation, invocation.ToString());
}

var selectorArgument = invocation.ArgumentList.Arguments[0];
var member = AnalyzeSelector(selectorArgument);
if (member == null)
{
return RuleAnalysisIssue.UnsupportedSelector(
selectorArgument,
selectorArgument.Expression.ToString());
}

if (HasUnsupportedArgument(kind, invocation))
{
return RuleAnalysisIssue.UnsupportedArgument(invocation, invocation.ToString());
}

return CreateRule(kind, invocation, member);
}

private RuleAnalysisResult CreateRule(
RuleKind kind,
InvocationExpressionSyntax invocation,
AnalyzedMemberAccess member)
{
var argument = _argumentAnalyzer.GetRuleArgument(kind, invocation);
var message = _argumentAnalyzer.GetMessage(kind, invocation);

return RuleAnalysisResult.ForRule(new RuleDefinition(
kind,
member.Path,
member.Access,
argument,
message,
string.Empty));
}

private AnalyzedMemberAccess? AnalyzeSelector(ArgumentSyntax selectorArgument)
{
return _memberAccessAnalyzer.Analyze(selectorArgument.Expression);
}

private static bool HasSelector(InvocationExpressionSyntax invocation)
{
return invocation.ArgumentList.Arguments.Count > 0;
}

private static bool HasUnsupportedArgument(RuleKind kind, InvocationExpressionSyntax invocation)
{
if (HasUnsupportedValueArgument(kind, invocation))
{
return true;
}

return HasUnsupportedMessageArgument(kind, invocation);
}

private static bool HasUnsupportedValueArgument(RuleKind kind, InvocationExpressionSyntax invocation)
{
var valueArgumentIndex = RuleShape.ValueArgumentIndex(kind);
if (valueArgumentIndex < 0)
{
return false;
}

return !IsSupportedArgument(invocation, valueArgumentIndex);
}

private static bool HasUnsupportedMessageArgument(RuleKind kind, InvocationExpressionSyntax invocation)
{
var messageArgumentIndex = RuleShape.MessageArgumentIndex(kind);
if (!HasArgument(invocation, messageArgumentIndex))
{
return false;
}

return !IsSupportedArgument(invocation, messageArgumentIndex);
}

private static bool IsSupportedArgument(InvocationExpressionSyntax invocation, int argumentIndex)
{
if (!HasArgument(invocation, argumentIndex))
{
return false;
}

return invocation.ArgumentList.Arguments[argumentIndex].Expression is LiteralExpressionSyntax;
}

private static bool HasArgument(InvocationExpressionSyntax invocation, int argumentIndex)
{
return invocation.ArgumentList.Arguments.Count > argumentIndex;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TinyValidations.SourceGen.Model;

namespace TinyValidations.SourceGen.Analysis.RuleInvocations
{
internal sealed class RequiresRuleAnalyzer
{
private readonly MemberAccessAnalyzer _memberAccessAnalyzer = new MemberAccessAnalyzer();

public RuleAnalysisResult Analyze(
SemanticModel semanticModel,
InvocationExpressionSyntax invocation)
{
if (invocation.ArgumentList.Arguments.Count < 3)
{
return RuleAnalysisIssue.UnsupportedArgument(invocation, invocation.ToString());
}

var selectorArgument = invocation.ArgumentList.Arguments[0];
var requirementArgument = invocation.ArgumentList.Arguments[1];
var messageArgument = invocation.ArgumentList.Arguments[2];

var member = AnalyzeSelector(selectorArgument);
if (member == null)
{
return RuleAnalysisIssue.UnsupportedSelector(
selectorArgument,
selectorArgument.Expression.ToString());
}

if (!IsSupportedRequirementMethod(semanticModel, requirementArgument.Expression, out var requirementMethod))
{
return RuleAnalysisIssue.UnsupportedArgument(
requirementArgument,
requirementArgument.Expression.ToString());
}

if (!IsSupportedMessage(messageArgument))
{
return RuleAnalysisIssue.UnsupportedArgument(
messageArgument,
messageArgument.Expression.ToString());
}

return CreateRule(member, requirementMethod, messageArgument);
}

private RuleAnalysisResult CreateRule(
AnalyzedMemberAccess member,
string requirementMethod,
ArgumentSyntax messageArgument)
{
var message = messageArgument.Expression.ToString();

return RuleAnalysisResult.ForRule(new RuleDefinition(
RuleKind.Requires,
member.Path,
member.Access,
string.Empty,
message,
string.Empty,
requirementMethod));
}

private AnalyzedMemberAccess? AnalyzeSelector(ArgumentSyntax selectorArgument)
{
return _memberAccessAnalyzer.Analyze(selectorArgument.Expression);
}

private static bool IsSupportedMessage(ArgumentSyntax messageArgument)
{
if (!(messageArgument.Expression is LiteralExpressionSyntax))
{
return false;
}

return true;
}

private static bool IsSupportedRequirementMethod(
SemanticModel semanticModel,
ExpressionSyntax expression,
out string requirementMethod)
{
requirementMethod = string.Empty;

var symbolInfo = semanticModel.GetSymbolInfo(expression);
var symbol = symbolInfo.Symbol ?? GetSingleCandidate(symbolInfo);
if (!(symbol is IMethodSymbol method))
{
return false;
}

if (!method.IsStatic)
{
return false;
}

if (method.TypeArguments.Length != 0)
{
return false;
}

if (method.Parameters.Length != 1)
{
return false;
}

if (method.ReturnType.SpecialType != SpecialType.System_Boolean)
{
return false;
}

requirementMethod = GetRequirementMethodName(method);
return true;
}

private static ISymbol? GetSingleCandidate(SymbolInfo symbolInfo)
{
if (symbolInfo.CandidateSymbols.Length != 1)
{
return null;
}

return symbolInfo.CandidateSymbols[0];
}

private static string GetRequirementMethodName(IMethodSymbol method)
{
var containingType = method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return containingType + "." + method.Name;
}
}
}
Loading
Loading