This commit is contained in:
Tyrrrz
2024-05-20 22:42:04 +03:00
parent a5a4ad05a0
commit 30bc1d3330
51 changed files with 236 additions and 275 deletions

View File

@@ -17,11 +17,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
<!-- Make sure to target the lowest possible version of the compiler for wider support -->
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -26,8 +26,8 @@ public class CommandMustBeAnnotatedAnalyzer()
if (type.IsAbstract)
return;
var implementsCommandInterface = type.AllInterfaces.Any(
i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
var implementsCommandInterface = type.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
var hasCommandAttribute = type.GetAttributes()

View File

@@ -24,8 +24,8 @@ public class CommandMustImplementInterfaceAnalyzer()
.Select(a => a.AttributeClass)
.Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
var implementsCommandInterface = type.AllInterfaces.Any(
i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
var implementsCommandInterface = type.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
// If the attribute is present, but the interface is not implemented,

View File

@@ -32,10 +32,9 @@ internal partial class CommandOptionSymbol
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
property
.GetAttributes()
.FirstOrDefault(
a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)
== true
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)
== true
);
public static CommandOptionSymbol? TryResolve(IPropertySymbol property)
@@ -46,35 +45,30 @@ internal partial class CommandOptionSymbol
var name =
attribute
.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_String)
.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_String)
.Select(a => a.Value)
.FirstOrDefault() as string;
var shortName =
attribute
.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_Char)
.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_Char)
.Select(a => a.Value)
.FirstOrDefault() as char?;
var isRequired =
attribute
.NamedArguments
.Where(a => a.Key == "IsRequired")
.NamedArguments.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?;
var converter = attribute
.NamedArguments
.Where(a => a.Key == "Converter")
.NamedArguments.Where(a => a.Key == "Converter")
.Select(a => a.Value.Value)
.Cast<ITypeSymbol?>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.NamedArguments.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()

View File

@@ -32,10 +32,9 @@ internal partial class CommandParameterSymbol
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
property
.GetAttributes()
.FirstOrDefault(
a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)
== true
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)
== true
);
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
@@ -48,28 +47,24 @@ internal partial class CommandParameterSymbol
var name =
attribute
.NamedArguments
.Where(a => a.Key == "Name")
.NamedArguments.Where(a => a.Key == "Name")
.Select(a => a.Value.Value)
.FirstOrDefault() as string;
var isRequired =
attribute
.NamedArguments
.Where(a => a.Key == "IsRequired")
.NamedArguments.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?;
var converter = attribute
.NamedArguments
.Where(a => a.Key == "Converter")
.NamedArguments.Where(a => a.Key == "Converter")
.Select(a => a.Value.Value)
.Cast<ITypeSymbol?>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.NamedArguments.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()

View File

@@ -29,10 +29,9 @@ public class OptionMustBeInsideCommandAnalyzer()
if (!CommandOptionSymbol.IsOptionProperty(property))
return;
var isInsideCommand = property
.ContainingType
.AllInterfaces
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
var isInsideCommand = property.ContainingType.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
if (!isInsideCommand)
{

View File

@@ -34,8 +34,7 @@ public class OptionMustHaveUniqueNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -33,8 +33,7 @@ public class OptionMustHaveUniqueShortNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -28,13 +28,11 @@ public class OptionMustHaveValidConverterAnalyzer()
return;
var converterValueType = option
.ConverterType
.GetBaseTypes()
.FirstOrDefault(
t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.ConverterType.GetBaseTypes()
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type
var isCompatible =
@@ -45,9 +43,10 @@ public class OptionMustHaveValidConverterAnalyzer()
? context.Compilation.IsAssignable(converterValueType, property.Type)
// Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context
.Compilation
.IsAssignable(converterValueType, enumerableUnderlyingType)
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
);
if (!isCompatible)

View File

@@ -28,12 +28,10 @@ public class OptionMustHaveValidValidatorsAnalyzer()
{
var validatorValueType = validatorType
.GetBaseTypes()
.FirstOrDefault(
t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type
var isCompatible =

View File

@@ -29,10 +29,9 @@ public class ParameterMustBeInsideCommandAnalyzer()
if (!CommandParameterSymbol.IsParameterProperty(property))
return;
var isInsideCommand = property
.ContainingType
.AllInterfaces
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
var isInsideCommand = property.ContainingType.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
if (!isInsideCommand)
{

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -34,8 +34,7 @@ public class ParameterMustHaveUniqueNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -30,8 +30,7 @@ public class ParameterMustHaveUniqueOrderAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -28,13 +28,11 @@ public class ParameterMustHaveValidConverterAnalyzer()
return;
var converterValueType = parameter
.ConverterType
.GetBaseTypes()
.FirstOrDefault(
t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.ConverterType.GetBaseTypes()
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type
var isCompatible =
@@ -45,9 +43,10 @@ public class ParameterMustHaveValidConverterAnalyzer()
? context.Compilation.IsAssignable(converterValueType, property.Type)
// Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context
.Compilation
.IsAssignable(converterValueType, enumerableUnderlyingType)
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
);
if (!isCompatible)

View File

@@ -28,12 +28,10 @@ public class ParameterMustHaveValidValidatorsAnalyzer()
{
var validatorValueType = validatorType
.GetBaseTypes()
.FirstOrDefault(
t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type
var isCompatible =

View File

@@ -52,8 +52,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzer()
// Check if IConsole is available in scope as an alternative to System.Console
var isConsoleInterfaceAvailable = context
.Node
.Ancestors()
.Node.Ancestors()
.OfType<MethodDeclarationSyntax>()
.SelectMany(m => m.ParameterList.Parameters)
.Select(p => p.Type)

View File

@@ -30,11 +30,10 @@ internal static class RoslynExtensions
}
public static ITypeSymbol? TryGetEnumerableUnderlyingType(this ITypeSymbol type) =>
type.AllInterfaces
.FirstOrDefault(
i =>
i.ConstructedFrom.SpecialType
== SpecialType.System_Collections_Generic_IEnumerable_T
type
.AllInterfaces.FirstOrDefault(i =>
i.ConstructedFrom.SpecialType
== SpecialType.System_Collections_Generic_IEnumerable_T
)
?.TypeArguments[0];
@@ -44,8 +43,7 @@ internal static class RoslynExtensions
property
// Can't rely on the RequiredMemberAttribute because it's generated by the compiler, not added by the user,
// so we have to check for the presence of the `required` modifier in the syntax tree instead.
.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.DeclaringSyntaxReferences.Select(r => r.GetSyntax())
.OfType<PropertyDeclarationSyntax>()
.SelectMany(p => p.Modifiers)
.Any(m => m.IsKind((SyntaxKind)8447));