mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Add analyzers for invalid validators
This commit is contained in:
@@ -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;
|
||||||
}"
|
}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user