Add analyzers for invalid validators

This commit is contained in:
Tyrrrz
2020-11-18 18:26:04 +02:00
parent d6da687170
commit f9f5a4696b
4 changed files with 164 additions and 5 deletions

View File

@@ -164,6 +164,30 @@ public class MyCommand : ICommand
[CommandParameter(0, Converter = typeof(MyConverter))] [CommandParameter(0, Converter = typeof(MyConverter))]
public string Param { get; set; } public string Param { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}"
)
};
yield return new object[]
{
new AnalyzerTestCase(
"Parameter with valid validator",
Analyzer.SupportedDiagnostics,
// language=cs
@"
public class MyValidator : ArgumentValueValidator<string>
{
public ValidationResult Validate(string value) => ValidationResult.Ok();
}
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
public string Param { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
}" }"
) )
@@ -292,6 +316,30 @@ public class MyCommand : ICommand
[CommandOption('o', Converter = typeof(MyConverter))] [CommandOption('o', Converter = typeof(MyConverter))]
public string Option { get; set; } public string Option { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}"
)
};
yield return new object[]
{
new AnalyzerTestCase(
"Option with valid validator",
Analyzer.SupportedDiagnostics,
// language=cs
@"
public class MyValidator : ArgumentValueValidator<string>
{
public ValidationResult Validate(string value) => ValidationResult.Ok();
}
[Command]
public class MyCommand : ICommand
{
[CommandOption('o', Validators = new[] {typeof(MyValidator)})]
public string Option { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
}" }"
) )
@@ -438,6 +486,30 @@ public class MyCommand : ICommand
[CommandParameter(0, Converter = typeof(MyConverter))] [CommandParameter(0, Converter = typeof(MyConverter))]
public string Param { get; set; } public string Param { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}"
)
};
yield return new object[]
{
new AnalyzerTestCase(
"Parameter with invalid validator",
DiagnosticDescriptors.CliFx0026,
// language=cs
@"
public class MyValidator
{
public ValidationResult Validate(string value) => ValidationResult.Ok();
}
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
public string Param { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
}" }"
) )
@@ -566,6 +638,30 @@ public class MyCommand : ICommand
[CommandOption('o', Converter = typeof(MyConverter))] [CommandOption('o', Converter = typeof(MyConverter))]
public string Option { get; set; } public string Option { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}"
)
};
yield return new object[]
{
new AnalyzerTestCase(
"Option with invalid validator",
DiagnosticDescriptors.CliFx0047,
// language=cs
@"
public class MyValidator
{
public ValidationResult Validate(string value) => ValidationResult.Ok();
}
[Command]
public class MyCommand : ICommand
{
[CommandOption('o', Validators = new[] {typeof(MyValidator)})]
public string Option { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
}" }"
) )

View File

@@ -18,12 +18,14 @@ namespace CliFx.Analyzers
DiagnosticDescriptors.CliFx0023, DiagnosticDescriptors.CliFx0023,
DiagnosticDescriptors.CliFx0024, DiagnosticDescriptors.CliFx0024,
DiagnosticDescriptors.CliFx0025, DiagnosticDescriptors.CliFx0025,
DiagnosticDescriptors.CliFx0026,
DiagnosticDescriptors.CliFx0041, DiagnosticDescriptors.CliFx0041,
DiagnosticDescriptors.CliFx0042, DiagnosticDescriptors.CliFx0042,
DiagnosticDescriptors.CliFx0043, DiagnosticDescriptors.CliFx0043,
DiagnosticDescriptors.CliFx0044, DiagnosticDescriptors.CliFx0044,
DiagnosticDescriptors.CliFx0045, DiagnosticDescriptors.CliFx0045,
DiagnosticDescriptors.CliFx0046 DiagnosticDescriptors.CliFx0046,
DiagnosticDescriptors.CliFx0047
); );
private static bool IsScalarType(ITypeSymbol typeSymbol) => private static bool IsScalarType(ITypeSymbol typeSymbol) =>
@@ -57,14 +59,24 @@ namespace CliFx.Analyzers
.NamedArguments .NamedArguments
.Where(a => a.Key == "Converter") .Where(a => a.Key == "Converter")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.FirstOrDefault() as ITypeSymbol; .Cast<ITypeSymbol?>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()
.ToArray();
return new return new
{ {
Property = p, Property = p,
Order = order, Order = order,
Name = name, Name = name,
Converter = converter Converter = converter,
Validators = validators
}; };
}) })
.ToArray(); .ToArray();
@@ -140,6 +152,18 @@ namespace CliFx.Analyzers
DiagnosticDescriptors.CliFx0025, parameter.Property.Locations.First() DiagnosticDescriptors.CliFx0025, parameter.Property.Locations.First()
)); ));
} }
// Invalid validators
var invalidValidatorsParameters = parameters
.Where(p => !p.Validators.All(v => v.AllInterfaces.Any(KnownSymbols.IsArgumentValueValidatorInterface)))
.ToArray();
foreach (var parameter in invalidValidatorsParameters)
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.CliFx0026, parameter.Property.Locations.First()
));
}
} }
private static void CheckCommandOptionProperties( private static void CheckCommandOptionProperties(
@@ -175,7 +199,16 @@ namespace CliFx.Analyzers
.NamedArguments .NamedArguments
.Where(a => a.Key == "Converter") .Where(a => a.Key == "Converter")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.FirstOrDefault() as ITypeSymbol; .Cast<ITypeSymbol>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()
.ToArray();
return new return new
{ {
@@ -183,7 +216,8 @@ namespace CliFx.Analyzers
Name = name, Name = name,
ShortName = shortName, ShortName = shortName,
EnvironmentVariableName = envVarName, EnvironmentVariableName = envVarName,
Converter = converter Converter = converter,
Validators = validators
}; };
}) })
.ToArray(); .ToArray();
@@ -270,6 +304,18 @@ namespace CliFx.Analyzers
DiagnosticDescriptors.CliFx0046, option.Property.Locations.First() DiagnosticDescriptors.CliFx0046, option.Property.Locations.First()
)); ));
} }
// Invalid validators
var invalidValidatorsOptions = options
.Where(p => !p.Validators.All(v => v.AllInterfaces.Any(KnownSymbols.IsArgumentValueValidatorInterface)))
.ToArray();
foreach (var option in invalidValidatorsOptions)
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.CliFx0047, option.Property.Locations.First()
));
}
} }
private static void CheckCommandType(SymbolAnalysisContext context) private static void CheckCommandType(SymbolAnalysisContext context)

View File

@@ -53,6 +53,13 @@ namespace CliFx.Analyzers
"Usage", DiagnosticSeverity.Error, true "Usage", DiagnosticSeverity.Error, true
); );
public static readonly DiagnosticDescriptor CliFx0026 =
new DiagnosticDescriptor(nameof(CliFx0026),
"Parameter validator must implement 'CliFx.ArgumentValueValidator<T>'",
"Parameter validator must implement 'CliFx.ArgumentValueValidator<T>'",
"Usage", DiagnosticSeverity.Error, true
);
public static readonly DiagnosticDescriptor CliFx0041 = public static readonly DiagnosticDescriptor CliFx0041 =
new DiagnosticDescriptor(nameof(CliFx0041), new DiagnosticDescriptor(nameof(CliFx0041),
"Option must have a name or short name specified", "Option must have a name or short name specified",
@@ -95,6 +102,13 @@ namespace CliFx.Analyzers
"Usage", DiagnosticSeverity.Error, true "Usage", DiagnosticSeverity.Error, true
); );
public static readonly DiagnosticDescriptor CliFx0047 =
new DiagnosticDescriptor(nameof(CliFx0047),
"Option validator must implement 'CliFx.ArgumentValueValidator<T>'",
"Option validator must implement 'CliFx.ArgumentValueValidator<T>'",
"Usage", DiagnosticSeverity.Error, true
);
public static readonly DiagnosticDescriptor CliFx0100 = public static readonly DiagnosticDescriptor CliFx0100 =
new DiagnosticDescriptor(nameof(CliFx0100), new DiagnosticDescriptor(nameof(CliFx0100),
"Use the provided IConsole abstraction instead of System.Console to ensure that the command can be tested in isolation", "Use the provided IConsole abstraction instead of System.Console to ensure that the command can be tested in isolation",

View File

@@ -28,6 +28,9 @@ namespace CliFx.Analyzers
public static bool IsArgumentValueConverterInterface(ISymbol symbol) => public static bool IsArgumentValueConverterInterface(ISymbol symbol) =>
symbol.DisplayNameMatches("CliFx.IArgumentValueConverter"); symbol.DisplayNameMatches("CliFx.IArgumentValueConverter");
public static bool IsArgumentValueValidatorInterface(ISymbol symbol) =>
symbol.DisplayNameMatches("CliFx.IArgumentValueValidator");
public static bool IsCommandAttribute(ISymbol symbol) => public static bool IsCommandAttribute(ISymbol symbol) =>
symbol.DisplayNameMatches("CliFx.Attributes.CommandAttribute"); symbol.DisplayNameMatches("CliFx.Attributes.CommandAttribute");