diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs new file mode 100644 index 0000000000..ae87caec60 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.NetCore.Analyzers.Performance; + +namespace Microsoft.NetCore.CSharp.Analyzers.Performance +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class CSharpConstantExpectedAnalyzer : ConstantExpectedAnalyzer + { + private static readonly CSharpDiagnosticHelper s_diagnosticHelper = new(); + private static readonly IdentifierNameSyntax s_constantExpectedIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpected); + private static readonly IdentifierNameSyntax s_constantExpectedAttributeIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedAttribute); + + protected override DiagnosticHelper Helper => s_diagnosticHelper; + + protected override void RegisterAttributeSyntax(CompilationStartAnalysisContext context, ConstantExpectedContext constantExpectedContext) + { + context.RegisterSyntaxNodeAction(context => OnAttributeNode(context, constantExpectedContext), SyntaxKind.Attribute); + } + + private void OnAttributeNode(SyntaxNodeAnalysisContext context, ConstantExpectedContext constantExpectedContext) + { + var attributeSyntax = (AttributeSyntax)context.Node; + var attributeName = attributeSyntax.Name; + if (!attributeName.IsEquivalentTo(s_constantExpectedIdentifier) && !attributeName.IsEquivalentTo(s_constantExpectedAttributeIdentifier)) + { + return; + } + + if (attributeSyntax.Parent.Parent is ParameterSyntax parameter) + { + var parameterSymbol = context.SemanticModel.GetDeclaredSymbol(parameter); + OnParameterWithConstantExpectedAttribute(parameterSymbol, constantExpectedContext, context.ReportDiagnostic); + } + } + + private sealed class CSharpDiagnosticHelper : DiagnosticHelper + { + private readonly IdentifierNameSyntax _constantExpectedMinIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedMin); + private readonly IdentifierNameSyntax _constantExpectedMaxIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedMax); + + public override Location? GetMaxLocation(SyntaxNode attributeNode) => GetArgumentLocation(attributeNode, _constantExpectedMaxIdentifier); + + public override Location? GetMinLocation(SyntaxNode attributeNode) => GetArgumentLocation(attributeNode, _constantExpectedMinIdentifier); + + private static Location? GetArgumentLocation(SyntaxNode attributeNode, IdentifierNameSyntax targetNameSyntax) + { + var attributeSyntax = (AttributeSyntax)attributeNode; + if (attributeSyntax.ArgumentList is null) + { + return null; + } + var targetArg = attributeSyntax.ArgumentList.Arguments.FirstOrDefault(arg => arg.NameEquals.Name.IsEquivalentTo(targetNameSyntax, true)); + return targetArg?.GetLocation(); + } + } + } +} +; \ No newline at end of file diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index cdf4f1397e..4cb7d58e49 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1 +1,8 @@ ; Please do not edit this file manually, it should only be updated through code fix application. + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1856 | Performance | Error | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856) +CA1857 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 62e7fe1249..bd0da36e43 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1864,6 +1864,42 @@ '{0}' uses the preview type '{1}' and needs to opt into preview features. See {2} for more information. + + Incorrect usage of ConstantExpected attribute + + + ConstantExpected attribute is not applied correctly on the parameter. + + + A constant is expected for the parameter + + + The parameter expects a constant for optimal performance. + + + The '{0}' type is not supported for ConstantExpected attribute + + + The '{0}' value is not compatible with parameter type of '{1}' + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + The Min and Max values are inverted + + + The constant does not fit within the value bounds of '{0}' to '{1}' + + + The constant is not of the same '{0}' type as the parameter + + + The argument should be a constant for optimal performance + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + Specify a culture or use an invariant version diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs new file mode 100644 index 0000000000..5060578fd6 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs @@ -0,0 +1,340 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + public abstract partial class ConstantExpectedAnalyzer + { + private sealed class UnmanagedHelper where T : unmanaged + { + 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(TryTransformInt64); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + else if (typeof(T) == typeof(ulong)) + { + var helper = new UnmanagedHelper.TransformHelper(TryTransformUInt64); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + else if (typeof(T) == typeof(float)) + { + var helper = new UnmanagedHelper.TransformHelper(TryTransformSingle); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + else if (typeof(T) == typeof(double)) + { + var helper = new UnmanagedHelper.TransformHelper(TryTransformDouble); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + else if (typeof(T) == typeof(char)) + { + var helper = new UnmanagedHelper.TransformHelper(TryTransformChar); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + else if (typeof(T) == typeof(bool)) + { + var helper = new UnmanagedHelper.TransformHelper(TryTransformBoolean); + _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper); + } + } + + public static bool TryCreate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, [NotNullWhen(true)] out ConstantExpectedParameter? parameter) + => Instance.TryCreate(parameterSymbol, attributeData, typeMin, typeMax, out parameter); + public static bool Validate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, DiagnosticHelper diagnosticHelper, out ImmutableArray diagnostics) + => Instance.Validate(parameterSymbol, attributeData, typeMin, typeMax, diagnosticHelper, out diagnostics); + + public delegate bool TryTransform(object constant, out T value, out bool isInvalid); + public sealed class TransformHelper + { + private readonly TryTransform _tryTransform; + + public TransformHelper(TryTransform tryTransform) + { + _tryTransform = tryTransform; + } + + public bool IsLessThan(T operand1, T operand2) => Comparer.Default.Compare(operand1, operand2) < 0; + public bool TryTransformMin(object constant, out T value, ref ErrorKind errorFlags) + { + if (_tryTransform(constant, out value, out bool isInvalid)) + { + return true; + } + + errorFlags |= isInvalid ? ErrorKind.MinIsIncompatible : ErrorKind.MinIsOutOfRange; + return false; + } + + public bool TryTransformMax(object constant, out T value, ref ErrorKind errorFlags) + { + if (_tryTransform(constant, out value, out bool isInvalid)) + { + return true; + } + + errorFlags |= isInvalid ? ErrorKind.MaxIsIncompatible : ErrorKind.MaxIsOutOfRange; + return false; + } + public bool TryConvert(object val, out T value) => _tryTransform(val, out value, out _); + } + + public sealed class ConstantExpectedParameterFactory + { + private readonly TransformHelper _helper; + + public ConstantExpectedParameterFactory(TransformHelper helper) + { + _helper = helper; + } + public bool Validate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, DiagnosticHelper diagnosticHelper, out ImmutableArray diagnostics) + { + if (!IsValidMinMax(attributeData, typeMin, typeMax, out _, out _, out ErrorKind errorFlags)) + { + diagnostics = diagnosticHelper.GetError(errorFlags, parameterSymbol, attributeData.ApplicationSyntaxReference.GetSyntax(), typeMin.ToString(), typeMax.ToString()); + return false; + } + + diagnostics = ImmutableArray.Empty; + return true; + } + + public bool TryCreate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, [NotNullWhen(true)] out ConstantExpectedParameter? parameter) + { + if (!IsValidMinMax(attributeData, typeMin, typeMax, out T minValue, out T maxValue, out _)) + { + parameter = null; + return false; + } + + parameter = new UnmanagedConstantExpectedParameter(parameterSymbol, minValue, maxValue, _helper); + return true; + } + + private bool IsValidMinMax(AttributeData attributeData, T typeMin, T typeMax, out T minValue, out T maxValue, out ErrorKind errorFlags) + { + minValue = typeMin; + maxValue = typeMax; + var ac = AttributeConstant.Get(attributeData); + errorFlags = ErrorKind.None; + if (ac.Min is not null && _helper.TryTransformMin(ac.Min, out minValue, ref errorFlags)) + { + if (_helper.IsLessThan(minValue, typeMin) || _helper.IsLessThan(typeMax, minValue)) + { + errorFlags |= ErrorKind.MinIsOutOfRange; + } + } + + if (ac.Max is not null && _helper.TryTransformMax(ac.Max, out maxValue, ref errorFlags)) + { + if (_helper.IsLessThan(maxValue, typeMin) || _helper.IsLessThan(typeMax, maxValue)) + { + errorFlags |= ErrorKind.MaxIsOutOfRange; + } + } + + if (errorFlags != ErrorKind.None) + { + return false; + } + + if (_helper.IsLessThan(maxValue, minValue)) + { + errorFlags = ErrorKind.MinMaxInverted; + return false; + } + return true; + } + } + + public sealed class UnmanagedConstantExpectedParameter : ConstantExpectedParameter + { + private readonly TransformHelper _helper; + public UnmanagedConstantExpectedParameter(IParameterSymbol parameter, T min, T max, TransformHelper helper) : base(parameter) + { + Min = min; + Max = max; + _helper = helper; + } + + public T Min { get; } + public T Max { get; } + + public override bool ValidateParameterIsWithinRange(ConstantExpectedParameter subsetCandidate, IArgumentOperation argument, [NotNullWhen(false)] out Diagnostic? validationDiagnostics) + { + if (Parameter.Type.SpecialType != subsetCandidate.Parameter.Type.SpecialType || + subsetCandidate is not UnmanagedConstantExpectedParameter subsetCandidateTParameter) + { + validationDiagnostics = CreateConstantInvalidConstantRuleDiagnostic(argument); + return false; + } + + if (!_helper.IsLessThan(subsetCandidateTParameter.Min, Min) && !_helper.IsLessThan(Max, subsetCandidateTParameter.Max)) + { + //within range + validationDiagnostics = null; + return true; + } + validationDiagnostics = CreateConstantOutOfBoundsRuleDiagnostic(argument, Min.ToString(), Max.ToString()); + return false; + } + + public override bool ValidateValue(IArgumentOperation argument, Optional constant, [NotNullWhen(false)] out Diagnostic? validationDiagnostics) + { + if (!ValidateConstant(argument, constant, out validationDiagnostics)) + { + return false; + } + + if (constant.Value is not null && _helper.TryConvert(constant.Value, out T value)) + { + if (!_helper.IsLessThan(value, Min) && !_helper.IsLessThan(Max, value)) + { + validationDiagnostics = null; + return true; + } + validationDiagnostics = CreateConstantOutOfBoundsRuleDiagnostic(argument, Min.ToString(), Max.ToString()); + return false; + } + validationDiagnostics = CreateConstantInvalidConstantRuleDiagnostic(argument); + return false; + } + } + } + + private static bool TryConvertSignedInteger(object constant, out long integer) + { + try + { + if (constant is string or bool) + { + integer = default; + return false; + } + integer = Convert.ToInt64(constant); + } + catch + { + integer = default; + return false; + } + return true; + } + private static bool TryConvertUnsignedInteger(object constant, out ulong integer) + { + try + { + if (constant is string or bool) + { + integer = default; + return false; + } + integer = Convert.ToUInt64(constant); + } + catch + { + integer = default; + return false; + } + return true; + } + + private static bool TryTransformInt64(object constant, out long value, out bool isInvalid) + { + bool isValidSigned = TryConvertSignedInteger(constant, out value); + isInvalid = false; + if (isValidSigned) + { + return isValidSigned; + } + if (!TryConvertUnsignedInteger(constant, out _)) + { + isInvalid = true; + } + return isValidSigned; + } + private static bool TryTransformUInt64(object constant, out ulong value, out bool isInvalid) + { + bool isValidUnsigned = TryConvertUnsignedInteger(constant, out value); + isInvalid = false; + if (isValidUnsigned) + { + return isValidUnsigned; + } + if (!TryConvertSignedInteger(constant, out _)) + { + isInvalid = true; + } + return isValidUnsigned; + } + + private static bool TryTransformChar(object constant, out char value, out bool isInvalid) + { + try + { + if (constant is string or bool) + { + return Invalid(out value, out isInvalid); + } + value = Convert.ToChar(constant); + } + catch + { + return Invalid(out value, out isInvalid); + } + isInvalid = false; + return true; + } + + private static bool TryTransformBoolean(object constant, out bool value, out bool isInvalid) + { + if (constant is bool b) + { + value = b; + isInvalid = false; + return true; + } + return Invalid(out value, out isInvalid); + } + + private static bool TryTransformSingle(object constant, out float value, out bool isInvalid) + { + if (constant is string or bool) + { + return Invalid(out value, out isInvalid); + } + value = Convert.ToSingle(constant); + isInvalid = false; + return true; + } + + private static bool TryTransformDouble(object constant, out double value, out bool isInvalid) + { + if (constant is string or bool) + { + return Invalid(out value, out isInvalid); + } + value = Convert.ToDouble(constant); + isInvalid = false; + return true; + } + + private static bool Invalid(out T value, out bool isInvalid) where T : unmanaged + { + value = default; + isInvalid = true; + return false; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs new file mode 100644 index 0000000000..4d9ad2ab30 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.cs @@ -0,0 +1,646 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + public abstract partial class ConstantExpectedAnalyzer : DiagnosticAnalyzer + { + protected static readonly string ConstantExpectedAttribute = nameof(ConstantExpectedAttribute); + protected static readonly string ConstantExpected = nameof(ConstantExpected); + protected const string ConstantExpectedMin = "Min"; + protected const string ConstantExpectedMax = "Max"; + private static readonly LocalizableString s_localizableApplicationTitle = CreateLocalizableResourceString(nameof(ConstantExpectedApplicationTitle)); + private static readonly LocalizableString s_localizableApplicationDescription = CreateLocalizableResourceString(nameof(ConstantExpectedApplicationDescription)); + private static readonly LocalizableString s_localizableUsageTitle = CreateLocalizableResourceString(nameof(ConstantExpectedUsageTitle)); + private static readonly LocalizableString s_localizableUsageDescription = CreateLocalizableResourceString(nameof(ConstantExpectedUsageDescription)); + + internal static class CA1856 + { + internal const string Id = nameof(CA1856); + internal const RuleLevel Level = RuleLevel.BuildError; + internal static readonly DiagnosticDescriptor UnsupportedTypeRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableApplicationTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedNotSupportedMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableApplicationDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor IncompatibleConstantTypeRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableApplicationTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedIncompatibleConstantTypeMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableApplicationDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor InvalidBoundsRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableApplicationTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedInvalidBoundsMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableApplicationDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor InvertedRangeRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableApplicationTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedInvertedRangeMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableApplicationDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + } + + internal static class CA1857 + { + internal const string Id = nameof(CA1857); + internal const RuleLevel Level = RuleLevel.BuildWarning; + + internal static readonly DiagnosticDescriptor ConstantOutOfBoundsRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableUsageTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedOutOfBoundsMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableUsageDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor ConstantNotConstantRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableUsageTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedNotConstantMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableUsageDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor ConstantInvalidConstantRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableUsageTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedInvalidMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableUsageDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static readonly DiagnosticDescriptor AttributeExpectedRule = DiagnosticDescriptorHelper.Create( + Id, + s_localizableUsageTitle, + CreateLocalizableResourceString(nameof(ConstantExpectedAttributExpectedMessage)), + DiagnosticCategory.Performance, + Level, + description: s_localizableUsageDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + } + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + CA1856.UnsupportedTypeRule, CA1856.IncompatibleConstantTypeRule, + CA1856.InvalidBoundsRule, CA1856.InvertedRangeRule, + CA1857.ConstantOutOfBoundsRule, CA1857.ConstantInvalidConstantRule, + CA1857.ConstantNotConstantRule, CA1857.AttributeExpectedRule); + + protected abstract DiagnosticHelper Helper { get; } + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!ConstantExpectedContext.TryCreate(context.Compilation, out var constantExpectedContext)) + { + return; + } + context.RegisterOperationAction(context => OnInvocation(context, constantExpectedContext), OperationKind.Invocation); + context.RegisterSymbolAction(context => OnMethodSymbol(context, constantExpectedContext), SymbolKind.Method); + RegisterAttributeSyntax(context, constantExpectedContext); + } + + private static void OnMethodSymbol(SymbolAnalysisContext context, ConstantExpectedContext constantExpectedContext) + { + var methodSymbol = (IMethodSymbol)context.Symbol; + if (methodSymbol.ExplicitInterfaceImplementations + .FirstOrDefault(methodSymbol.IsImplementationOfInterfaceMember) is { } explicitInterfaceMethod) + { + CheckParameters(methodSymbol.Parameters, explicitInterfaceMethod.Parameters); + } + else if (methodSymbol.OverriddenMethod is not null) + { + CheckParameters(methodSymbol.Parameters, methodSymbol.OverriddenMethod.Parameters); + } + else if (methodSymbol.IsImplementationOfAnyImplicitInterfaceMember(out IMethodSymbol interfaceMethodSymbol)) + { + CheckParameters(methodSymbol.Parameters, interfaceMethodSymbol.Parameters); + } + + + void CheckParameters(ImmutableArray parameters, ImmutableArray baseParameters) + { + if (constantExpectedContext.ValidatesAttributeImplementedFromParent(parameters, baseParameters, out var diagnostics)) + { + return; + } + + foreach (var diagnostic in diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + } + } + + private static void OnInvocation(OperationAnalysisContext context, ConstantExpectedContext constantExpectedContext) + { + var invocation = (IInvocationOperation)context.Operation; + + foreach (var argument in invocation.Arguments) + { + if (!constantExpectedContext.TryCreateConstantExpectedParameter(argument.Parameter, out var argConstantParameter)) + { + continue; + } + var v = argument.Value.WalkDownConversion(); + if (v is IParameterReferenceOperation parameterReference && + constantExpectedContext.TryCreateConstantExpectedParameter(parameterReference.Parameter, out var currConstantParameter)) + { + if (!argConstantParameter.ValidateParameterIsWithinRange(currConstantParameter, argument, out var parameterCheckDiagnostic)) + { + context.ReportDiagnostic(parameterCheckDiagnostic); + } + continue; + } + var constantValue = v.ConstantValue; + if (!argConstantParameter.ValidateValue(argument, constantValue, out var valueDiagnostic)) + { + context.ReportDiagnostic(valueDiagnostic); + } + } + } + + protected abstract void RegisterAttributeSyntax(CompilationStartAnalysisContext context, ConstantExpectedContext constantExpectedContext); + + protected void OnParameterWithConstantExpectedAttribute(IParameterSymbol parameter, ConstantExpectedContext constantExpectedContext, Action reportAction) + { + if (!constantExpectedContext.ValidateConstantExpectedParameter(parameter, Helper, out ImmutableArray diagnostics)) + { + foreach (var diagnostic in diagnostics) + { + reportAction(diagnostic); + } + } + } + + protected sealed class ConstantExpectedContext + { + public INamedTypeSymbol AttributeSymbol { get; } + + public ConstantExpectedContext(INamedTypeSymbol attributeSymbol) + { + AttributeSymbol = attributeSymbol; + } + /// + /// Validates for ConstantExpected attribute in base parameter and returns AttributeExpectedRule if the coresponding implementation parameter does not have it + /// + /// + /// + /// Non empty when method returns false + /// + public bool ValidatesAttributeImplementedFromParent(ImmutableArray parameters, ImmutableArray baseParameters, out ImmutableArray diagnostics) + { + var arraybuilder = ImmutableArray.CreateBuilder(); + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + if (!IsConstantCompatible(parameter.Type)) + { + continue; + } + var baseParameter = baseParameters[i]; + if (HasConstantExpectedAttributeData(baseParameter) && !HasConstantExpectedAttributeData(parameter)) + { + // mark the parameter including the type and name + var diagnostic = parameter.DeclaringSyntaxReferences[0].GetSyntax().CreateDiagnostic(CA1857.AttributeExpectedRule); + arraybuilder.Add(diagnostic); + } + } + diagnostics = arraybuilder.ToImmutable(); + return diagnostics.Length is 0; + } + + private static bool IsConstantCompatible(ITypeSymbol type) + { + return type.SpecialType switch + { + SpecialType.System_Char => true, + SpecialType.System_Byte => true, + SpecialType.System_UInt16 => true, + SpecialType.System_UInt32 => true, + SpecialType.System_UInt64 => true, + SpecialType.System_SByte => true, + SpecialType.System_Int16 => true, + SpecialType.System_Int32 => true, + SpecialType.System_Int64 => true, + SpecialType.System_Single => true, + SpecialType.System_Double => true, + SpecialType.System_Boolean => true, + SpecialType.System_String => true, + SpecialType.None when type.TypeKind == TypeKind.TypeParameter => true, + _ => false, + }; + } + + /// + /// Tries to create a ConstantExpectedParameter to represent the ConstantExpected attribute application + /// + /// + /// + /// + public bool TryCreateConstantExpectedParameter(IParameterSymbol parameterSymbol, [NotNullWhen(true)] out ConstantExpectedParameter? parameter) + { + var underlyingType = GetUnderlyingType(parameterSymbol); + + if (!TryGetConstantExpectedAttributeData(parameterSymbol, out var attributeData)) + { + parameter = null; + return false; + } + + switch (underlyingType.SpecialType) + { + case SpecialType.System_Char: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, char.MinValue, char.MaxValue, out parameter); + case SpecialType.System_Byte: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, byte.MinValue, byte.MaxValue, out parameter); + case SpecialType.System_UInt16: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, ushort.MinValue, ushort.MaxValue, out parameter); + case SpecialType.System_UInt32: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, uint.MinValue, uint.MaxValue, out parameter); + case SpecialType.System_UInt64: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, ulong.MinValue, ulong.MaxValue, out parameter); + case SpecialType.System_SByte: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, sbyte.MinValue, sbyte.MaxValue, out parameter); + case SpecialType.System_Int16: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, short.MinValue, short.MaxValue, out parameter); + case SpecialType.System_Int32: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, int.MinValue, int.MaxValue, out parameter); + case SpecialType.System_Int64: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, long.MinValue, long.MaxValue, out parameter); + case SpecialType.System_Single: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, float.MinValue, float.MaxValue, out parameter); + case SpecialType.System_Double: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, double.MinValue, double.MaxValue, out parameter); + case SpecialType.System_Boolean: + return UnmanagedHelper.TryCreate(parameterSymbol, attributeData, false, true, out parameter); + case SpecialType.System_String: + return StringConstantExpectedParameter.TryCreate(parameterSymbol, attributeData, out parameter); + default: + parameter = null; + return false; + } + } + + /// + /// Validates that the parameter has a valid application of the ConstantExpected attributes. Returns diagnostics otherwise + /// + /// + /// + /// not empty when method returns false + /// + public bool ValidateConstantExpectedParameter(IParameterSymbol parameterSymbol, DiagnosticHelper helper, out ImmutableArray diagnostics) + { + var underlyingType = GetUnderlyingType(parameterSymbol); + + if (!TryGetConstantExpectedAttributeData(parameterSymbol, out var attributeData)) + { + diagnostics = ImmutableArray.Empty; + return false; + } + + switch (underlyingType.SpecialType) + { + case SpecialType.System_Char: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, char.MinValue, char.MaxValue, helper, out diagnostics); + case SpecialType.System_Byte: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, byte.MinValue, byte.MaxValue, helper, out diagnostics); + case SpecialType.System_UInt16: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, ushort.MinValue, ushort.MaxValue, helper, out diagnostics); + case SpecialType.System_UInt32: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, uint.MinValue, uint.MaxValue, helper, out diagnostics); + case SpecialType.System_UInt64: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, ulong.MinValue, ulong.MaxValue, helper, out diagnostics); + case SpecialType.System_SByte: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, sbyte.MinValue, sbyte.MaxValue, helper, out diagnostics); + case SpecialType.System_Int16: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, short.MinValue, short.MaxValue, helper, out diagnostics); + case SpecialType.System_Int32: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, int.MinValue, int.MaxValue, helper, out diagnostics); + case SpecialType.System_Int64: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, long.MinValue, long.MaxValue, helper, out diagnostics); + case SpecialType.System_Single: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, float.MinValue, float.MaxValue, helper, out diagnostics); + case SpecialType.System_Double: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, double.MinValue, double.MaxValue, helper, out diagnostics); + case SpecialType.System_Boolean: + return UnmanagedHelper.Validate(parameterSymbol, attributeData, false, true, helper, out diagnostics); + case SpecialType.System_String: + return ValidateMinMaxIsNull(parameterSymbol, attributeData, helper, out diagnostics); + case SpecialType.None when parameterSymbol.Type.TypeKind == TypeKind.TypeParameter: + return ValidateMinMaxIsNull(parameterSymbol, attributeData, helper, out diagnostics); + default: + diagnostics = helper.ParameterIsInvalid(parameterSymbol.Type.ToDisplayString(), attributeData.ApplicationSyntaxReference.GetSyntax()); + return false; + } + + static bool ValidateMinMaxIsNull(IParameterSymbol parameterSymbol, AttributeData attributeData, DiagnosticHelper helper, out ImmutableArray diagnostics) + { + ErrorKind errorFlags = 0; + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg.Key.Equals(ConstantExpectedMin, StringComparison.Ordinal) + && !namedArg.Value.IsNull) + { + errorFlags |= ErrorKind.MinIsIncompatible; + } + else if (namedArg.Key.Equals(ConstantExpectedMax, StringComparison.Ordinal) + && !namedArg.Value.IsNull) + { + errorFlags |= ErrorKind.MaxIsIncompatible; + } + } + if (errorFlags is not 0) + { + diagnostics = helper.GetError(errorFlags, parameterSymbol, attributeData.ApplicationSyntaxReference.GetSyntax(), "null", "null"); + return false; + } + diagnostics = ImmutableArray.Empty; + return true; + } + } + + private static ITypeSymbol GetUnderlyingType(IParameterSymbol parameterSymbol) + { + ITypeSymbol underlyingType; + if (parameterSymbol.Type.TypeKind is TypeKind.Enum) + { + var enumType = (INamedTypeSymbol)parameterSymbol.Type; + underlyingType = enumType.EnumUnderlyingType; + } + else + { + underlyingType = parameterSymbol.Type; + } + + return underlyingType; + } + + public bool TryGetConstantExpectedAttributeData(IParameterSymbol parameter, [NotNullWhen(true)] out AttributeData? attributeData) + { + attributeData = parameter.GetAttributes() + .FirstOrDefault(attrData => IsConstantExpectedAttribute(attrData.AttributeClass)); + return attributeData is not null; + } + + private bool HasConstantExpectedAttributeData(IParameterSymbol parameter) + { + return parameter.GetAttributes() + .Any(attrData => IsConstantExpectedAttribute(attrData.AttributeClass)); + } + + private bool IsConstantExpectedAttribute(INamedTypeSymbol namedType) + { + return namedType.Equals(AttributeSymbol, SymbolEqualityComparer.Default); + } + + public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out ConstantExpectedContext? constantExpectedContext) + { + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDiagnosticsCodeAnalysisConstantExpectedAttribute, out var attributeSymbol)) + { + constantExpectedContext = null; + return false; + } + + constantExpectedContext = new ConstantExpectedContext(attributeSymbol); + return true; + } + } + + /// + /// Encodes a parameter with its ConstantExpected attribute rules + /// + protected abstract class ConstantExpectedParameter + { + protected ConstantExpectedParameter(IParameterSymbol parameter) + { + Parameter = parameter; + } + /// + /// Parameter with the ConstantExpected attribute + /// + public IParameterSymbol Parameter { get; } + + /// + /// Validates the provided constant value is within the constaints of ConstantExpected attribute set + /// + /// + /// + /// Non empty when method returns false + /// + public abstract bool ValidateValue(IArgumentOperation argument, Optional constant, [NotNullWhen(false)] out Diagnostic? validationDiagnostics); + + public bool ValidateConstant(IArgumentOperation argument, Optional constant, [NotNullWhen(false)] out Diagnostic? validationDiagnostics) + { + if (!constant.HasValue) + { + validationDiagnostics = argument.CreateDiagnostic(CA1857.ConstantNotConstantRule); + return false; + } + validationDiagnostics = null; + return true; + } + + public abstract bool ValidateParameterIsWithinRange(ConstantExpectedParameter subsetCandidate, IArgumentOperation argument, [NotNullWhen(false)] out Diagnostic? validationDiagnostics); + protected Diagnostic CreateConstantInvalidConstantRuleDiagnostic(IArgumentOperation argument) => argument.CreateDiagnostic(CA1857.ConstantInvalidConstantRule, Parameter.Type.ToDisplayString()); + protected Diagnostic CreateConstantOutOfBoundsRuleDiagnostic(IArgumentOperation argument, string minText, string maxText) => argument.CreateDiagnostic(CA1857.ConstantOutOfBoundsRule, minText, maxText); + } + + private sealed class StringConstantExpectedParameter : ConstantExpectedParameter + { + public StringConstantExpectedParameter(IParameterSymbol parameter) : base(parameter) { } + + public override bool ValidateParameterIsWithinRange(ConstantExpectedParameter subsetCandidate, IArgumentOperation argument, [NotNullWhen(false)] out Diagnostic? validationDiagnostics) + { + if (subsetCandidate is not StringConstantExpectedParameter) + { + validationDiagnostics = CreateConstantInvalidConstantRuleDiagnostic(argument); + return false; + } + validationDiagnostics = null; + return true; + } + + public override bool ValidateValue(IArgumentOperation argument, Optional constant, [NotNullWhen(false)] out Diagnostic? validationDiagnostics) + { + if (!ValidateConstant(argument, constant, out validationDiagnostics)) + { + return false; + } + if (constant.Value is not string and not null) + { + validationDiagnostics = CreateConstantInvalidConstantRuleDiagnostic(argument); + return false; + } + validationDiagnostics = null; + return true; + } + + public static bool TryCreate(IParameterSymbol parameterSymbol, AttributeData attributeData, [NotNullWhen(true)] out ConstantExpectedParameter? parameter) + { + var ac = AttributeConstant.Get(attributeData); + if (ac.Min is not null || ac.Max is not null) + { + parameter = null; + return false; + } + parameter = new StringConstantExpectedParameter(parameterSymbol); + return true; + } + } + + private readonly struct AttributeConstant + { + public readonly object? Min; + public readonly object? Max; + + public AttributeConstant(object? min, object? max) + { + Min = min; + Max = max; + } + public static AttributeConstant Get(AttributeData attributeData) + { + object? minConstant = null; + object? maxConstant = null; + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg.Key.Equals(ConstantExpectedMin, StringComparison.Ordinal)) + { + minConstant = ToObject(namedArg.Value); + } + else if (namedArg.Key.Equals(ConstantExpectedMax, StringComparison.Ordinal)) + { + maxConstant = ToObject(namedArg.Value); + } + } + + return new AttributeConstant(minConstant, maxConstant); + + static object? ToObject(TypedConstant typedConstant) + { + if (typedConstant.IsNull) + { + return null; + } + return typedConstant.Kind == TypedConstantKind.Array ? typedConstant.Values : typedConstant.Value; + } + } + } + + + protected abstract class DiagnosticHelper + { + public abstract Location? GetMinLocation(SyntaxNode attributeSyntax); + public abstract Location? GetMaxLocation(SyntaxNode attributeSyntax); + + public ImmutableArray ParameterIsInvalid(string expectedTypeName, SyntaxNode attributeSyntax) => ImmutableArray.Create(Diagnostic.Create(CA1856.UnsupportedTypeRule, attributeSyntax.GetLocation(), expectedTypeName)); + + public Diagnostic MinIsIncompatible(string expectedTypeName, SyntaxNode attributeSyntax) => Diagnostic.Create(CA1856.IncompatibleConstantTypeRule, GetMinLocation(attributeSyntax)!, ConstantExpectedMin, expectedTypeName); + + public Diagnostic MaxIsIncompatible(string expectedTypeName, SyntaxNode attributeSyntax) => Diagnostic.Create(CA1856.IncompatibleConstantTypeRule, GetMaxLocation(attributeSyntax)!, ConstantExpectedMax, expectedTypeName); + + public Diagnostic MinIsOutOfRange(SyntaxNode attributeSyntax, string typeMinValue, string typeMaxValue) => Diagnostic.Create(CA1856.InvalidBoundsRule, GetMinLocation(attributeSyntax)!, ConstantExpectedMin, typeMinValue, typeMaxValue); + + public Diagnostic MaxIsOutOfRange(SyntaxNode attributeSyntax, string typeMinValue, string typeMaxValue) => Diagnostic.Create(CA1856.InvalidBoundsRule, GetMaxLocation(attributeSyntax)!, ConstantExpectedMax, typeMinValue, typeMaxValue); + + public static Diagnostic MinMaxIsInverted(SyntaxNode attributeSyntax) => Diagnostic.Create(CA1856.InvertedRangeRule, attributeSyntax.GetLocation()); + + public ImmutableArray GetError(ErrorKind errorFlags, IParameterSymbol parameterSymbol, SyntaxNode attributeSyntax, string typeMinValue, string typeMaxValue) + { + switch (errorFlags) + { + case ErrorKind.MinIsIncompatible: + return ImmutableArray.Create(MinIsIncompatible(parameterSymbol.Type.ToDisplayString(), attributeSyntax)); + case ErrorKind.MaxIsIncompatible: + return ImmutableArray.Create(MaxIsIncompatible(parameterSymbol.Type.ToDisplayString(), attributeSyntax)); + case ErrorKind.MinIsIncompatible | ErrorKind.MaxIsIncompatible: + var expectedTypeName = parameterSymbol.Type.ToDisplayString(); + return ImmutableArray.Create(MinIsIncompatible(expectedTypeName, attributeSyntax), MaxIsIncompatible(expectedTypeName, attributeSyntax)); + case ErrorKind.MinIsOutOfRange: + return ImmutableArray.Create(MinIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue)); + case ErrorKind.MaxIsOutOfRange: + return ImmutableArray.Create(MaxIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue)); + case ErrorKind.MinIsOutOfRange | ErrorKind.MaxIsOutOfRange: + return ImmutableArray.Create(MinIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue), MaxIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue)); + case ErrorKind.MinIsOutOfRange | ErrorKind.MaxIsIncompatible: + return ImmutableArray.Create(MinIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue), MaxIsIncompatible(parameterSymbol.Type.ToDisplayString(), attributeSyntax)); + case ErrorKind.MinIsIncompatible | ErrorKind.MaxIsOutOfRange: + return ImmutableArray.Create(MinIsIncompatible(parameterSymbol.Type.ToDisplayString(), attributeSyntax), MaxIsOutOfRange(attributeSyntax, typeMinValue, typeMaxValue)); + case ErrorKind.MinMaxInverted: + return ImmutableArray.Create(MinMaxIsInverted(attributeSyntax)); + default: + throw new ArgumentOutOfRangeException(nameof(errorFlags)); + } + } + } + + [Flags] + protected enum ErrorKind + { + None = 0, + /// + /// mutually exclusive with MinIsIncompatible and MinMaxInverted + /// + MinIsOutOfRange = 1, + /// + /// mutually exclusive with MinIsOutOfRange and MinMaxInverted + /// + MinIsIncompatible = 1 << 2, + /// + /// mutually exclusive with MaxIsIncompatible and MinMaxInverted + /// + MaxIsOutOfRange = 1 << 3, + /// + /// mutually exclusive with MaxIsOutOfRange and MinMaxInverted + /// + MaxIsIncompatible = 1 << 4, + /// + /// mutually exclusive + /// + MinMaxInverted = 1 << 5, + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 5789ee0904..aa68b85a71 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Při deserializaci nedůvěryhodného vstupu není deserializace objektu {0} bezpečná. Objekt {1} je buď objektem {0}, nebo je z tohoto objektu odvozený. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 131bc3e5b6..0a819c538d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Beim Deserialisieren einer nicht vertrauenswürdigen Eingabe ist die Deserialisierung eines {0}-Objekts unsicher. "{1}" ist entweder "{0}" oder davon abgeleitet. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 4cbec5e957..477bef6339 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Cuando se deserializa una entrada que no es de confianza, no es segura la deserialización de un objeto {0}. "{1}" es {0} o se deriva de este. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index fe8c3635f7..0e5479e550 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Quand vous désérialisez une entrée non fiable, la désérialisation d'un objet {0} n'est pas une action sécurisée. '{1}' est égal à ou dérive de {0} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 4343462702..08f171eebc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Quando si deserializza input non attendibile, la deserializzazione di un oggetto {0} non è sicura. '{1}' è {0} o deriva da esso diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index da05cd2a3c..0a30fd0f8a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 信頼されていない入力を逆シリアル化する場合、{0} オブジェクトの逆シリアル化は安全ではありません。'{1}' は {0} であるか、それから派生しています diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index f08e75b579..efcbbdfc51 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 신뢰할 수 없는 입력을 역직렬화하는 경우 {0} 개체를 역직렬화하는 것은 안전하지 않습니다. '{1}'은(는) {0}이거나 이 항목에서 파생됩니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 73bc922afa..bbf0b0223b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Deserializowanie obiektu {0} podczas deserializacji niezaufanych danych wejściowych nie jest bezpieczne. Element „{1}” jest elementem {0} lub pochodzi od niego diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index b04fe7b4af..5799395acb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Ao desserializar uma entrada não confiável, a desserialização de um objeto {0} não é segura. '{1}' é {0} ou deriva-se dele diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 9a601f326d..ab9b30cf4c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} При десериализации недоверенных входных данных десериализация объекта {0} является небезопасной. Объект "{1}" является объектом {0} или производным от него объектом. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 7a49e1201c..f5bd911e1e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} Güvenilmeyen giriş seri durumdan çıkarılırken, {0} nesnesinin seri durumdan çıkarılması güvenli değildir. '{1}', {0} nesnesidir veya bu nesneden türetilmiştir diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 9827b65e13..888dec83b1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 对不受信任的输入进行反序列化处理时,反序列化 {0} 对象是不安全的。“{1}”是或派生自 {0} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1fe1ccb790..1977b97705 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -182,6 +182,66 @@ , Separator used for separating list of platform names: {API} is only supported on: {‘windows’, ‘browser’, ‘linux’} + + ConstantExpected attribute is not applied correctly on the parameter. + ConstantExpected attribute is not applied correctly on the parameter. + + + + Incorrect usage of ConstantExpected attribute + Incorrect usage of ConstantExpected attribute + + + + The ConstantExpected attribute is required for the parameter due to the parent method annotation + The ConstantExpected attribute is required for the parameter due to the parent method annotation + + + + The '{0}' value is not compatible with parameter type of '{1}' + The '{0}' value is not compatible with parameter type of '{1}' + + + + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}' + + + + The constant is not of the same '{0}' type as the parameter + The constant is not of the same '{0}' type as the parameter + + + + The Min and Max values are inverted + The Min and Max values are inverted + + + + The argument should be a constant for optimal performance + The argument should be a constant for optimal performance + + + + The '{0}' type is not supported for ConstantExpected attribute + The '{0}' type is not supported for ConstantExpected attribute + + + + The constant does not fit within the value bounds of '{0}' to '{1}' + The constant does not fit within the value bounds of '{0}' to '{1}' + + + + The parameter expects a constant for optimal performance. + The parameter expects a constant for optimal performance. + + + + A constant is expected for the parameter + A constant is expected for the parameter + + When deserializing untrusted input, deserializing a {0} object is insecure. '{1}' either is or derives from {0} 還原序列化不受信任的輸入時,將 {0} 物件還原序列化並不安全。'{1}' 屬於或衍生自 {0} diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index b5fcad02fd..4a50157bff 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1584,6 +1584,30 @@ It is more efficient to use 'Clear', instead of 'Fill' with default value. |CodeFix|True| --- +## [CA1856](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856): Incorrect usage of ConstantExpected attribute + +ConstantExpected attribute is not applied correctly on the parameter. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Error| +|CodeFix|False| +--- + +## [CA1857](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857): A constant is expected for the parameter + +The parameter expects a constant for optimal performance. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Warning| +|CodeFix|False| +--- + ## [CA2000](https://learn.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 09d8fc98a0..576fc974e9 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -5,7 +5,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.CSharp.NetAnalyzers", - "version": "7.0.0", + "version": "8.0.0", "language": "en-US" }, "rules": { @@ -304,6 +304,44 @@ ] } }, + "CA1856": { + "id": "CA1856", + "shortDescription": "Incorrect usage of ConstantExpected attribute", + "fullDescription": "ConstantExpected attribute is not applied correctly on the parameter.", + "defaultLevel": "error", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpConstantExpectedAnalyzer", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, + "CA1857": { + "id": "CA1857", + "shortDescription": "A constant is expected for the parameter", + "fullDescription": "The parameter expects a constant for optimal performance.", + "defaultLevel": "warning", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpConstantExpectedAnalyzer", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2014": { "id": "CA2014", "shortDescription": "Do not use stackalloc in loops", @@ -538,7 +576,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.NetAnalyzers", - "version": "7.0.0", + "version": "8.0.0", "language": "en-US" }, "rules": { @@ -5815,7 +5853,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers", - "version": "7.0.0", + "version": "8.0.0", "language": "en-US" }, "rules": { diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index b3c8905c00..acfaceed87 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -7,3 +7,5 @@ CA1421 | | Seal internal types | CA1853 | | Unnecessary call to 'Dictionary.ContainsKey(key)' | CA1855 | | Prefer 'Clear' over 'Fill' | +CA1856 | | Incorrect usage of ConstantExpected attribute | +CA1857 | | A constant is expected for the parameter | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs new file mode 100644 index 0000000000..51dd7a0c18 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedTests.cs @@ -0,0 +1,1125 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpConstantExpectedAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public sealed class ConstantExpectedTests + { + [Theory] + [InlineData("char", "char.MinValue", "char.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("byte", "byte.MinValue", "byte.MaxValue")] + [InlineData("ushort", "ushort.MinValue", "ushort.MaxValue")] + [InlineData("uint", "uint.MinValue", "uint.MaxValue")] + [InlineData("ulong", "ulong.MinValue", "ulong.MaxValue")] + [InlineData("bool", "false", "true")] + [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); + } + + [Theory] + [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("byte", "byte.MinValue", "byte.MaxValue")] + [InlineData("ushort", "ushort.MinValue", "ushort.MaxValue")] + [InlineData("uint", "uint.MinValue", "uint.MaxValue")] + [InlineData("ulong", "ulong.MinValue", "ulong.MaxValue")] + public static async Task TestConstantExpectedSupportedEnumTypesAsync(string type, string minValue, string maxValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod1([ConstantExpected] AEnum val) {{ }} + public static void TestMethod2([ConstantExpected(Min={minValue})] AEnum val) {{ }} + public static void TestMethod3([ConstantExpected(Max={maxValue})] AEnum val) {{ }} + public static void TestMethod4([ConstantExpected(Min={minValue}, Max={maxValue})] AEnum val) {{ }} + public static void TestMethod5([ConstantExpected(Min=null)] AEnum val) {{ }} + public static void TestMethod6([ConstantExpected(Max=null)] AEnum val) {{ }} + public static void TestMethod7([ConstantExpected(Min=null, Max=null)] AEnum val) {{ }} +}} + +public enum AEnum : {type} +{{ + One, + Two +}} +"; + await TestCSAsync(csInput); + } + + [Fact] + public static async Task TestConstantExpectedSupportedComplexTypesAsync() + { + string csInput = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{ + public static void TestMethodString([ConstantExpected] string val) { } + public static void TestMethodGeneric([ConstantExpected] T val) { } + + public static class GenenricClass + { + public static void TestMethodGeneric([ConstantExpected] T val) { } + } +} +"; + await TestCSAsync(csInput); + } + + [Theory] + [InlineData("char")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + [InlineData("byte")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("bool")] + [InlineData("string")] + public static async Task TestConstantExpectedSupportedComplex2TypesAsync(string type) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public interface ITest + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public interface ITest2 + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public abstract class AbstractTest + {{ + public abstract T Method2(T operand1, [ConstantExpected] T operand2); + }} + + public class Generic : AbstractTest<{type}>, ITest<{type}>, ITest2<{type}> + {{ + public {type} Method({type} operand1, {{|#0:{type} operand2|}}) => throw new NotImplementedException(); + {type} ITest2<{type}>.Method({type} operand1, {{|#1:{type} operand2|}}) => throw new NotImplementedException(); + public override {type} Method2({type} operand1, {{|#2:{type} operand2|}}) => throw new NotImplementedException(); + }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(0), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(1), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(2)); + } + + [Theory] + [InlineData("char")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + [InlineData("byte")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("bool")] + [InlineData("string")] + public static async Task TestMissingConstantExpectedSupportedComplex2TypesAsync(string type) + { + string csInput = @$" +using System; +using Similar; +#nullable enable + +public class Test +{{ + public interface ITest + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public interface ITest2 + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public abstract class AbstractTest + {{ + public abstract T Method2(T operand1, [ConstantExpected] T operand2); + }} + + public class Generic : AbstractTest<{type}>, ITest<{type}>, ITest2<{type}> + {{ + public {type} Method({type} operand1, {type} operand2) => throw new NotImplementedException(); + {type} ITest2<{type}>.Method({type} operand1, {type} operand2) => throw new NotImplementedException(); + public override {type} Method2({type} operand1, {type} operand2) => throw new NotImplementedException(); + }} +}} +"; + await TestCSMissingAttributeAsync(csInput); + } + + [Fact] + public static async Task TestConstantExpectedSupportedComplex3TypesAsync() + { + string csInput = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{ + public interface ITest + { + T Method(T operand1, [ConstantExpected] T operand2); + } + public interface ITest2 + { + T Method(T operand1, [ConstantExpected] T operand2); + } + public abstract class AbstractTest + { + public abstract T Method2(T operand1, [ConstantExpected] T operand2); + } + public class GenericForward : AbstractTest, ITest, ITest2 + { + public T Method(T operand1, {|#0:T operand2|}) => throw new NotImplementedException(); + T ITest2.Method(T operand1, {|#1:T operand2|}) => throw new NotImplementedException(); + public override T Method2(T operand1, {|#2:T operand2|}) => throw new NotImplementedException(); + } +} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(0), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(1), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.AttributeExpectedRule) + .WithLocation(2)); + } + + [Theory] + [InlineData("", "", "nint", "nint")] + [InlineData("", "", "nuint", "nuint")] + [InlineData("", "", "object", "object")] + [InlineData("", "", "Test", "Test")] + [InlineData("", "", "Guid", "System.Guid")] + [InlineData("", "", "decimal", "decimal")] + [InlineData("", "", "byte[]", "byte[]")] + [InlineData("", "", "(int, long)", "(int, long)")] + [InlineData("", "", "T[]", "T[]")] + [InlineData("", "", "T[]", "T[]")] + public static async Task TestConstantExpectedUnsupportedTypesAsync(string classGeneric, string methodGeneric, string type, string diagnosticType) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test{classGeneric} +{{ + public static void TestMethod{methodGeneric}([{{|#0:ConstantExpected|}}] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.UnsupportedTypeRule) + .WithLocation(0) + .WithArguments(diagnosticType)); + } + + [Theory] + [InlineData("object")] + [InlineData("Test")] + [InlineData("Guid")] + [InlineData("decimal")] + [InlineData("byte[]")] + [InlineData("(int, long)")] + public static async Task TestConstantExpectedUnsupportedIgnoredComplexTypesAsync(string type) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public interface ITest + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public interface ITest2 + {{ + T Method(T operand1, [ConstantExpected] T operand2); + }} + public abstract class AbstractTest + {{ + public abstract T Method2(T operand1, [ConstantExpected] T operand2); + }} + public class Generic : AbstractTest<{type}>, ITest<{type}>, ITest2<{type}> + {{ + public {type} Method({type} operand1, {type} operand2) => throw new NotImplementedException(); + {type} ITest2<{type}>.Method({type} operand1, {type} operand2) => throw new NotImplementedException(); + public override {type} Method2({type} operand1, {type} operand2) => throw new NotImplementedException(); + }} +}} +"; + await TestCSAsync(csInput); + } + + [Theory] + [InlineData("", "", "char", "\"a\"", "\"a\"")] + [InlineData("", "", "sbyte", "\"a\"", "\"a\"")] + [InlineData("", "", "short", "\"a\"", "\"a\"")] + [InlineData("", "", "int", "\"a\"", "\"a\"")] + [InlineData("", "", "long", "\"a\"", "\"a\"")] + [InlineData("", "", "byte", "\"a\"", "\"a\"")] + [InlineData("", "", "ushort", "\"a\"", "\"a\"")] + [InlineData("", "", "uint", "\"a\"", "\"a\"")] + [InlineData("", "", "ulong", "\"a\"", "\"a\"")] + [InlineData("", "", "bool", "\"a\"", "\"a\"")] + [InlineData("", "", "float", "\"a\"", "\"a\"")] + [InlineData("", "", "double", "\"a\"", "\"a\"")] + [InlineData("", "", "string", "true", "false")] + [InlineData("", "", "T", "\"min\"", "false")] + [InlineData("", "", "T", "\"min\"", "false")] + public static async Task TestConstantExpectedIncompatibleConstantTypeErrorAsync(string classGeneric, string methodGeneric, string type, string badMinValue, string badMaxValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test{classGeneric} +{{ + public static void TestMethod{methodGeneric}([ConstantExpected({{|#0:Min = {badMinValue}|}})] {type} val) {{ }} + public static void TestMethod2{methodGeneric}([ConstantExpected({{|#1:Min = {badMinValue}|}}, {{|#2:Max = {badMaxValue}|}})] {type} val) {{ }} + public static void TestMethod3{methodGeneric}([ConstantExpected({{|#3:Max = {badMaxValue}|}})] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(0) + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(1) + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(2) + .WithArguments("Max", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.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("byte", "1", "0")] + [InlineData("ushort", "1", "0")] + [InlineData("uint", "1", "0")] + [InlineData("ulong", "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.CA1856.InvertedRangeRule) + .WithLocation(0)); + } + + [Theory] + [InlineData("sbyte", "AEnum.Five", "AEnum.Two")] + [InlineData("short", "AEnum.Five", "AEnum.Two")] + [InlineData("int", "AEnum.Five", "AEnum.Two")] + [InlineData("long", "AEnum.Five", "AEnum.Two")] + [InlineData("byte", "AEnum.Five", "AEnum.Two")] + [InlineData("ushort", "AEnum.Five", "AEnum.Two")] + [InlineData("uint", "AEnum.Five", "AEnum.Two")] + [InlineData("ulong", "AEnum.Five", "AEnum.Two")] + public static async Task TestEnumConstantExpectedInvertedConstantTypeErrorAsync(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) {{ }} +}} + +public enum AEnum : {type} +{{ + Zero, + One = 1, + Two = 1 << 1, + Three = 1 << 2, + Four = 1 << 3, + Five = 1 << 4 +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvertedRangeRule) + .WithLocation(0)); + } + + [Theory] + [InlineData("sbyte", sbyte.MinValue, sbyte.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("short", short.MinValue, short.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("int", int.MinValue, int.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("long", long.MinValue, long.MaxValue, "ulong.MaxValue", "ulong.MaxValue", "false", "true")] + [InlineData("byte", byte.MinValue, byte.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("ushort", ushort.MinValue, ushort.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("uint", uint.MinValue, uint.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("ulong", ulong.MinValue, ulong.MaxValue, "long.MinValue", "-1", "false", "true")] + [InlineData("float", float.MinValue, float.MaxValue, "double.MinValue", "double.MaxValue", "false", "true")] + public static async Task TestConstantExpectedInvalidBoundsAsync(string type, object min, object max, string min1, string max1, string badMinValue, string badMaxValue) + { + string minString = min.ToString(); + string maxString = max.ToString(); + 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 = {min1}|}}, {{|#2:Max = {max1}|}})] {type} val) {{ }} + public static void TestMethod3([ConstantExpected({{|#3:Max = {max1}|}})] {type} val) {{ }} + public static void TestMethod4([ConstantExpected({{|#4:Min = {badMinValue}|}}, {{|#5:Max = {max1}|}})] {type} val) {{ }} + public static void TestMethod5([ConstantExpected({{|#6:Min = {min1}|}}, {{|#7:Max = {badMaxValue}|}})] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(0) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(1) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(2) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(3) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(4) + .WithArguments("Min", type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(5) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(6) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(7) + .WithArguments("Max", type)); + } + + [Theory] + [InlineData("sbyte", sbyte.MinValue, sbyte.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("short", short.MinValue, short.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("int", int.MinValue, int.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("long", long.MinValue, long.MaxValue, "ulong.MaxValue", "ulong.MaxValue", "false", "true")] + [InlineData("byte", byte.MinValue, byte.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("ushort", ushort.MinValue, ushort.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("uint", uint.MinValue, uint.MaxValue, "long.MinValue", "long.MaxValue", "false", "true")] + [InlineData("ulong", ulong.MinValue, ulong.MaxValue, "long.MinValue", "-1", "false", "true")] + public static async Task TestEnumConstantExpectedInvalidBoundsAsync(string type, object min, object max, string min1, string max1, string badMinValue, string badMaxValue) + { + string minString = min.ToString(); + string maxString = max.ToString(); + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod([ConstantExpected({{|#0:Min = {min1}|}})] AEnum val) {{ }} + public static void TestMethod2([ConstantExpected({{|#1:Min = {min1}|}}, {{|#2:Max = {max1}|}})] AEnum val) {{ }} + public static void TestMethod3([ConstantExpected({{|#3:Max = {max1}|}})] AEnum val) {{ }} + public static void TestMethod4([ConstantExpected({{|#4:Min = {badMinValue}|}}, {{|#5:Max = {max1}|}})] AEnum val) {{ }} + public static void TestMethod5([ConstantExpected({{|#6:Min = {min1}|}}, {{|#7:Max = {badMaxValue}|}})] AEnum val) {{ }} +}} + +public enum AEnum : {type} +{{ + Zero, + One = 1, + Two = 1 << 1, + Three = 1 << 2, + Four = 1 << 3, + Five = 1 << 4 +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(0) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(1) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(2) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(3) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(4) + .WithArguments("Min", "AEnum"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(5) + .WithArguments("Max", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.InvalidBoundsRule) + .WithLocation(6) + .WithArguments("Min", minString, maxString), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1856.IncompatibleConstantTypeRule) + .WithLocation(7) + .WithArguments("Max", "AEnum")); + } + + [Theory] + [InlineData("char", "'A'", "'Z'", "'A'", "(char)('A'+'\\u0001')")] + [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("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("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); + } + + [Theory] + [InlineData("sbyte", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("short", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("int", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("long", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("byte", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("ushort", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("uint", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + [InlineData("ulong", "AEnum.One", "AEnum.Five", "AEnum.Two", "AEnum.One | AEnum.Two")] + public static async Task TestEnumArgumentConstantAsync(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({value}); + TestMethodGeneric({expression}); + GenericClass.TestMethodGeneric({value}); + GenericClass.TestMethodGeneric({expression}); + }} + public static void TestMethodWithConstant([ConstantExpected] AEnum val) {{ }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {minValue}, Max = {maxValue})] AEnum val) {{ }} + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + + public static class GenericClass + {{ + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + }} +}} + +public enum AEnum : {type} +{{ + Zero, + One = 1, + Two = 1 << 1, + Three = 1 << 2, + Four = 1 << 3, + Five = 1 << 4 +}} +"; + await TestCSAsync(csInput); + } + + [Theory] + [InlineData("char")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + [InlineData("byte")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("string")] + public static async Task TestArgumentNotConstantAsync(string type) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod({type} nonConstant) + {{ + TestMethodWithConstant({{|#0:nonConstant|}}); + TestMethodGeneric<{type}>({{|#1:nonConstant|}}); + GenenricClass<{type}>.TestMethodGeneric({{|#2:nonConstant|}}); + }} + public static void TestMethodWithConstant([ConstantExpected] {type} val) {{ }} + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + + public static class GenenricClass + {{ + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(0), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(1), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(2)); + } + + [Theory] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("int")] + [InlineData("long")] + [InlineData("byte")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("ulong")] + public static async Task TestEnumArgumentNotConstantAsync(string type) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod(AEnum nonConstant) + {{ + TestMethodWithConstant({{|#0:nonConstant|}}); + TestMethodGeneric({{|#1:nonConstant|}}); + GenenricClass.TestMethodGeneric({{|#2:nonConstant|}}); + }} + public static void TestMethodWithConstant([ConstantExpected] AEnum val) {{ }} + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + + public static class GenenricClass + {{ + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + }} +}} + +public enum AEnum : {type} +{{ + One, + Two +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(0), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(1), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantNotConstantRule) + .WithLocation(2)); + } + + [Theory] + [InlineData("char", "(char)(object)10.5")] + [InlineData("string", "(string)(object)20")] + public static async Task TestArgumentInvalidConstantAsync(string type, string constant) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod() + {{ + TestMethodWithConstant({{|#0:{constant}|}}); + TestMethodGeneric<{type}>({{|#1:{constant}|}}); + GenericClass<{type}>.TestMethodGeneric({{|#2:{constant}|}}); + }} + public static void TestMethodWithConstant([ConstantExpected] {type} val) {{ }} + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + + public static class GenericClass + {{ + public static void TestMethodGeneric([ConstantExpected] T val) {{ }} + }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(0) + .WithArguments(type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(1) + .WithArguments(type), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(2) + .WithArguments(type)); + } + + [Theory] + [InlineData("char", "'B'", "'C'", "'D'")] + [InlineData("sbyte", "3", "4", "5")] + [InlineData("short", "3", "4", "5")] + [InlineData("int", "3", "4", "5")] + [InlineData("long", "3", "4", "5")] + [InlineData("byte", "3", "4", "5")] + [InlineData("ushort", "3", "4", "5")] + [InlineData("uint", "3", "4", "5")] + [InlineData("ulong", "3", "4", "5")] + [InlineData("float", "3", "4", "5")] + [InlineData("double", "3", "4", "5")] + public static async Task TestArgumentOutOfBoundsConstantAsync(string type, string min, string max, string testValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod() + {{ + TestMethodWithConstant({{|#0:{testValue}|}}); + }} + public static void TestMethodWithConstant([ConstantExpected(Min = {min}, Max = {max})] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantOutOfBoundsRule) + .WithLocation(0) + .WithArguments(min.Trim('\''), max.Trim('\''))); + } + + [Theory] + [InlineData("sbyte", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("short", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("int", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("long", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("byte", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("ushort", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("uint", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + [InlineData("ulong", "AEnum.Three", "AEnum.Four", "AEnum.Five")] + public static async Task TestEnumArgumentOutOfBoundsConstantAsync(string type, string min, string max, string testValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod() + {{ + TestMethodWithConstant({{|#0:{testValue}|}}); + }} + public static void TestMethodWithConstant([ConstantExpected(Min = {min}, Max = {max})] AEnum val) {{ }} +}} + +public enum AEnum : {type} +{{ + Zero, + One, + Two, + Three, + Four, + Five +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantOutOfBoundsRule) + .WithLocation(0) + .WithArguments("3", "4")); + } + + [Fact] + public static async Task TestArgumentInvalidGenericTypeParameterConstantAsync() + { + string csInput = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{ + public static void TestMethod(int[] nonConstant) + { + TestMethodGeneric(nonConstant); // ignore scenario + GenericClass.TestMethodGeneric(nonConstant); // ignore scenario + } + public static void TestMethodGeneric([ConstantExpected] T val) { } + + public static class GenericClass + { + public static void TestMethodGeneric([ConstantExpected] T val) { } + } +} +"; + await TestCSAsync(csInput); + } + + [Theory] + [InlineData("char", "'B'", "'C'")] + [InlineData("sbyte", "3", "4")] + [InlineData("short", "3", "4")] + [InlineData("int", "3", "4")] + [InlineData("long", "3", "4")] + [InlineData("byte", "3", "4")] + [InlineData("ushort", "3", "4")] + [InlineData("uint", "3", "4")] + [InlineData("ulong", "3", "4")] + [InlineData("float", "3", "4")] + [InlineData("double", "3", "4")] + [InlineData("bool", "false", "false")] + public static async Task TestConstantCompositionAsync(string type, string min, string max) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod([ConstantExpected] {type} constant) + {{ + TestMethodWithConstant(constant); + }} + public static void TestMethodWithConstant([ConstantExpected] {type} val) {{ }} + public static void TestMethodConstrained([ConstantExpected(Min = {min}, Max = {max})] {type} constant) + {{ + TestMethodWithConstant(constant); + TestMethodWithConstrainedConstant(constant); + }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {min}, Max = {max})] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput); + } + + [Theory] + [InlineData("sbyte", "AEnum.Two", "AEnum.Three")] + [InlineData("short", "AEnum.Two", "AEnum.Three")] + [InlineData("int", "AEnum.Two", "AEnum.Three")] + [InlineData("long", "AEnum.Two", "AEnum.Three")] + [InlineData("byte", "AEnum.Two", "AEnum.Three")] + [InlineData("ushort", "AEnum.Two", "AEnum.Three")] + [InlineData("uint", "AEnum.Two", "AEnum.Three")] + [InlineData("ulong", "AEnum.Two", "AEnum.Three")] + public static async Task TestEnumConstantCompositionAsync(string type, string min, string max) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethod([ConstantExpected] AEnum constant) + {{ + TestMethodWithConstant(constant); + }} + public static void TestMethodWithConstant([ConstantExpected] AEnum val) {{ }} + public static void TestMethodConstrained([ConstantExpected(Min = {min}, Max = {max})] AEnum constant) + {{ + TestMethodWithConstant(constant); + TestMethodWithConstrainedConstant(constant); + }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {min}, Max = {max})] AEnum val) {{ }} +}} + +public enum AEnum : {type} +{{ + Zero, + One = 1, + Two = 1 << 1, + Three = 1 << 2, + Four = 1 << 3, + Five = 1 << 4 +}} +"; + 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); + } + + [Theory] + [InlineData("char", "'B'", "'C'", "'D'", 'B', 'C')] + [InlineData("sbyte", "3", "4", "5", 3, 4)] + [InlineData("short", "3", "4", "5", 3, 4)] + [InlineData("int", "3", "4", "5", 3, 4)] + [InlineData("long", "3", "4", "5", 3, 4)] + [InlineData("byte", "3", "4", "5", 3, 4)] + [InlineData("ushort", "3", "4", "5", 3, 4)] + [InlineData("uint", "3", "4", "5", 3, 4)] + [InlineData("ulong", "3", "4", "5", 3, 4)] + [InlineData("float", "3", "4", "5", 3, 4)] + [InlineData("double", "3", "4", "5", 3, 4)] + [InlineData("bool", "false", "false", "true", false, false)] + public static async Task TestConstantCompositionOutOfBoundsAsync(string type, string min, string max, string outOfBoundMax, object minValue, object maxValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethodConstrained([ConstantExpected(Min = {min}, Max = {outOfBoundMax})] {type} constant) + {{ + TestMethodWithConstrainedConstant({{|#0:constant|}}); + }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {min}, Max = {max})] {type} val) {{ }} +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantOutOfBoundsRule) + .WithLocation(0) + .WithArguments(minValue.ToString(), maxValue.ToString())); + } + + [Theory] + [InlineData("sbyte", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("short", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("int", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("long", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("byte", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("ushort", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("uint", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + [InlineData("ulong", "AEnum.Two", "AEnum.Three", "AEnum.Four", 2, 4)] + public static async Task TestEnumConstantCompositionOutOfBoundsAsync(string type, string min, string max, string outOfBoundMax, object minValue, object maxValue) + { + string csInput = @$" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{{ + public static void TestMethodConstrained([ConstantExpected(Min = {min}, Max = {outOfBoundMax})] AEnum constant) + {{ + TestMethodWithConstrainedConstant({{|#0:constant|}}); + }} + public static void TestMethodWithConstrainedConstant([ConstantExpected(Min = {min}, Max = {max})] AEnum val) {{ }} +}} + +public enum AEnum : {type} +{{ + Zero, + One = 1, + Two = 1 << 1, + Three = 1 << 2, + Four = 1 << 3, + Five = 1 << 4 +}} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantOutOfBoundsRule) + .WithLocation(0) + .WithArguments(minValue.ToString(), maxValue.ToString())); + } + + [Fact] + public static async Task TestConstantCompositionNotSameTypeAsync() + { + string csInput = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +public class Test +{ + public static void TestMethod([ConstantExpected] long constant) + { + TestMethodWithConstant({|#0:(int)constant|}); + TestMethodWithStringConstant({|#1:(string)(object)constant|}); + } + public static void TestMethod([ConstantExpected] short constant) + { + TestMethodWithConstant({|#2:constant|}); + } + public static void TestMethodWithConstant([ConstantExpected] int val) { } + public static void TestMethodWithStringConstant([ConstantExpected] string val) { } +} +"; + await TestCSAsync(csInput, + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(0) + .WithArguments("int"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(1) + .WithArguments("string"), + VerifyCS.Diagnostic(ConstantExpectedAnalyzer.CA1857.ConstantInvalidConstantRule) + .WithLocation(2) + .WithArguments("int")); + } + + private static async Task TestCSAsync(string source, params DiagnosticResult[] diagnosticResults) + { + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.Preview, + }; + test.ExpectedDiagnostics.AddRange(diagnosticResults); + await test.RunAsync(); + } + + private static async Task TestCSMissingAttributeAsync(string source, params DiagnosticResult[] diagnosticResults) + { + var test = new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.Preview, + }; + test.TestState.Sources.Add(s_similarAttributeSource); + test.ExpectedDiagnostics.AddRange(diagnosticResults); + await test.RunAsync(); + } + + private static readonly string s_similarAttributeSource = @"#nullable enable +using System; +namespace Similar +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + public sealed class ConstantExpectedAttribute : Attribute + { + public object? Min { get; set; } + public object? Max { get; set; } + } +}"; + } +} diff --git a/src/PerformanceTests/Tests/Enabled/CSharp_CA1856.cs b/src/PerformanceTests/Tests/Enabled/CSharp_CA1856.cs new file mode 100644 index 0000000000..feb838880a --- /dev/null +++ b/src/PerformanceTests/Tests/Enabled/CSharp_CA1856.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.NetCore.CSharp.Analyzers.Performance; +using PerformanceTests.Utilities; +using PerfUtilities; + +namespace CSharpPerformanceTests.Enabled +{ + public class CSharp_CA1856 + { + [IterationSetup] + public static void CreateEnvironmentCA1856() + { + var sources = new List<(string name, string content)>(); + string attributeSource = @"#nullable enable +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + public sealed class ConstantExpectedAttribute : Attribute + { + public object? Min { get; set; } + public object? Max { get; set; } + } +}"; + sources.Add(("attributeSource", attributeSource)); + + for (var i = 0; i < Constants.Number_Of_Code_Files; i++) + { + var name = "TypeName" + i; + sources.Add((name, @$" +using System; +using System.Diagnostics.CodeAnalysis; + +class {name} +{{ + public void Test([ConstantExpected(Min = 30, Max = 15)] int val) // diagnostic x2 + {{ + }} + + public void Test2([ConstantExpected(Min = 0, Max = 15)] int val) // no diagnostic + {{ + }} +}} +")); + } + + var compilation = CSharpCompilationHelper.CreateAsync(sources.ToArray()).GetAwaiter().GetResult(); + BaselineCompilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(new EmptyAnalyzer())); + CompilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(new CSharpConstantExpectedAnalyzer())); + } + + private static CompilationWithAnalyzers BaselineCompilationWithAnalyzers; + private static CompilationWithAnalyzers CompilationWithAnalyzers; + + [Benchmark] + public async Task CA1856_DiagnosticsProduced() + { + var analysisResult = await CompilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None); + var diagnostics = analysisResult.GetAllDiagnostics(analysisResult.Analyzers.First()); + if (analysisResult.Analyzers.Length != 1) + { + throw new InvalidOperationException($"Expected a single analyzer but found '{analysisResult.Analyzers.Length}'"); + } + + if (analysisResult.CompilationDiagnostics.Count != 0) + { + throw new InvalidOperationException($"Expected no compilation diagnostics but found '{analysisResult.CompilationDiagnostics.Count}'"); + } + + if (diagnostics.Length != 2 * Constants.Number_Of_Code_Files) + { + throw new InvalidOperationException($"Expected '{2 * Constants.Number_Of_Code_Files:N0}' analyzer diagnostics but found '{diagnostics.Length}'"); + } + } + + [Benchmark(Baseline = true)] + public async Task CA1856_Baseline() + { + var analysisResult = await BaselineCompilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None); + var diagnostics = analysisResult.GetAllDiagnostics(analysisResult.Analyzers.First()); + if (analysisResult.Analyzers.Length != 1) + { + throw new InvalidOperationException($"Expected a single analyzer but found '{analysisResult.Analyzers.Length}'"); + } + + if (analysisResult.CompilationDiagnostics.Count != 0) + { + throw new InvalidOperationException($"Expected no compilation diagnostics but found '{analysisResult.CompilationDiagnostics.Count}'"); + } + + if (diagnostics.Length != 0) + { + throw new InvalidOperationException($"Expected no analyzer diagnostics but found '{diagnostics.Length}'"); + } + } + } +} diff --git a/src/PerformanceTests/Tests/Enabled/CSharp_CA1857.cs b/src/PerformanceTests/Tests/Enabled/CSharp_CA1857.cs new file mode 100644 index 0000000000..361979b26f --- /dev/null +++ b/src/PerformanceTests/Tests/Enabled/CSharp_CA1857.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.NetCore.CSharp.Analyzers.Performance; +using PerformanceTests.Utilities; +using PerfUtilities; + +namespace CSharpPerformanceTests.Enabled +{ + public class CSharp_CA1857 + { + [IterationSetup] + public static void CreateEnvironmentCA1856() + { + var sources = new List<(string name, string content)>(); + string attributeSource = @"#nullable enable +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + public sealed class ConstantExpectedAttribute : Attribute + { + public object? Min { get; set; } + public object? Max { get; set; } + } +}"; + sources.Add(("attributeSource", attributeSource)); + + string testClass = @" +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +namespace ConstantExpectedTest +{ + public class Test + { + public void TestMethodInt32([ConstantExpected] int val) { } + public void TestMethodInt32Ex([ConstantExpected(Min = 0, Max = 15)] int val) { } + } + public interface ITestInterface + { + void TestMethod([ConstantExpected] T val) { } + } +} +"; + sources.Add(("testClass", testClass)); + for (var i = 0; i < Constants.Number_Of_Code_Files; i++) + { + var name = "TypeName" + i; + sources.Add((name, @$" +using System; +using ConstantExpectedTest; + +class {name} +{{ + private readonly Test _test; + + public {name}(Test test) + {{ + _test = test; + }} + + public void Test(int a) + {{ + _test.TestMethodInt32(20); + _test.TestMethodInt32Ex(20); // diagnostic + }} + + private sealed class TestImpl : ITestInterface + {{ + public void TestMethod(int val) {{ }} // diagnostic + }} +}} +")); + } + + var compilation = CSharpCompilationHelper.CreateAsync(sources.ToArray()).GetAwaiter().GetResult(); + BaselineCompilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(new EmptyAnalyzer())); + CompilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(new CSharpConstantExpectedAnalyzer())); + } + + private static CompilationWithAnalyzers BaselineCompilationWithAnalyzers; + private static CompilationWithAnalyzers CompilationWithAnalyzers; + + [Benchmark] + public async Task CA1857_DiagnosticsProduced() + { + var analysisResult = await CompilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None); + var diagnostics = analysisResult.GetAllDiagnostics(analysisResult.Analyzers.First()); + if (analysisResult.Analyzers.Length != 1) + { + throw new InvalidOperationException($"Expected a single analyzer but found '{analysisResult.Analyzers.Length}'"); + } + + if (analysisResult.CompilationDiagnostics.Count != 0) + { + throw new InvalidOperationException($"Expected no compilation diagnostics but found '{analysisResult.CompilationDiagnostics.Count}'"); + } + + if (diagnostics.Length != 2 * Constants.Number_Of_Code_Files) + { + throw new InvalidOperationException($"Expected '{2 * Constants.Number_Of_Code_Files:N0}' analyzer diagnostics but found '{diagnostics.Length}'"); + } + } + + [Benchmark(Baseline = true)] + public async Task CA1857_Baseline() + { + var analysisResult = await BaselineCompilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None); + var diagnostics = analysisResult.GetAllDiagnostics(analysisResult.Analyzers.First()); + if (analysisResult.Analyzers.Length != 1) + { + throw new InvalidOperationException($"Expected a single analyzer but found '{analysisResult.Analyzers.Length}'"); + } + + if (analysisResult.CompilationDiagnostics.Count != 0) + { + throw new InvalidOperationException($"Expected no compilation diagnostics but found '{analysisResult.CompilationDiagnostics.Count}'"); + } + + if (diagnostics.Length != 0) + { + throw new InvalidOperationException($"Expected no analyzer diagnostics but found '{diagnostics.Length}'"); + } + } + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index cca82ee27c..00c64a2dd3 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1855 +Performance: HA, CA1800-CA1857 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 016c1f1f5c..fdbfca0d8d 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -197,6 +197,7 @@ internal static class WellKnownTypeNames public const string SystemDateTimeOffset = "System.DateTimeOffset"; public const string SystemDecimal = "System.Decimal"; public const string SystemDiagnosticContractsContract = "System.Diagnostics.Contracts.Contract"; + public const string SystemDiagnosticsCodeAnalysisConstantExpectedAttribute = "System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute"; public const string SystemDiagnosticsCodeAnalysisNotNullAttribute = "System.Diagnostics.CodeAnalysis.NotNullAttribute"; public const string SystemDiagnosticsConditionalAttribute = "System.Diagnostics.ConditionalAttribute"; public const string SystemDiagnosticsContractsPureAttribute = "System.Diagnostics.Contracts.PureAttribute";