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,
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
1311using System ;
1412using System . Collections . Generic ;
15- using System . Linq ;
13+ using System . ComponentModel . Composition ;
1614using System . Management . Automation ;
1715using System . Management . Automation . Language ;
18- using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
19- using System . ComponentModel . Composition ;
2016using System . Globalization ;
17+ using System . Linq ;
18+ using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
2119
2220namespace 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}
0 commit comments