From 4fee79ec5181e092fbf89caaf5c77b04e81bcf88 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:32:22 +0000 Subject: [PATCH 1/5] Initial plan From 7cdaee383180acd7c9619ed745cda2ec57cd1b02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:38:39 +0000 Subject: [PATCH 2/5] Add ValidateRange boundary type mismatch rule Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --- Rules/AvoidValidateRangeTypeMismatch.cs | 140 ++++++++++++++++++ Rules/Strings.resx | 12 ++ .../AvoidValidateRangeTypeMismatch.tests.ps1 | 36 +++++ docs/Rules/AvoidValidateRangeTypeMismatch.md | 49 ++++++ docs/Rules/README.md | 2 + 5 files changed, 239 insertions(+) create mode 100644 Rules/AvoidValidateRangeTypeMismatch.cs create mode 100644 Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 create mode 100644 docs/Rules/AvoidValidateRangeTypeMismatch.md diff --git a/Rules/AvoidValidateRangeTypeMismatch.cs b/Rules/AvoidValidateRangeTypeMismatch.cs new file mode 100644 index 000000000..f288b5ba5 --- /dev/null +++ b/Rules/AvoidValidateRangeTypeMismatch.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Management.Automation.Language; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +#if !CORECLR +using System.ComponentModel.Composition; +#endif + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// AvoidValidateRangeTypeMismatch: Detect integer ValidateRange bounds on floating-point and decimal parameters. + /// +#if !CORECLR + [Export(typeof(IScriptRule))] +#endif + public sealed class AvoidValidateRangeTypeMismatch : IScriptRule + { + private static readonly HashSet FloatingPointAndDecimalTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "double", "System.Double", + "single", "float", "System.Single", + "decimal", "System.Decimal" + }; + + private static readonly HashSet IntegralTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "byte", "System.Byte", + "sbyte", "System.SByte", + "short", "System.Int16", + "ushort", "System.UInt16", + "int", "System.Int32", + "uint", "System.UInt32", + "long", "System.Int64", + "ulong", "System.UInt64" + }; + + public IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast is null) + { + throw new ArgumentNullException(Strings.NullAstErrorMessage); + } + + IEnumerable parameterAsts = ast.FindAll(testAst => testAst is ParameterAst, searchNestedScriptBlocks: true); + foreach (ParameterAst parameterAst in parameterAsts) + { + TypeConstraintAst typeConstraint = parameterAst.Attributes.OfType().FirstOrDefault(); + if (typeConstraint == null || !FloatingPointAndDecimalTypes.Contains(typeConstraint.TypeName.FullName)) + { + continue; + } + + foreach (AttributeAst attributeAst in parameterAst.Attributes.OfType()) + { + if (!IsValidateRangeAttribute(attributeAst) + || attributeAst.PositionalArguments.Count < 2 + || !attributeAst.PositionalArguments.Take(2).Any(IsIntegralBoundary)) + { + continue; + } + + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidValidateRangeTypeMismatchError, parameterAst.Name, typeConstraint.TypeName.FullName), + attributeAst.Extent, + GetName(), + DiagnosticSeverity.Warning, + fileName); + } + } + } + + private static bool IsIntegralBoundary(ExpressionAst expressionAst) + { + if (expressionAst is ConstantExpressionAst constantExpressionAst) + { + return constantExpressionAst.Value != null + && IntegralTypes.Contains(constantExpressionAst.Value.GetType().FullName); + } + + if (expressionAst is ConvertExpressionAst convertExpressionAst) + { + return IntegralTypes.Contains(convertExpressionAst.Type.TypeName.FullName) + && IsIntegralBoundary(convertExpressionAst.Child); + } + + if (expressionAst is UnaryExpressionAst unaryExpressionAst + && (unaryExpressionAst.TokenKind == TokenKind.Minus || unaryExpressionAst.TokenKind == TokenKind.Plus)) + { + return IsIntegralBoundary(unaryExpressionAst.Child); + } + + return false; + } + + private static bool IsValidateRangeAttribute(AttributeAst attributeAst) + { + string attributeName = attributeAst.TypeName.FullName; + int lastDot = attributeName.LastIndexOf('.'); + string simpleName = lastDot < 0 ? attributeName : attributeName.Substring(lastDot + 1); + return String.Equals(simpleName, "ValidateRange", StringComparison.OrdinalIgnoreCase) + || String.Equals(simpleName, "ValidateRangeAttribute", StringComparison.OrdinalIgnoreCase); + } + + public string GetName() + { + return String.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidValidateRangeTypeMismatchName); + } + + public string GetCommonName() + { + return Strings.AvoidValidateRangeTypeMismatchCommonName; + } + + public string GetDescription() + { + return Strings.AvoidValidateRangeTypeMismatchDescription; + } + + public SourceType GetSourceType() + { + return SourceType.Builtin; + } + + public RuleSeverity GetSeverity() + { + return RuleSeverity.Warning; + } + + public string GetSourceName() + { + return String.Format(CultureInfo.CurrentCulture, Strings.SourceName); + } + } +} diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 2f2b1e1a9..43d173a90 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -1221,6 +1221,18 @@ AvoidMultipleTypeAttributes + + Avoid mismatched ValidateRange boundary types. + + + Integer ValidateRange boundaries can allow fractional values for floating-point and decimal parameters. Use floating-point or decimal boundaries that match the parameter type. + + + ValidateRange boundaries should match the type of parameter '{0}' ({1}) to correctly restrict fractional values. + + + AvoidValidateRangeTypeMismatch + Avoid Using Broken Hash Algorithms diff --git a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 new file mode 100644 index 000000000..0a307a2ee --- /dev/null +++ b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 @@ -0,0 +1,36 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'AvoidValidateRangeTypeMismatch' { + BeforeAll { + $ruleName = 'PSAvoidValidateRangeTypeMismatch' + } + + It 'reports integral boundaries for floating-point and decimal parameter types' { + $scripts = @( + 'function Test { param([double][ValidateRange(1, 2)]$Value) }' + 'function Test { param([single][ValidateRange(-1, 2)]$Value) }' + 'function Test { param([decimal][ValidateRange(1, 2.0)]$Value) }' + 'function Test { param([double][System.Management.Automation.ValidateRangeAttribute(1, 2)]$Value) }' + ) + + foreach ($scriptDefinition in $scripts) { + $diagnostics = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $ruleName + $diagnostics.Count | Should -Be 1 + $diagnostics[0].Extent.Text | Should -Match 'ValidateRange' + } + } + + It 'ignores matching floating-point boundaries and non-floating-point parameters' { + $scripts = @( + 'function Test { param([double][ValidateRange(1.0, 2.0)]$Value) }' + 'function Test { param([single][ValidateRange(1.0, 2.0)]$Value) }' + 'function Test { param([decimal][ValidateRange(1.0, 2.0)]$Value) }' + 'function Test { param([int][ValidateRange(1, 2)]$Value) }' + ) + + foreach ($scriptDefinition in $scripts) { + Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $ruleName | Should -BeNullOrEmpty + } + } +} diff --git a/docs/Rules/AvoidValidateRangeTypeMismatch.md b/docs/Rules/AvoidValidateRangeTypeMismatch.md new file mode 100644 index 000000000..cd7b265db --- /dev/null +++ b/docs/Rules/AvoidValidateRangeTypeMismatch.md @@ -0,0 +1,49 @@ +--- +description: Detect integer ValidateRange boundaries on floating-point and decimal parameters +ms.topic: reference +title: AvoidValidateRangeTypeMismatch +--- +# AvoidValidateRangeTypeMismatch + +**Severity Level: Warning** + +**Default state: Always enabled** + +## Description + +Integer boundaries in a `ValidateRange` attribute can select integer validation behavior for +floating-point and decimal parameters. This can allow fractional values outside the declared range. +Use boundaries with the same numeric type as the parameter. + +## Example + +### Noncompliant + +```powershell +function Set-Percentage +{ + param([double][ValidateRange(0, 100)]$Value) +} +``` + +### Compliant + +```powershell +function Set-Percentage +{ + param([double][ValidateRange(0.0, 100.0)]$Value) +} +``` + +## Configure rule + +This rule is always enabled and isn't configurable. Use one of the following methods to avoid using +this rule: + +- Create a custom rule configuration file to include only the rules you want or exclude the rules + you don't want. +- Add the appropriate rule suppression attributes to your code to suppress the rule for specific + code blocks. For more information, see the _Suppressing rules_ section of [Using PSScriptAnalyzer][01]. + + +[01]: ../using-scriptanalyzer.md#suppressing-rules diff --git a/docs/Rules/README.md b/docs/Rules/README.md index c35d646d0..b02342821 100644 --- a/docs/Rules/README.md +++ b/docs/Rules/README.md @@ -52,6 +52,7 @@ title: List of PSScriptAnalyzer rules | [AvoidUsingUsernameAndPasswordParams][32] | Error | Always enabled | | | [AvoidUsingWMICmdlet][33] | Warning | Always enabled | | | [AvoidUsingWriteHost][34] | Warning | Always enabled | | +| [AvoidValidateRangeTypeMismatch][81] | Warning | Always enabled | | | [DSCDscExamplesPresent][35] | Information | Always enabled | | | [DSCDscTestsPresent][36] | Information | Always enabled | | | [DSCReturnCorrectTypesForDSCFunctions][37] | Information | Always enabled | | @@ -180,3 +181,4 @@ title: List of PSScriptAnalyzer rules [78]: UseToExportFieldsInManifest.md [79]: UseUsingScopeModifierInNewRunspaces.md [80]: UseUTF8EncodingForHelpFile.md +[81]: AvoidValidateRangeTypeMismatch.md From 117fa6b48a8749e91ca82cbfa217f05c05f120dd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:12:46 +0000 Subject: [PATCH 3/5] Handle arrays and casts in ValidateRange type-mismatch warnings (#2273) * Initial plan * Fix ValidateRange analyzer review findings Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> * Restore decimal range test coverage Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --- Rules/AvoidValidateRangeTypeMismatch.cs | 52 ++++++++++++------- .../AvoidValidateRangeTypeMismatch.tests.ps1 | 6 ++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Rules/AvoidValidateRangeTypeMismatch.cs b/Rules/AvoidValidateRangeTypeMismatch.cs index f288b5ba5..319a865ba 100644 --- a/Rules/AvoidValidateRangeTypeMismatch.cs +++ b/Rules/AvoidValidateRangeTypeMismatch.cs @@ -21,23 +21,23 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #endif public sealed class AvoidValidateRangeTypeMismatch : IScriptRule { - private static readonly HashSet FloatingPointAndDecimalTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet FloatingPointAndDecimalTypes = new HashSet { - "double", "System.Double", - "single", "float", "System.Single", - "decimal", "System.Decimal" + typeof(double), + typeof(float), + typeof(decimal) }; - private static readonly HashSet IntegralTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet IntegralTypes = new HashSet { - "byte", "System.Byte", - "sbyte", "System.SByte", - "short", "System.Int16", - "ushort", "System.UInt16", - "int", "System.Int32", - "uint", "System.UInt32", - "long", "System.Int64", - "ulong", "System.UInt64" + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong) }; public IEnumerable AnalyzeScript(Ast ast, string fileName) @@ -51,7 +51,23 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) foreach (ParameterAst parameterAst in parameterAsts) { TypeConstraintAst typeConstraint = parameterAst.Attributes.OfType().FirstOrDefault(); - if (typeConstraint == null || !FloatingPointAndDecimalTypes.Contains(typeConstraint.TypeName.FullName)) + if (typeConstraint == null) + { + continue; + } + + Type parameterType = typeConstraint.TypeName.GetReflectionType(); + if (parameterType == null) + { + continue; + } + + if (parameterType.IsArray) + { + parameterType = parameterType.GetElementType(); + } + + if (!FloatingPointAndDecimalTypes.Contains(parameterType)) { continue; } @@ -60,7 +76,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (!IsValidateRangeAttribute(attributeAst) || attributeAst.PositionalArguments.Count < 2 - || !attributeAst.PositionalArguments.Take(2).Any(IsIntegralBoundary)) + || !attributeAst.PositionalArguments.Take(2).All(IsIntegralBoundary)) { continue; } @@ -79,14 +95,12 @@ private static bool IsIntegralBoundary(ExpressionAst expressionAst) { if (expressionAst is ConstantExpressionAst constantExpressionAst) { - return constantExpressionAst.Value != null - && IntegralTypes.Contains(constantExpressionAst.Value.GetType().FullName); + return constantExpressionAst.Value != null && IntegralTypes.Contains(constantExpressionAst.Value.GetType()); } if (expressionAst is ConvertExpressionAst convertExpressionAst) { - return IntegralTypes.Contains(convertExpressionAst.Type.TypeName.FullName) - && IsIntegralBoundary(convertExpressionAst.Child); + return IntegralTypes.Contains(convertExpressionAst.Type.TypeName.GetReflectionType()); } if (expressionAst is UnaryExpressionAst unaryExpressionAst diff --git a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 index 0a307a2ee..bc3915240 100644 --- a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 +++ b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 @@ -10,8 +10,11 @@ Describe 'AvoidValidateRangeTypeMismatch' { $scripts = @( 'function Test { param([double][ValidateRange(1, 2)]$Value) }' 'function Test { param([single][ValidateRange(-1, 2)]$Value) }' - 'function Test { param([decimal][ValidateRange(1, 2.0)]$Value) }' + 'function Test { param([decimal][ValidateRange(1, 2)]$Value) }' + 'function Test { param([double[]][ValidateRange(1, 2)]$Values) }' 'function Test { param([double][System.Management.Automation.ValidateRangeAttribute(1, 2)]$Value) }' + 'function Test { param([double][ValidateRange([int]0.0, [int]100.0)]$Value) }' + 'function Test { param([double][ValidateRange([int32]0.0, [int32]100.0)]$Value) }' ) foreach ($scriptDefinition in $scripts) { @@ -26,6 +29,7 @@ Describe 'AvoidValidateRangeTypeMismatch' { 'function Test { param([double][ValidateRange(1.0, 2.0)]$Value) }' 'function Test { param([single][ValidateRange(1.0, 2.0)]$Value) }' 'function Test { param([decimal][ValidateRange(1.0, 2.0)]$Value) }' + 'function Test { param([double][ValidateRange(1, 2.0)]$Value) }' 'function Test { param([int][ValidateRange(1, 2)]$Value) }' ) From 919cef7c29b2aee0d0ef944688350e119b2d24a7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:14:53 +0000 Subject: [PATCH 4/5] Recognize static integral ValidateRange bounds (#2278) * Initial plan * Handle ValidateRange static integral bounds Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> * Fix ValidateRange review edge cases Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> * Add real Contoso.ValidateRangeAttribute to test fixture Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> * Test non-constant ValidateRange bounds Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --- Rules/AvoidValidateRangeTypeMismatch.cs | 17 ++++-- .../AvoidValidateRangeTypeMismatch.tests.ps1 | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Rules/AvoidValidateRangeTypeMismatch.cs b/Rules/AvoidValidateRangeTypeMismatch.cs index 319a865ba..aa48db75f 100644 --- a/Rules/AvoidValidateRangeTypeMismatch.cs +++ b/Rules/AvoidValidateRangeTypeMismatch.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Management.Automation.Language; +using System.Reflection; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; #if !CORECLR using System.ComponentModel.Composition; @@ -109,16 +110,22 @@ private static bool IsIntegralBoundary(ExpressionAst expressionAst) return IsIntegralBoundary(unaryExpressionAst.Child); } + if (expressionAst is MemberExpressionAst memberExpressionAst + && memberExpressionAst.Static + && memberExpressionAst.Expression is TypeExpressionAst typeExpressionAst + && memberExpressionAst.Member is StringConstantExpressionAst memberName) + { + Type type = typeExpressionAst.TypeName.GetReflectionType(); + FieldInfo field = type?.GetField(memberName.Value, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy); + return field != null && field.IsLiteral && IntegralTypes.Contains(field.FieldType); + } + return false; } private static bool IsValidateRangeAttribute(AttributeAst attributeAst) { - string attributeName = attributeAst.TypeName.FullName; - int lastDot = attributeName.LastIndexOf('.'); - string simpleName = lastDot < 0 ? attributeName : attributeName.Substring(lastDot + 1); - return String.Equals(simpleName, "ValidateRange", StringComparison.OrdinalIgnoreCase) - || String.Equals(simpleName, "ValidateRangeAttribute", StringComparison.OrdinalIgnoreCase); + return attributeAst.TypeName.GetReflectionAttributeType() == typeof(System.Management.Automation.ValidateRangeAttribute); } public string GetName() diff --git a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 index bc3915240..24e6ad129 100644 --- a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 +++ b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 @@ -4,6 +4,39 @@ Describe 'AvoidValidateRangeTypeMismatch' { BeforeAll { $ruleName = 'PSAvoidValidateRangeTypeMismatch' + Add-Type -TypeDefinition @' +public class ValidateRangeBoundsBaseForScriptAnalyzer +{ + public const int Max = 100; +} + +public class ValidateRangeBoundsForScriptAnalyzer : ValidateRangeBoundsBaseForScriptAnalyzer +{ + public static readonly int ReadonlyIntegral = 100; + public const double FloatingPoint = 100.0; + public static int ThrowingGetterInvocationCount; + + public static int ThrowingProperty + { + get + { + ThrowingGetterInvocationCount++; + throw new System.InvalidOperationException(); + } + } +} + +namespace Contoso +{ + [System.AttributeUsage(System.AttributeTargets.All)] + public class ValidateRangeAttribute : System.Attribute + { + public ValidateRangeAttribute(int minRange, int maxRange) + { + } + } +} +'@ } It 'reports integral boundaries for floating-point and decimal parameter types' { @@ -15,6 +48,9 @@ Describe 'AvoidValidateRangeTypeMismatch' { 'function Test { param([double][System.Management.Automation.ValidateRangeAttribute(1, 2)]$Value) }' 'function Test { param([double][ValidateRange([int]0.0, [int]100.0)]$Value) }' 'function Test { param([double][ValidateRange([int32]0.0, [int32]100.0)]$Value) }' + 'function Test { param([double][ValidateRange(0, [int]::MaxValue)]$Value) }' + 'function Test { param([double][ValidateRange(0, [ValidateRangeBoundsForScriptAnalyzer]::Max)]$Value) }' + 'function Test { param([double][System.Management.Automation.ValidateRange(0, 100)]$Value) }' ) foreach ($scriptDefinition in $scripts) { @@ -30,6 +66,7 @@ Describe 'AvoidValidateRangeTypeMismatch' { 'function Test { param([single][ValidateRange(1.0, 2.0)]$Value) }' 'function Test { param([decimal][ValidateRange(1.0, 2.0)]$Value) }' 'function Test { param([double][ValidateRange(1, 2.0)]$Value) }' + 'function Test { param([double][Contoso.ValidateRange(0, 100)]$Value) }' 'function Test { param([int][ValidateRange(1, 2)]$Value) }' ) @@ -37,4 +74,19 @@ Describe 'AvoidValidateRangeTypeMismatch' { Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $ruleName | Should -BeNullOrEmpty } } + + It 'ignores non-constant static member expressions without invoking getters' { + $scripts = @( + 'function Test { param([double][ValidateRange(0, [ValidateRangeBoundsForScriptAnalyzer]::ThrowingProperty)]$Value) }' + 'function Test { param([double][ValidateRange(0, [ValidateRangeBoundsForScriptAnalyzer]::ReadonlyIntegral)]$Value) }' + 'function Test { param([double][ValidateRange(0, [ValidateRangeBoundsForScriptAnalyzer]::FloatingPoint)]$Value) }' + ) + + foreach ($scriptDefinition in $scripts) { + Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $ruleName | + Where-Object RuleName -eq $ruleName | Should -BeNullOrEmpty + } + + [ValidateRangeBoundsForScriptAnalyzer]::ThrowingGetterInvocationCount | Should -Be 0 + } } From 32bcb6759b76fb7604330cd6f4d496abe2d0292d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:27:08 +0000 Subject: [PATCH 5/5] Handle nullable ValidateRange parameter types Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --- Rules/AvoidValidateRangeTypeMismatch.cs | 2 ++ Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Rules/AvoidValidateRangeTypeMismatch.cs b/Rules/AvoidValidateRangeTypeMismatch.cs index aa48db75f..d23f51243 100644 --- a/Rules/AvoidValidateRangeTypeMismatch.cs +++ b/Rules/AvoidValidateRangeTypeMismatch.cs @@ -68,6 +68,8 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) parameterType = parameterType.GetElementType(); } + parameterType = Nullable.GetUnderlyingType(parameterType) ?? parameterType; + if (!FloatingPointAndDecimalTypes.Contains(parameterType)) { continue; diff --git a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 index 24e6ad129..767da3add 100644 --- a/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 +++ b/Tests/Rules/AvoidValidateRangeTypeMismatch.tests.ps1 @@ -44,6 +44,9 @@ namespace Contoso 'function Test { param([double][ValidateRange(1, 2)]$Value) }' 'function Test { param([single][ValidateRange(-1, 2)]$Value) }' 'function Test { param([decimal][ValidateRange(1, 2)]$Value) }' + 'function Test { param([Nullable[double]][ValidateRange(1, 2)]$Value) }' + 'function Test { param([Nullable[single]][ValidateRange(1, 2)]$Value) }' + 'function Test { param([Nullable[decimal]][ValidateRange(1, 2)]$Value) }' 'function Test { param([double[]][ValidateRange(1, 2)]$Values) }' 'function Test { param([double][System.Management.Automation.ValidateRangeAttribute(1, 2)]$Value) }' 'function Test { param([double][ValidateRange([int]0.0, [int]100.0)]$Value) }'