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) }'