198 lines
7.4 KiB
C#
198 lines
7.4 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|
{
|
|
internal static class SymbolApiConventionMatcher
|
|
{
|
|
internal static bool IsMatch(ApiControllerSymbolCache symbolCache, IMethodSymbol method, IMethodSymbol conventionMethod)
|
|
{
|
|
return MethodMatches() && ParametersMatch();
|
|
|
|
bool MethodMatches()
|
|
{
|
|
var methodNameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionMethod);
|
|
if (!IsNameMatch(method.Name, conventionMethod.Name, methodNameMatchBehavior))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ParametersMatch()
|
|
{
|
|
var methodParameters = method.Parameters;
|
|
var conventionMethodParameters = conventionMethod.Parameters;
|
|
|
|
for (var i = 0; i < conventionMethodParameters.Length; i++)
|
|
{
|
|
var conventionParameter = conventionMethodParameters[i];
|
|
if (conventionParameter.IsParams)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (methodParameters.Length <= i)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var nameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionParameter);
|
|
var typeMatchBehavior = GetTypeMatchBehavior(symbolCache, conventionParameter);
|
|
|
|
if (!IsTypeMatch(methodParameters[i].Type, conventionParameter.Type, typeMatchBehavior) ||
|
|
!IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Ensure convention has at least as many parameters as the method. params convention argument are handled
|
|
// inside the for loop.
|
|
return methodParameters.Length == conventionMethodParameters.Length;
|
|
}
|
|
}
|
|
|
|
internal static SymbolApiConventionNameMatchBehavior GetNameMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol)
|
|
{
|
|
var attribute = symbol.GetAttributes(symbolCache.ApiConventionNameMatchAttribute).FirstOrDefault();
|
|
if (attribute == null ||
|
|
attribute.ConstructorArguments.Length != 1 ||
|
|
attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum)
|
|
{
|
|
return SymbolApiConventionNameMatchBehavior.Exact;
|
|
}
|
|
|
|
var intValue = (int)attribute.ConstructorArguments[0].Value;
|
|
return (SymbolApiConventionNameMatchBehavior)intValue;
|
|
}
|
|
|
|
internal static SymbolApiConventionTypeMatchBehavior GetTypeMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol)
|
|
{
|
|
var attribute = symbol.GetAttributes(symbolCache.ApiConventionTypeMatchAttribute).FirstOrDefault();
|
|
if (attribute == null ||
|
|
attribute.ConstructorArguments.Length != 1 ||
|
|
attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum)
|
|
{
|
|
return SymbolApiConventionTypeMatchBehavior.AssignableFrom;
|
|
}
|
|
|
|
var intValue = (int)attribute.ConstructorArguments[0].Value;
|
|
return (SymbolApiConventionTypeMatchBehavior)intValue;
|
|
}
|
|
|
|
internal static bool IsNameMatch(string name, string conventionName, SymbolApiConventionNameMatchBehavior nameMatchBehavior)
|
|
{
|
|
switch (nameMatchBehavior)
|
|
{
|
|
case SymbolApiConventionNameMatchBehavior.Any:
|
|
return true;
|
|
|
|
case SymbolApiConventionNameMatchBehavior.Exact:
|
|
return string.Equals(name, conventionName, StringComparison.Ordinal);
|
|
|
|
case SymbolApiConventionNameMatchBehavior.Prefix:
|
|
return IsNameMatchPrefix();
|
|
|
|
case SymbolApiConventionNameMatchBehavior.Suffix:
|
|
return IsNameMatchSuffix();
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
bool IsNameMatchPrefix()
|
|
{
|
|
if (name.Length < conventionName.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (name.Length == conventionName.Length)
|
|
{
|
|
// name = "Post", conventionName = "Post"
|
|
return string.Equals(name, conventionName, StringComparison.Ordinal);
|
|
}
|
|
|
|
if (!name.StartsWith(conventionName, StringComparison.Ordinal))
|
|
{
|
|
// name = "GetPerson", conventionName = "Post"
|
|
return false;
|
|
}
|
|
|
|
// Check for name = "PostPerson", conventionName = "Post"
|
|
// Verify the first letter after the convention name is upper case. In this case 'P' from "Person"
|
|
return char.IsUpper(name[conventionName.Length]);
|
|
}
|
|
|
|
bool IsNameMatchSuffix()
|
|
{
|
|
if (name.Length < conventionName.Length)
|
|
{
|
|
// name = "person", conventionName = "personName"
|
|
return false;
|
|
}
|
|
|
|
if (name.Length == conventionName.Length)
|
|
{
|
|
// name = id, conventionName = id
|
|
return string.Equals(name, conventionName, StringComparison.Ordinal);
|
|
}
|
|
|
|
// Check for name = personName, conventionName = name
|
|
var index = name.Length - conventionName.Length - 1;
|
|
if (!char.IsLower(name[index]))
|
|
{
|
|
// Verify letter before "name" is lowercase. In this case the letter 'n' at the end of "person"
|
|
return false;
|
|
}
|
|
|
|
index++;
|
|
if (name[index] != char.ToUpper(conventionName[0]))
|
|
{
|
|
// Verify the first letter from convention is upper case. In this case 'n' from "name"
|
|
return false;
|
|
}
|
|
|
|
// Match the remaining letters with exact case. i.e. match "ame" from "personName", "name"
|
|
index++;
|
|
return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0;
|
|
}
|
|
}
|
|
|
|
internal static bool IsTypeMatch(ITypeSymbol type, ITypeSymbol conventionType, SymbolApiConventionTypeMatchBehavior typeMatchBehavior)
|
|
{
|
|
switch (typeMatchBehavior)
|
|
{
|
|
case SymbolApiConventionTypeMatchBehavior.Any:
|
|
return true;
|
|
|
|
case SymbolApiConventionTypeMatchBehavior.AssignableFrom:
|
|
return conventionType.IsAssignableFrom(type);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal enum SymbolApiConventionTypeMatchBehavior
|
|
{
|
|
Any,
|
|
AssignableFrom
|
|
}
|
|
|
|
internal enum SymbolApiConventionNameMatchBehavior
|
|
{
|
|
Any,
|
|
Exact,
|
|
Prefix,
|
|
Suffix,
|
|
}
|
|
}
|
|
}
|