Skip to content

Commit f17b521

Browse files
author
Kapil Borle
committed
Add 'remove' to UseShouldProcessForStateChangingFunctions rule
The rule now checks for the following verbs: * Remove * Restart * Stop * New * Set * Update * Reset
1 parent 14eaf70 commit f17b521

File tree

3 files changed

+57
-32
lines changed

3 files changed

+57
-32
lines changed
Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//
2-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
32
//
43
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -8,24 +7,38 @@
87
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
98
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
109
// THE SOFTWARE.
11-
//
1210

1311
using System;
1412
using System.Collections.Generic;
15-
using System.Linq;
13+
using System.ComponentModel.Composition;
1614
using System.Management.Automation;
1715
using System.Management.Automation.Language;
18-
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
19-
using System.ComponentModel.Composition;
2016
using System.Globalization;
17+
using System.Linq;
18+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
2119

2220
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
23-
{/// <summary>
21+
{
22+
/// <summary>
2423
/// UseShouldProcessForStateChangingFunctions: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state.
2524
/// </summary>
2625
[Export(typeof(IScriptRule))]
2726
public class UseShouldProcessForStateChangingFunctions : IScriptRule
2827
{
28+
/// <summary>
29+
/// Array of verbs that can potentially change the state of a system
30+
/// </summary>
31+
private string[] stateChangingVerbs =
32+
{
33+
"Restart-",
34+
"Stop-",
35+
"New-",
36+
"Set-",
37+
"Update-",
38+
"Reset-",
39+
"Remove-"
40+
};
41+
2942
/// <summary>
3043
/// AnalyzeScript: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state.
3144
/// </summary>
@@ -34,39 +47,45 @@ public class UseShouldProcessForStateChangingFunctions : IScriptRule
3447
/// <returns>A List of diagnostic results of this rule</returns>
3548
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
3649
{
37-
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
38-
39-
IEnumerable<Ast> funcDefAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true);
40-
string supportsShouldProcess = "SupportsShouldProcess";
41-
string trueString = "$true";
50+
if (ast == null)
51+
{
52+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
53+
}
54+
IEnumerable<Ast> funcDefAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true);
4255
foreach (FunctionDefinitionAst funcDefAst in funcDefAsts)
4356
{
4457
string funcName = funcDefAst.Name;
4558
bool hasShouldProcessAttribute = false;
46-
47-
if (funcName.StartsWith("Restart-", StringComparison.OrdinalIgnoreCase) ||
48-
funcName.StartsWith("Stop-", StringComparison.OrdinalIgnoreCase)||
49-
funcName.StartsWith("New-", StringComparison.OrdinalIgnoreCase) ||
50-
funcName.StartsWith("Set-", StringComparison.OrdinalIgnoreCase) ||
51-
funcName.StartsWith("Update-", StringComparison.OrdinalIgnoreCase) ||
52-
funcName.StartsWith("Reset-", StringComparison.OrdinalIgnoreCase))
59+
var targetFuncName = this.stateChangingVerbs.Where(
60+
elem => funcName.StartsWith(
61+
elem,
62+
StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
63+
if (targetFuncName != null)
5364
{
5465
IEnumerable<Ast> attributeAsts = funcDefAst.FindAll(testAst => testAst is NamedAttributeArgumentAst, true);
5566
if (funcDefAst.Body != null && funcDefAst.Body.ParamBlock != null
5667
&& funcDefAst.Body.ParamBlock.Attributes != null &&
5768
funcDefAst.Body.ParamBlock.Parameters != null)
5869
{
59-
if (!funcDefAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof (CmdletBindingAttribute)))
70+
if (!funcDefAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute)))
6071
{
6172
continue;
6273
}
6374

6475
foreach (NamedAttributeArgumentAst attributeAst in attributeAsts)
6576
{
66-
if (attributeAst.ArgumentName.Equals(supportsShouldProcess, StringComparison.OrdinalIgnoreCase))
77+
if (attributeAst.ArgumentName.Equals("SupportsShouldProcess", StringComparison.OrdinalIgnoreCase))
6778
{
68-
if((attributeAst.Argument.Extent.Text.Equals(trueString, StringComparison.OrdinalIgnoreCase)) && !attributeAst.ExpressionOmitted ||
69-
attributeAst.ExpressionOmitted)
79+
if (!attributeAst.ExpressionOmitted)
80+
{
81+
var varExpAst = attributeAst.Argument as VariableExpressionAst;
82+
if (varExpAst != null
83+
&& varExpAst.VariablePath.UserPath.Equals("true", StringComparison.OrdinalIgnoreCase))
84+
{
85+
hasShouldProcessAttribute = true;
86+
}
87+
}
88+
else
7089
{
7190
hasShouldProcessAttribute = true;
7291
}
@@ -76,7 +95,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
7695
if (!hasShouldProcessAttribute)
7796
{
7897
yield return
79-
new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture,Strings.UseShouldProcessForStateChangingFunctionsError, funcName),funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
98+
new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsError, funcName), funcDefAst.Extent, this.GetName(), DiagnosticSeverity.Warning, fileName);
8099
}
81100
}
82101
}
@@ -89,7 +108,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
89108
/// <returns>The name of this rule</returns>
90109
public string GetName()
91110
{
92-
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseShouldProcessForStateChangingFunctionsName);
111+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, this.GetSourceName(), Strings.UseShouldProcessForStateChangingFunctionsName);
93112
}
94113

95114
/// <summary>
@@ -111,8 +130,9 @@ public string GetDescription()
111130
}
112131

113132
/// <summary>
114-
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
133+
/// GetSourceType: Retrieves the type of the rule: built-in, managed or module.
115134
/// </summary>
135+
/// <returns>Source type {PS, PSDSC}</returns>
116136
public SourceType GetSourceType()
117137
{
118138
return SourceType.Builtin;
@@ -121,20 +141,19 @@ public SourceType GetSourceType()
121141
/// <summary>
122142
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
123143
/// </summary>
124-
/// <returns></returns>
144+
/// <returns>Rule severity {Information, Warning, Error}</returns>
125145
public RuleSeverity GetSeverity()
126146
{
127147
return RuleSeverity.Warning;
128148
}
129149

130-
131150
/// <summary>
132151
/// GetSourceName: Retrieves the module/assembly name the rule is from.
133152
/// </summary>
153+
/// <returns>Source name</returns>
134154
public string GetSourceName()
135155
{
136156
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
137157
}
138158
}
139-
140159
}

Tests/Rules/UseShouldProcessForStateChangingFunctions.ps1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@ function Set-MyObject{
88
[CmdletBinding()]
99
param([string]$c, [int]$d)
1010

11-
}
11+
}
12+
13+
function Remove-MyObject{
14+
[CmdletBinding()]
15+
param([string]$c, [int]$d)
16+
}

Tests/Rules/UseShouldProcessForStateChangingFunctions.tests.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ $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-
It "has 2 violations where ShouldProcess is not supported" {
11-
$violations.Count | Should Be 2
10+
$numViolations = 3
11+
It ("has {0} violations where ShouldProcess is not supported" -f $numViolations) {
12+
$violations.Count | Should Be $numViolations
1213
}
1314

1415
It "has the correct description message" {

0 commit comments

Comments
 (0)