diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs index 1698498f50..15341b0fc8 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs @@ -33,7 +33,7 @@ private void OnAttributeNode(SyntaxNodeAnalysisContext context) var parameter = (ParameterSyntax)attributeSyntax.Parent.Parent; var parameterSymbol = context.SemanticModel.GetDeclaredSymbol(parameter); - OnParameterWithConstantExecptedAttribute(parameterSymbol, context.ReportDiagnostic); + OnParameterWithConstantExpectedAttribute(parameterSymbol, context.ReportDiagnostic); } private sealed class CSharpDiagnosticHelper : DiagnosticHelper diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 5d35f49a65..ae2939228d 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -6,5 +6,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- CA1849 | Performance | Disabled | UseAsyncMethodInAsyncContext, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1849) CA1850 | Performance | Info | PreferHashDataOverComputeHashAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1850) +CA1860 | Performance | Error | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860) +CA1861 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860) CA5404 | Security | Disabled | DoNotDisableTokenValidationChecks, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca5404) CA5405 | Security | Disabled | DoNotAlwaysSkipTokenValidationInDelegates, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca5405) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs index 8236b6b66e..053763ed38 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs @@ -14,40 +14,40 @@ public abstract partial class ConstantExpectedAnalyzer { private sealed class UnmanagedHelper where T : unmanaged { - private static readonly UnmanagedHelper.ConstantExpectedParameterFactory? _instance; - private static UnmanagedHelper.ConstantExpectedParameterFactory Instance => _instance ?? throw new InvalidOperationException("unsupported type"); + private static readonly ConstantExpectedParameterFactory? _instance; + private static ConstantExpectedParameterFactory Instance => _instance ?? throw new InvalidOperationException("unsupported type"); static UnmanagedHelper() { if (typeof(T) == typeof(long)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertInt64, TryTransformInt64); - _instance = new UnmanagedHelper.ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } else if (typeof(T) == typeof(ulong)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertUInt64, TryTransformUInt64); - _instance = new ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } else if (typeof(T) == typeof(float)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertSingle, TryTransformSingle); - _instance = new ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } else if (typeof(T) == typeof(double)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertDouble, TryTransformDouble); - _instance = new ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } else if (typeof(T) == typeof(char)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertChar, TryTransformChar); - _instance = new ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } else if (typeof(T) == typeof(bool)) { var helper = new UnmanagedHelper.TransformHelper(TryConvertBoolean, TryTransformBoolean); - _instance = new ConstantExpectedParameterFactory((UnmanagedHelper.TransformHelper)(object)helper); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); } } @@ -264,51 +264,50 @@ private static bool TryConvertUnsignedInteger(object constant, out ulong integer private static bool TryConvertInt64(object? constant, out long value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = Convert.ToInt64(constant); + return true; } - value = Convert.ToInt64(constant); - return true; + value = default; + return false; } private static bool TryTransformInt64(object constant, out long value, out bool isInvalid) { bool isValidSigned = TryConvertSignedInteger(constant, out value); isInvalid = false; - if (!isValidSigned) + if (isValidSigned) { - bool isValidUnsigned = TryConvertUnsignedInteger(constant, out _); - if (!isValidUnsigned) - { - isInvalid = true; - } + return isValidSigned; + } + if (!TryConvertUnsignedInteger(constant, out _)) + { + isInvalid = true; } - return isValidSigned; } private static bool TryConvertUInt64(object? constant, out ulong value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = Convert.ToUInt64(constant); + return true; } - value = Convert.ToUInt64(constant); - return true; + value = default; + return false; } private static bool TryTransformUInt64(object constant, out ulong value, out bool isInvalid) { bool isValidUnsigned = TryConvertUnsignedInteger(constant, out value); isInvalid = false; - if (!isValidUnsigned) + if (isValidUnsigned) { - bool isValidSigned = TryConvertSignedInteger(constant, out _); - if (!isValidSigned) - { - isInvalid = true; - } + return isValidUnsigned; + } + if (!TryConvertSignedInteger(constant, out _)) + { + isInvalid = true; } return isValidUnsigned; } @@ -336,13 +335,13 @@ private static bool TryTransformChar(object constant, out char value, out bool i } private static bool TryConvertChar(object? constant, out char value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = Convert.ToChar(constant); + return true; } - value = Convert.ToChar(constant); - return true; + value = default; + return false; } private static bool TryTransformBoolean(object constant, out bool value, out bool isInvalid) @@ -359,13 +358,13 @@ private static bool TryTransformBoolean(object constant, out bool value, out boo } private static bool TryConvertBoolean(object? constant, out bool value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = (bool)constant; + return true; } - value = (bool)constant; - return true; + value = default; + return false; } private static bool TryTransformSingle(object constant, out float value, out bool isInvalid) @@ -389,15 +388,16 @@ private static bool TryTransformSingle(object constant, out float value, out boo isInvalid = false; return true; } + private static bool TryConvertSingle(object? constant, out float value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = Convert.ToSingle(constant); + return true; } - value = Convert.ToSingle(constant); - return true; + value = default; + return false; } private static bool TryTransformDouble(object constant, out double value, out bool isInvalid) @@ -423,13 +423,13 @@ private static bool TryTransformDouble(object constant, out double value, out bo } private static bool TryConvertDouble(object? constant, out double value) { - if (constant is null) + if (constant is not null) { - value = default; - return false; + value = Convert.ToDouble(constant); + return true; } - value = Convert.ToDouble(constant); - return true; + value = default; + return false; } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs index 3007649524..7f6b5b15f4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs @@ -171,7 +171,7 @@ private static void OnInvocation(OperationAnalysisContext context) protected abstract void RegisterAttributeSyntax(CompilationStartAnalysisContext context); - protected void OnParameterWithConstantExecptedAttribute(IParameterSymbol parameter, Action reportAction) + protected void OnParameterWithConstantExpectedAttribute(IParameterSymbol parameter, Action reportAction) { if (!ValidateConstantExpectedParameter(parameter, out ImmutableArray diagnostics)) { @@ -301,15 +301,10 @@ private static bool TryGetConstantExpectedAttributeData(IParameterSymbol paramet private static bool IsConstantExpectedAttribute(INamedTypeSymbol namedType) { - if (!namedType.Name.Equals(ConstantExpectedAttribute, StringComparison.Ordinal)) - { - return false; - } - if (!namedType.GetMembers().OfType().All(s => s.Name.Equals("Min", StringComparison.Ordinal) || s.Name.Equals("Max", StringComparison.Ordinal))) - { - return false; - } - return true; + return namedType.Name.Equals(ConstantExpectedAttribute, StringComparison.Ordinal) && + namedType.GetMembers().OfType() + .All(s => s.Name.Equals("Min", StringComparison.Ordinal) || + s.Name.Equals("Max", StringComparison.Ordinal)); } private abstract class ConstantExpectedParameter @@ -407,11 +402,7 @@ public static (object? MinConstant, object? MaxConstant) GetAttributeConstants(A { return null; } - if (typedConstant.Kind == TypedConstantKind.Array) - { - return typedConstant.Values; - } - return typedConstant.Value; + return typedConstant.Kind == TypedConstantKind.Array ? typedConstant.Values : typedConstant.Value; } } diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index cb70695cd5..e0680201b2 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1476,6 +1476,30 @@ It is more efficient to use the static 'HashData' method over creating and manag |CodeFix|True| --- +## [CA1860](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860): Incorerct use of ConstantExpectedAttribute + +ConstantExpectedAttribute is not applied correctly on the paramter. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Error| +|CodeFix|False| +--- + +## [CA1861](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861): Constant is expected for the parameter + +The parameter expects a constant for best performance. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Warning| +|CodeFix|False| +--- + ## [CA2000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 0560a44240..dbe1b30da0 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -246,6 +246,44 @@ ] } }, + "CA1860": { + "id": "CA1860", + "shortDescription": "Incorerct use of ConstantExpectedAttribute", + "fullDescription": "ConstantExpectedAttribute is not applied correctly on the paramter.", + "defaultLevel": "error", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpConstantExpectedAnalyzer", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, + "CA1861": { + "id": "CA1861", + "shortDescription": "Constant is expected for the parameter", + "fullDescription": "The parameter expects a constant for best performance.", + "defaultLevel": "warning", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpConstantExpectedAnalyzer", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2014": { "id": "CA2014", "shortDescription": "Do not use stackalloc in loops", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 41f7117cbe..a2e24b8389 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -4,16 +4,14 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| CA1419 | | Provide a parameterless constructor that is as visible as the containing type for concrete types derived from 'System.Runtime.InteropServices.SafeHandle' | CA1727 | | Use PascalCase for named placeholders | -CA1839 | | Use 'Environment.ProcessPath' | -CA1840 | | Use 'Environment.CurrentManagedThreadId' | CA1842 | | Do not use 'WhenAll' with a single task | CA1843 | | Do not use 'WaitAll' with a single task | CA1848 | | Use the LoggerMessage delegates | -CA1850 | | Prefer static 'HashData' method over 'ComputeHash' | +CA1860 | | Incorerct use of ConstantExpectedAttribute | +CA1861 | | Constant is expected for the parameter | CA2017 | | Parameter count mismatch | CA2253 | | Named placeholders should not be numeric values | CA2254 | | Template should be a static expression | -CA2255 | | The 'ModuleInitializer' attribute should not be used in libraries | CA2256 | | All members declared in parent interfaces must have an implementation in a DynamicInterfaceCastableImplementation-attributed interface | CA2257 | | Members defined on an interface with the 'DynamicInterfaceCastableImplementationAttribute' should be 'static' | CA2258 | | Providing a 'DynamicInterfaceCastableImplementation' interface in Visual Basic is unsupported | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs index 5302af7af3..d79d073620 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs @@ -12,8 +12,58 @@ namespace Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.Microsoft.NetCore.Analyz { public sealed class ConstantExpectedTests { + [Theory] + [InlineData("char", "char.MinValue", "char.MaxValue")] + [InlineData("byte", "byte.MinValue", "byte.MaxValue")] + [InlineData("ushort", "ushort.MinValue", "ushort.MaxValue")] + [InlineData("uint", "uint.MinValue", "uint.MaxValue")] + [InlineData("ulong", "ulong.MinValue", "ulong.MaxValue")] + [InlineData("nuint", "uint.MinValue", "uint.MaxValue")] + [InlineData("sbyte", "sbyte.MinValue", "sbyte.MaxValue")] + [InlineData("short", "short.MinValue", "short.MaxValue")] + [InlineData("int", "int.MinValue", "int.MaxValue")] + [InlineData("long", "long.MinValue", "long.MaxValue")] + [InlineData("nint", "int.MinValue", "int.MaxValue")] + [InlineData("float", "float.MinValue", "float.MaxValue")] + [InlineData("double", "double.MinValue", "double.MaxValue")] + public static async Task TestConstantExpectedSupportedUnmanagedTypesAsync(string type, string minValue, string maxValue) + { + + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod1([ConstantExpected] {type} val) + {{ + }} + public static void TestMethod2([ConstantExpected(Min={minValue})] {type} val) + {{ + }} + public static void TestMethod3([ConstantExpected(Max={maxValue})] {type} val) + {{ + }} + public static void TestMethod4([ConstantExpected(Min={minValue}, Max={maxValue})] {type} val) + {{ + }} + public static void TestMethod5([ConstantExpected(Min=null)] {type} val) + {{ + }} + public static void TestMethod6([ConstantExpected(Max=null)] {type} val) + {{ + }} + public static void TestMethod7([ConstantExpected(Min=null, Max=null)] {type} val) + {{ + }} +}} +"; + await TestCSAsync(csInput); + } + [Fact] - public static async Task TestConstantExpectedSupportedTypesAsync() + public static async Task TestConstantExpectedSupportedComplexTypesAsync() { string csInput = @" @@ -23,39 +73,6 @@ public static async Task TestConstantExpectedSupportedTypesAsync() public class Test { - public static void TestMethodByte([ConstantExpected] byte val) - { - } - public static void TestMethodSByte([ConstantExpected] sbyte val) - { - } - public static void TestMethodUInt16([ConstantExpected] ushort val) - { - } - public static void TestMethodInt16([ConstantExpected] short val) - { - } - public static void TestMethodUInt32([ConstantExpected] uint val) - { - } - public static void TestMethodInt32([ConstantExpected] int val) - { - } - public static void TestMethodUInt64([ConstantExpected] ulong val) - { - } - public static void TestMethodInt64([ConstantExpected] long val) - { - } - public static void TestMethodFloat([ConstantExpected] float val) - { - } - public static void TestMethodDouble([ConstantExpected] double val) - { - } - public static void TestMethodBoolean([ConstantExpected] bool val) - { - } public static void TestMethodString([ConstantExpected] string val) { } @@ -126,10 +143,92 @@ await TestCSAsync(csInput, .WithArguments("(int, long)")); } - [Fact] - public static async Task TestConstantExpectedIncompatibleConstantTypeErrorAsync() + [Theory] + [InlineData("char", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("sbyte", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("short", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("int", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("long", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("nint", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("byte", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("ushort", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("uint", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("ulong", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("nuint", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("bool", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("float", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + [InlineData("double", "\"a\"", "\"a\"", "\"a\"", "\"a\"")] + public static async Task TestConstantExpectedIncompatibleConstantTypeErrorAsync(string type, string min1, string min2, string max2, string max3) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod([ConstantExpected({{|#0:Min = {min1}|}})] {type} val) + {{ + }} + public static void TestMethod2([ConstantExpected({{|#1:Min = {min2}|}}, {{|#2:Max = {max2}|}})] {type} val) + {{ + }} + public static void TestMethod3([ConstantExpected({{|#3:Max = {max3}|}})] {type} val) + {{ + }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithLocation(0) + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithLocation(1) + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithLocation(2) + .WithArguments("Max", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithLocation(3) + .WithArguments("Max", type)); + } + + [Theory] + [InlineData("char", "'Z'", "'A'")] + [InlineData("sbyte", "1", "0")] + [InlineData("short", "1", "0")] + [InlineData("int", "1", "0")] + [InlineData("long", "1", "0")] + [InlineData("nint", "1", "0")] + [InlineData("byte", "1", "0")] + [InlineData("ushort", "1", "0")] + [InlineData("uint", "1", "0")] + [InlineData("ulong", "1", "0")] + [InlineData("nuint", "1", "0")] + [InlineData("float", "1", "0")] + [InlineData("double", "1", "0")] + public static async Task TestConstantExpectedInvertedConstantTypeErrorAsync(string type, string min, string max) { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod([{{|#0:ConstantExpected(Min = {min}, Max = {max})|}}] {type} val) + {{ + }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvertedRangeRule) + .WithLocation(0)); + } + [Fact] + public static async Task TestConstantExpectedIncompatibleConstantMinMaxTypeErrorAsync() + { string csInput = @" using System; using System.Diagnostics.CodeAnalysis; @@ -137,96 +236,250 @@ public static async Task TestConstantExpectedIncompatibleConstantTypeErrorAsync( public class Test { - public static void TestMethod([ConstantExpected({|#0:Min = ""min""|})] int val) + public static void TestMethodString1([{|#0:ConstantExpected(Min = true)|}] string val) { } - public static void TestMethod2([ConstantExpected({|#1:Min = ""min""|}, {|#2:Max = ""a""|})] int val) + public static void TestMethodString2([{|#1:ConstantExpected(Min = true, Max = 5f)|}] string val) { } - public static void TestMethod3([ConstantExpected({|#3:Max = true|})] short val) + public static void TestMethodString3([{|#2:ConstantExpected(Max = 10.0)|}] string val) { } - public static void TestMethodString([{|#4:ConstantExpected(Max = true)|}] string val) + + public static void TestMethodGeneric1([{|#3:ConstantExpected(Min = ""min"")|}] T val) { - } - public static void TestMethodGeneric([{|#5:ConstantExpected(Min = ""min"", Max = ""a"")|}] T val) + } + public static void TestMethodGeneric2([{|#4:ConstantExpected(Min = ""min"", Max = '1')|}] T val) { - } - + } + public static void TestMethodGeneric3([{|#5:ConstantExpected(Max = ulong.MaxValue)|}] T val) + { + } public static class GenenricClass { - public static void TestMethodGeneric([{|#6:ConstantExpected(Min = ""min"", Max = ""a"")|}] T val) + public static void TestMethodGeneric1([{|#6:ConstantExpected(Min = ""min"")|}] T val) + { + } + public static void TestMethodGeneric2([{|#7:ConstantExpected(Min = ""min"", Max = ""a"")|}] T val) + { + } + public static void TestMethodGeneric3([{|#8:ConstantExpected(Max = ""a"")|}] T val) { } } } "; await TestCSAsync(csInput, - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(0) - .WithArguments("Min", "int"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithArguments("string"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(1) - .WithArguments("Min", "int"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithArguments("string"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(2) - .WithArguments("Max", "int"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithArguments("string"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(3) - .WithArguments("Max", "short"), + .WithArguments("generic"), VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(4) - .WithArguments("string"), + .WithArguments("generic"), VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(5) .WithArguments("generic"), VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) .WithLocation(6) + .WithArguments("generic"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) + .WithLocation(7) + .WithArguments("generic"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantForMinMaxRule) + .WithLocation(8) .WithArguments("generic")); } [Fact] - public static async Task TestConstantExpectedInvalidTypeRangeValueAsync() + public static async Task TestConstantExpectedInvalidBoundsAsync() { + string[][] setArray = { + new[] + { + "byte", byte.MinValue.ToString(), byte.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "ushort", ushort.MinValue.ToString(), ushort.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "uint", uint.MinValue.ToString(), uint.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "ulong", ulong.MinValue.ToString(), ulong.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "-1", "-1" + }, + new[] + { + "nuint", uint.MinValue.ToString(), uint.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "sbyte", sbyte.MinValue.ToString(), sbyte.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "short", short.MinValue.ToString(), short.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "int", int.MinValue.ToString(), int.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "long", long.MinValue.ToString(), long.MaxValue.ToString(), + "ulong.MaxValue", "ulong.MaxValue", "ulong.MaxValue", "ulong.MaxValue" + }, + new[] + { + "nint", int.MinValue.ToString(), int.MaxValue.ToString(), + "long.MinValue", "long.MinValue", "long.MaxValue", "long.MaxValue" + }, + new[] + { + "float", float.MinValue.ToString(), float.MaxValue.ToString(), + "double.MinValue", "double.MinValue", "double.MaxValue", "double.MaxValue" + } + }; - string csInput = @" + foreach (string[] set in setArray) + { + await TestTheoryAsync(set[0], set[1], set[2], set[3], set[4], set[5], set[6]); + } + + static async Task TestTheoryAsync(string type, string min, string max, string min1, string min2, string max2, + string max3) + { + string csInput = @$" using System; using System.Diagnostics.CodeAnalysis; #nullable enable public class Test -{ - public static void TestMethod([ConstantExpected({|#0:Min = 256|})] byte val) - { - } - public static void TestMethod2([ConstantExpected({|#1:Min = -256|}, {|#2:Max = 256|})] byte val) - { - } - public static void TestMethod3([ConstantExpected({|#3:Min = double.MinValue|}, {|#4:Max = double.MaxValue|})] float val) - { - } -} +{{ + public static void TestMethod([ConstantExpected({{|#0:Min = {min1}|}})] {type} val) + {{ + }} + public static void TestMethod2([ConstantExpected({{|#1:Min = {min2}|}}, {{|#2:Max = {max2}|}})] {type} val) + {{ + }} + public static void TestMethod3([ConstantExpected({{|#3:Max = {max3}|}})] {type} val) + {{ + }} + public static void TestMethod4([ConstantExpected({{|#4:Min = false|}}, {{|#5:Max = {max2}|}})] {type} val) + {{ + }} + public static void TestMethod5([ConstantExpected({{|#6:Min = {min2}|}}, {{|#7:Max = true|}})] {type} val) + {{ + }} +}} "; - await TestCSAsync(csInput, - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) .WithLocation(0) - .WithArguments("Min", "0", "255"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithArguments("Min", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) .WithLocation(1) - .WithArguments("Min", "0", "255"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithArguments("Min", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) .WithLocation(2) - .WithArguments("Max", "0", "255"), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithArguments("Max", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) .WithLocation(3) - .WithArguments("Min", float.MinValue.ToString(), float.MaxValue.ToString()), - VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithArguments("Max", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) .WithLocation(4) - .WithArguments("Max", float.MinValue.ToString(), float.MaxValue.ToString())); + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithLocation(5) + .WithArguments("Max", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.InvalidBoundsRule) + .WithLocation(6) + .WithArguments("Min", min, max), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.IncompatibleConstantTypeRule) + .WithLocation(7) + .WithArguments("Max", type)); + } + } + + [Theory] + [InlineData("char", "'A'", "'Z'", "'A'", "(char)('A'+'\\u0001')")] + [InlineData("byte", "10", "20", "10", "2*5")] + [InlineData("ushort", "10", "20", "10", "2*5")] + [InlineData("uint", "10", "20", "10", "2*5")] + [InlineData("ulong", "10", "20", "10", "2*5")] + [InlineData("nuint", "10", "20", "10", "2*5")] + [InlineData("sbyte", "10", "20", "10", "2*5")] + [InlineData("short", "10", "20", "10", "2*5")] + [InlineData("int", "10", "20", "10", "2*5")] + [InlineData("long", "10", "20", "10", "2*5")] + [InlineData("nint", "10", "20", "10", "2*5")] + [InlineData("float", "10", "20", "10", "2*5")] + [InlineData("double", "10", "20", "10", "2*5")] + [InlineData("bool", "true", "true", "true", "!false")] + [InlineData("string", "null", "null", "\"true\"", "\"false\"")] + public static async Task TestArgumentConstantAsync(string type, string minValue, string maxValue, string value, string expression) + { + + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod() + {{ + TestMethodWithConstant({value}); + TestMethodWithConstant({expression}); + TestMethodWithConstrainedConstant({value}); + TestMethodWithConstrainedConstant({expression}); + TestMethodGeneric<{type}>({value}); + TestMethodGeneric<{type}>({expression}); + GenericClass<{type}>.TestMethodGeneric({value}); + GenericClass<{type}>.TestMethodGeneric({expression}); + }} + public static void TestMethodWithConstant([ConstantExpected] {type} val) + {{ + }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {minValue}, Max = {maxValue})] {type} val) + {{ + }} + public static void TestMethodGeneric([ConstantExpected] T val) + {{ + }} + + public static class GenericClass + {{ + public static void TestMethodGeneric([ConstantExpected] T val) + {{ + }} + }} +}} +"; + await TestCSAsync(csInput); } [Fact] - public static async Task TestArgumentConstantAsync() + public static async Task TestArgumentNotConstantAsync() { string csInput = @" @@ -236,20 +489,15 @@ public static async Task TestArgumentConstantAsync() public class Test { - public static void TestMethod() + public static void TestMethod(int nonConstant) { - TestMethodWithConstant(10); - TestMethodWithConstrainedConstant(10); - TestMethodWithConstrainedConstant(2*5); - TestMethodGeneric(10); - GenenricClass.TestMethodGeneric(10); + TestMethodWithConstant({|#0:nonConstant|}); + TestMethodGeneric({|#1:nonConstant|}); + GenenricClass.TestMethodGeneric({|#2:nonConstant|}); } public static void TestMethodWithConstant([ConstantExpected] int val) { } - public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = 10, Max = 20)] int val) - { - } public static void TestMethodGeneric([ConstantExpected] T val) { } @@ -262,11 +510,17 @@ public static void TestMethodGeneric([ConstantExpected] T val) } } "; - await TestCSAsync(csInput); + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) + .WithLocation(0), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) + .WithLocation(1), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) + .WithLocation(2)); } [Fact] - public static async Task TestArgumentNotConstantAsync() + public static async Task TestArgumentStringNotConstantAsync() { string csInput = @" @@ -276,13 +530,13 @@ public static async Task TestArgumentNotConstantAsync() public class Test { - public static void TestMethod(int nonConstant) + public static void TestMethod(string nonConstant) { TestMethodWithConstant({|#0:nonConstant|}); - TestMethodGeneric({|#1:nonConstant|}); - GenenricClass.TestMethodGeneric({|#2:nonConstant|}); + TestMethodGeneric({|#1:nonConstant|}); + GenenricClass.TestMethodGeneric({|#2:nonConstant|}); } - public static void TestMethodWithConstant([ConstantExpected] int val) + public static void TestMethodWithConstant([ConstantExpected] string val) { } public static void TestMethodGeneric([ConstantExpected] T val) @@ -299,11 +553,11 @@ public static void TestMethodGeneric([ConstantExpected] T val) "; await TestCSAsync(csInput, VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) - .WithLocation(0), + .WithLocation(0), VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) - .WithLocation(1), + .WithLocation(1), VerifyCS.Diagnostic(ConstantExpectedAnalyzer.ConstantNotConstantRule) - .WithLocation(2)); + .WithLocation(2)); } [Fact] @@ -333,7 +587,7 @@ await TestCSAsync(csInput, } [Fact] - public static async Task TestArgumentInvalidGenericTypeParamterConstantAsync() + public static async Task TestArgumentInvalidGenericTypeParameterConstantAsync() { string csInput = @" @@ -363,7 +617,6 @@ public static void TestMethodGeneric([ConstantExpected] T val) await TestCSAsync(csInput); } - [Fact] public static async Task TestConstantCompositionAsync() { @@ -398,6 +651,29 @@ public static void TestMethod2WithConstrainedConstant([ConstantExpected(Min = 10 await TestCSAsync(csInput); } + [Fact] + public static async Task TestConstantCompositionStringAsync() + { + + string csInput = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{ + public static void TestMethod([ConstantExpected] string constant) + { + TestMethodWithConstant(constant); + } + public static void TestMethodWithConstant([ConstantExpected] string val) + { + } +} +"; + await TestCSAsync(csInput); + } + [Fact] public static async Task TestConstantCompositionOutOfRangeAsync() { @@ -441,7 +717,6 @@ await TestCSAsync(csInput, .WithArguments("10", "20")); } - private static async Task TestCSAsync(string source, params DiagnosticResult[] diagnosticResults) { var test = new VerifyCS.Test @@ -465,6 +740,5 @@ public sealed class ConstantExpectedAttribute : Attribute public object? Max { get; set; } } }"; - } } diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index a94aba62d9..c412574b5c 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1850 +Performance: HA, CA1800-CA1850, CA1860-CA1861 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2258 Naming: CA1700-CA1727