Skip to content

Commit 96fa8d0

Browse files
author
Kapil Borle
committed
Update UseShouldProcessForStateChangingFunctions rule implemetation
This commit makes the following changes. * Extent includes only the function name instead of the entire function definition * Use GetReflectionTypeAttribute instead of GetReflectionType to compare with the type of CmdletBindingAttribute * Iterate through the attributes only once * Instead of checking for $true for SupportShouldProcess attribute, check for any constant that can be converted to true
1 parent ae530a8 commit 96fa8d0

4 files changed

+60
-33
lines changed

Rules/UseShouldProcessForStateChangingFunctions.cs

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5656
{
5757
yield return new DiagnosticRecord(
5858
string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsError, funcDefAst.Name),
59-
funcDefAst.Extent,
59+
Helper.Instance.GetScriptExtentForFunctionName(funcDefAst),
6060
this.GetName(),
6161
DiagnosticSeverity.Warning,
6262
fileName);
@@ -80,12 +80,9 @@ private bool IsStateChangingFunctionWithNoShouldProcessAttribute(Ast ast)
8080
elem => funcName.StartsWith(
8181
elem,
8282
StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
83-
if (targetFuncName == null
84-
|| funcDefAst.Body == null
83+
if (targetFuncName == null
8584
|| funcDefAst.Body.ParamBlock == null
86-
|| funcDefAst.Body.ParamBlock.Attributes == null
87-
|| !funcDefAst.Body.ParamBlock.Attributes.Any(
88-
attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute)))
85+
|| funcDefAst.Body.ParamBlock.Attributes == null)
8986
{
9087
return false;
9188
}
@@ -99,44 +96,32 @@ private bool IsStateChangingFunctionWithNoShouldProcessAttribute(Ast ast)
9996
/// <returns>Returns true or false</returns>
10097
private bool HasShouldProcessTrue(IEnumerable<AttributeAst> attributeAsts)
10198
{
102-
if (attributeAsts == null)
103-
{
104-
return false;
105-
}
10699
foreach (var attributeAst in attributeAsts)
107100
{
108101
if (attributeAst == null || attributeAst.NamedArguments == null)
109102
{
110103
continue;
111104
}
112-
foreach (var namedAttributeAst in attributeAst.NamedArguments)
105+
if (attributeAst.TypeName.GetReflectionAttributeType()
106+
== typeof(CmdletBindingAttribute))
113107
{
114-
if (namedAttributeAst == null)
115-
{
116-
continue;
117-
}
118-
if (!IsShouldProcessAttribute(namedAttributeAst))
108+
foreach (var namedAttributeAst in attributeAst.NamedArguments)
119109
{
120-
continue;
110+
if (namedAttributeAst == null
111+
|| !namedAttributeAst.ArgumentName.Equals(
112+
"SupportsShouldProcess",
113+
StringComparison.OrdinalIgnoreCase))
114+
{
115+
continue;
116+
}
117+
return IsShouldProcessTrue(namedAttributeAst);
121118
}
122-
return IsShouldProcessTrue(namedAttributeAst);
123119
}
124120
}
125121
// Cannot find any SupportShouldProcess attribute
126122
return false;
127123
}
128124

129-
/// <summary>
130-
/// Checks if the named attribute is SupportShouldProcess
131-
/// </summary>
132-
/// <param name="namedAttributeArgumentAst"></param>
133-
/// <returns>Returns true or false</returns>
134-
private bool IsShouldProcessAttribute(NamedAttributeArgumentAst namedAttributeArgumentAst)
135-
{
136-
return namedAttributeArgumentAst != null
137-
&& namedAttributeArgumentAst.ArgumentName.Equals("SupportsShouldProcess", StringComparison.OrdinalIgnoreCase);
138-
}
139-
140125
/// <summary>
141126
/// Checks if the SupportShouldProcess attribute is true
142127
/// </summary>
@@ -151,11 +136,25 @@ private bool IsShouldProcessTrue(NamedAttributeArgumentAst namedAttributeArgumen
151136
else
152137
{
153138
var varExpAst = namedAttributeArgumentAst.Argument as VariableExpressionAst;
154-
if (varExpAst != null
155-
&& varExpAst.VariablePath.UserPath.Equals("true", StringComparison.OrdinalIgnoreCase))
139+
if (varExpAst == null)
156140
{
157-
return true;
141+
var constExpAst = namedAttributeArgumentAst.Argument as ConstantExpressionAst;
142+
if (constExpAst == null)
143+
{
144+
return false;
145+
}
146+
bool constExpVal;
147+
if (LanguagePrimitives.TryConvertTo<bool>(constExpAst.Value, out constExpVal))
148+
{
149+
return constExpVal;
150+
}
158151
}
152+
else if (varExpAst.VariablePath.UserPath.Equals(
153+
bool.TrueString,
154+
StringComparison.OrdinalIgnoreCase))
155+
{
156+
return true;
157+
}
159158
}
160159
return false;
161160
}

Tests/Rules/UseShouldProcessForStateChangingFunctions.ps1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
}
66

7+
function Stop-MyObject{
8+
[CmdletBinding(SupportsShouldProcess = 0)]
9+
param([string]$c, [int]$d)
10+
11+
}
12+
13+
function New-MyObject{
14+
[CmdletBinding(SupportsShouldProcess = "")]
15+
param([string]$c, [int]$d)
16+
17+
}
18+
719
function Set-MyObject{
820
[CmdletBinding()]
921
param([string]$c, [int]$d)

Tests/Rules/UseShouldProcessForStateChangingFunctions.tests.ps1

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ $noViolations = Invoke-ScriptAnalyzer $directory\UseShouldProcessForStateChangin
77

88
Describe "It checks UseShouldProcess is enabled when there are state changing verbs in the function names" {
99
Context "When there are violations" {
10-
$numViolations = 3
10+
$numViolations = 5
1111
It ("has {0} violations where ShouldProcess is not supported" -f $numViolations) {
1212
$violations.Count | Should Be $numViolations
1313
}
1414

1515
It "has the correct description message" {
1616
$violations[0].Message | Should Match $violationMessage
1717
}
18+
19+
It "has the correct extent" {
20+
$violations[0].Extent.Text | Should Be "Set-MyObject"
21+
}
1822
}
1923

2024
Context "When there are no violations" {

Tests/Rules/UseShouldProcessForStateChangingFunctionsNoViolations.ps1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
param ([string]$c)
55
}
66

7+
function Stop-MyObject{
8+
[CmdletBinding(SupportsShouldProcess = 1)]
9+
param([string]$c, [int]$d)
10+
11+
}
12+
13+
function New-MyObject{
14+
[CmdletBinding(SupportsShouldProcess = "a")]
15+
param([string]$c, [int]$d)
16+
17+
}
18+
719
function Test-GetMyObject{
820

921
param([string]$c, [int]$d)

0 commit comments

Comments
 (0)