Tyrrrz/CliFx

Check `BindingConverter<T>` and `BindingValidator<T>` types in analyzers

Closed this issue · 0 comments

Ensure that, on a property of type (example) int, specified converter must be of type BindingConverter<int> and specified validators must be of type BindingValidator<int>. Additionally, types that are assignable to int should also be allowed.

This will require some more complex logic in the analyzers, because currently they're just checking for an internal non-generic interface:

// We check against an internal interface because checking against a generic class is a pain
var converterImplementsInterface = parameter
.ConverterType
.AllInterfaces
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface));
if (!converterImplementsInterface)
{
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
}

public const string CliFxBindingConverterInterface = "CliFx.Extensibility.IBindingConverter";
public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>";
public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator";
public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>";

Example of incorrect usage:

// Should be BindingConverter<int>
public class MyConverter : BindingConverter<string> { /* ... */ }

// Should be BindingValidator<int>
public class MyValidator : BindingValidator<string> { /* ... */ }

// ...

// Analyzer error here:
[CommandParameter(0, Converter = typeof(MyConverter), Validators = new[] {typeof(MyValidator)})]
public int MyValue { get; init; }

Example of correct usage:

public class MyConverter : BindingConverter<int> { /* ... */ }
public class MyValidator : BindingValidator<int> { /* ... */ }

// ...

[CommandParameter(0, Converter = typeof(MyConverter), Validators = new[] {typeof(MyValidator)})]
public int MyValue { get; init; }

Example of another correct usage:

// This is fine too, because typeof(int).IsAssignableFrom(typeof(byte)) is true
public class MyConverter : BindingConverter<byte> { /* ... */ }
public class MyValidator : BindingValidator<byte> { /* ... */ }

// ...

[CommandParameter(0, Converter = typeof(MyConverter), Validators = new[] {typeof(MyValidator)})]
public int MyValue { get; init; }