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))]
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;
}"
)
@@ -292,6 +316,30 @@ public class MyCommand : ICommand
[CommandOption('o', Converter = typeof(MyConverter))]
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;
}"
)
@@ -438,6 +486,30 @@ public class MyCommand : ICommand
[CommandParameter(0, Converter = typeof(MyConverter))]
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;
}"
)
@@ -566,6 +638,30 @@ public class MyCommand : ICommand
[CommandOption('o', Converter = typeof(MyConverter))]
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;
}"
)

View File

@@ -18,12 +18,14 @@ namespace CliFx.Analyzers
DiagnosticDescriptors.CliFx0023,
DiagnosticDescriptors.CliFx0024,
DiagnosticDescriptors.CliFx0025,
DiagnosticDescriptors.CliFx0026,
DiagnosticDescriptors.CliFx0041,
DiagnosticDescriptors.CliFx0042,
DiagnosticDescriptors.CliFx0043,
DiagnosticDescriptors.CliFx0044,
DiagnosticDescriptors.CliFx0045,
DiagnosticDescriptors.CliFx0046
DiagnosticDescriptors.CliFx0046,
DiagnosticDescriptors.CliFx0047
);
private static bool IsScalarType(ITypeSymbol typeSymbol) =>
@@ -57,14 +59,24 @@ namespace CliFx.Analyzers
.NamedArguments
.Where(a => a.Key == "Converter")
.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
{
Property = p,
Order = order,
Name = name,
Converter = converter
Converter = converter,
Validators = validators
};
})
.ToArray();
@@ -140,6 +152,18 @@ namespace CliFx.Analyzers
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(
@@ -175,7 +199,16 @@ namespace CliFx.Analyzers
.NamedArguments
.Where(a => a.Key == "Converter")
.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
{
@@ -183,7 +216,8 @@ namespace CliFx.Analyzers
Name = name,
ShortName = shortName,
EnvironmentVariableName = envVarName,
Converter = converter
Converter = converter,
Validators = validators
};
})
.ToArray();
@@ -270,6 +304,18 @@ namespace CliFx.Analyzers
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)

View File

@@ -53,6 +53,13 @@ namespace CliFx.Analyzers
"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 =
new DiagnosticDescriptor(nameof(CliFx0041),
"Option must have a name or short name specified",
@@ -95,6 +102,13 @@ namespace CliFx.Analyzers
"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 =
new DiagnosticDescriptor(nameof(CliFx0100),
"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) =>
symbol.DisplayNameMatches("CliFx.IArgumentValueConverter");
public static bool IsArgumentValueValidatorInterface(ISymbol symbol) =>
symbol.DisplayNameMatches("CliFx.IArgumentValueValidator");
public static bool IsCommandAttribute(ISymbol symbol) =>
symbol.DisplayNameMatches("CliFx.Attributes.CommandAttribute");