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 ) ) ]
@@ -34,53 +33,53 @@ public class UseShouldProcessForStateChangingFunctions : IScriptRule
3433 /// <returns>A List of diagnostic results of this rule</returns>
3534 public IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
3635 {
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" ;
42- foreach ( FunctionDefinitionAst funcDefAst in funcDefAsts )
36+ if ( ast == null )
4337 {
44- string funcName = funcDefAst . Name ;
45- 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 ) )
53- {
54- IEnumerable < Ast > attributeAsts = funcDefAst . FindAll ( testAst => testAst is NamedAttributeArgumentAst , true ) ;
55- if ( funcDefAst . Body != null && funcDefAst . Body . ParamBlock != null
56- && funcDefAst . Body . ParamBlock . Attributes != null &&
57- funcDefAst . Body . ParamBlock . Parameters != null )
58- {
59- if ( ! funcDefAst . Body . ParamBlock . Attributes . Any ( attr => attr . TypeName . GetReflectionType ( ) == typeof ( CmdletBindingAttribute ) ) )
60- {
61- continue ;
62- }
63-
64- foreach ( NamedAttributeArgumentAst attributeAst in attributeAsts )
65- {
66- if ( attributeAst . ArgumentName . Equals ( supportsShouldProcess , StringComparison . OrdinalIgnoreCase ) )
67- {
68- if ( ( attributeAst . Argument . Extent . Text . Equals ( trueString , StringComparison . OrdinalIgnoreCase ) ) && ! attributeAst . ExpressionOmitted ||
69- attributeAst . ExpressionOmitted )
70- {
71- hasShouldProcessAttribute = true ;
72- }
73- }
74- }
38+ throw new ArgumentNullException ( Strings . NullAstErrorMessage ) ;
39+ }
40+ IEnumerable < Ast > funcDefWithNoShouldProcessAttrAsts = ast . FindAll ( IsStateChangingFunctionWithNoShouldProcessAttribute , true ) ;
41+ foreach ( FunctionDefinitionAst funcDefAst in funcDefWithNoShouldProcessAttrAsts )
42+ {
43+ yield return new DiagnosticRecord (
44+ string . Format ( CultureInfo . CurrentCulture , Strings . UseShouldProcessForStateChangingFunctionsError , funcDefAst . Name ) ,
45+ Helper . Instance . GetScriptExtentForFunctionName ( funcDefAst ) ,
46+ this . GetName ( ) ,
47+ DiagnosticSeverity . Warning ,
48+ fileName ) ;
49+ }
50+
51+ }
52+ /// <summary>
53+ /// Checks if the ast defines a state changing function
54+ /// </summary>
55+ /// <param name="ast"></param>
56+ /// <returns>Returns true or false</returns>
57+ private bool IsStateChangingFunctionWithNoShouldProcessAttribute ( Ast ast )
58+ {
59+ var funcDefAst = ast as FunctionDefinitionAst ;
60+ if ( funcDefAst == null )
61+ {
62+ return false ;
63+ }
64+ return Helper . Instance . IsStateChangingFunctionName ( funcDefAst . Name )
65+ && ( funcDefAst . Body . ParamBlock == null
66+ || funcDefAst . Body . ParamBlock . Attributes == null
67+ || ! HasShouldProcessTrue ( funcDefAst . Body . ParamBlock . Attributes ) ) ;
68+ }
7569
76- if ( ! hasShouldProcessAttribute )
77- {
78- yield return
79- new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . UseShouldProcessForStateChangingFunctionsError , funcName ) , funcDefAst . Extent , GetName ( ) , DiagnosticSeverity . Warning , fileName ) ;
80- }
81- }
82- }
70+ /// <summary>
71+ /// Checks if an attribute has SupportShouldProcess set to $true
72+ /// </summary>
73+ /// <param name="attributeAsts"></param>
74+ /// <returns>Returns true or false</returns>
75+ private bool HasShouldProcessTrue ( IEnumerable < AttributeAst > attributeAsts )
76+ {
77+ var shouldProcessAttributeAst = Helper . Instance . GetShouldProcessAttributeAst ( attributeAsts ) ;
78+ if ( shouldProcessAttributeAst == null )
79+ {
80+ return false ;
8381 }
82+ return Helper . Instance . GetNamedArgumentAttributeValue ( shouldProcessAttributeAst ) ;
8483 }
8584
8685 /// <summary>
@@ -89,7 +88,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
8988 /// <returns>The name of this rule</returns>
9089 public string GetName ( )
9190 {
92- return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . UseShouldProcessForStateChangingFunctionsName ) ;
91+ return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , this . GetSourceName ( ) , Strings . UseShouldProcessForStateChangingFunctionsName ) ;
9392 }
9493
9594 /// <summary>
@@ -111,8 +110,9 @@ public string GetDescription()
111110 }
112111
113112 /// <summary>
114- /// GetSourceType: Retrieves the type of the rule: builtin , managed or module.
113+ /// GetSourceType: Retrieves the type of the rule: built-in , managed or module.
115114 /// </summary>
115+ /// <returns>Source type {PS, PSDSC}</returns>
116116 public SourceType GetSourceType ( )
117117 {
118118 return SourceType . Builtin ;
@@ -121,20 +121,19 @@ public SourceType GetSourceType()
121121 /// <summary>
122122 /// GetSeverity: Retrieves the severity of the rule: error, warning of information.
123123 /// </summary>
124- /// <returns></returns>
124+ /// <returns>Rule severity {Information, Warning, Error} </returns>
125125 public RuleSeverity GetSeverity ( )
126126 {
127127 return RuleSeverity . Warning ;
128128 }
129129
130-
131130 /// <summary>
132131 /// GetSourceName: Retrieves the module/assembly name the rule is from.
133132 /// </summary>
133+ /// <returns>Source name</returns>
134134 public string GetSourceName ( )
135135 {
136136 return string . Format ( CultureInfo . CurrentCulture , Strings . SourceName ) ;
137137 }
138138 }
139-
140139}
0 commit comments