1313using System ;
1414using System . Linq ;
1515using System . Collections . Generic ;
16+ using System . Management . Automation ;
1617using System . Management . Automation . Language ;
17- using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
1818using System . ComponentModel . Composition ;
1919using System . Globalization ;
20+ using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
2021
2122namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . BuiltinRules
2223{
2324 /// <summary>
2425 /// ProvideDefaultParameterValue: Check if any uninitialized variable is used.
2526 /// </summary>
2627 [ Export ( typeof ( IScriptRule ) ) ]
27- public class ProvideDefaultParameterValue : IScriptRule
28+ public class AvoidDefaultValueForMandatoryParameter : IScriptRule
2829 {
2930 /// <summary>
3031 /// AnalyzeScript: Check if any uninitialized variable is used.
@@ -36,48 +37,53 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
3637 // Finds all functionAst
3738 IEnumerable < Ast > functionAsts = ast . FindAll ( testAst => testAst is FunctionDefinitionAst , true ) ;
3839
39- // Checks whether this is a dsc resource file (we don't raise this rule for get, set and test-target resource
40- bool isDscResourceFile = ! String . IsNullOrWhiteSpace ( fileName ) && Helper . Instance . IsDscResourceModule ( fileName ) ;
41-
42- List < string > targetResourcesFunctions = new List < string > ( new string [ ] { "get-targetresource" , "set-targetresource" , "test-targetresource" } ) ;
43-
44-
4540 foreach ( FunctionDefinitionAst funcAst in functionAsts )
4641 {
47- // Finds all ParamAsts.
48- IEnumerable < Ast > varAsts = funcAst . FindAll ( testAst => testAst is VariableExpressionAst , true ) ;
49-
50- // Iterrates all ParamAsts and check if their names are on the list.
51-
52- HashSet < string > dscVariables = new HashSet < string > ( ) ;
53- if ( isDscResourceFile && targetResourcesFunctions . Contains ( funcAst . Name , StringComparer . OrdinalIgnoreCase ) )
42+ if ( funcAst . Body != null && funcAst . Body . ParamBlock != null
43+ && funcAst . Body . ParamBlock . Attributes != null && funcAst . Body . ParamBlock . Parameters != null )
5444 {
55- // don't raise the rules for variables in the param block.
56- if ( funcAst . Body != null && funcAst . Body . ParamBlock != null && funcAst . Body . ParamBlock . Parameters != null )
45+ // only raise this rule for function with cmdletbinding
46+ if ( ! funcAst . Body . ParamBlock . Attributes . Any ( attr => attr . TypeName . GetReflectionType ( ) == typeof ( CmdletBindingAttribute ) ) )
5747 {
58- dscVariables . UnionWith ( funcAst . Body . ParamBlock . Parameters . Select ( paramAst => paramAst . Name . VariablePath . UserPath ) ) ;
48+ continue ;
5949 }
60- }
61- // only raise the rules for variables in the param block.
62- if ( funcAst . Body != null && funcAst . Body . ParamBlock != null && funcAst . Body . ParamBlock . Parameters != null )
63- {
50+
6451 foreach ( var paramAst in funcAst . Body . ParamBlock . Parameters )
6552 {
66- if ( Helper . Instance . IsUninitialized ( paramAst . Name , funcAst ) && ! dscVariables . Contains ( paramAst . Name . VariablePath . UserPath ) )
53+ bool mandatory = false ;
54+
55+ // check that param is mandatory
56+ foreach ( var paramAstAttribute in paramAst . Attributes )
6757 {
68- yield return new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . ProvideDefaultParameterValueError , paramAst . Name . VariablePath . UserPath ) ,
69- paramAst . Name . Extent , GetName ( ) , DiagnosticSeverity . Warning , fileName , paramAst . Name . VariablePath . UserPath ) ;
58+ if ( paramAstAttribute is AttributeAst )
59+ {
60+ var namedArguments = ( paramAstAttribute as AttributeAst ) . NamedArguments ;
61+ if ( namedArguments != null )
62+ {
63+ foreach ( NamedAttributeArgumentAst namedArgument in namedArguments )
64+ {
65+ if ( String . Equals ( namedArgument . ArgumentName , "mandatory" , StringComparison . OrdinalIgnoreCase ) )
66+ {
67+ // 2 cases: [Parameter(Mandatory)] and [Parameter(Mandatory=$true)]
68+ if ( namedArgument . ExpressionOmitted || ( ! namedArgument . ExpressionOmitted && String . Equals ( namedArgument . Argument . Extent . Text , "$true" , StringComparison . OrdinalIgnoreCase ) ) )
69+ {
70+ mandatory = true ;
71+ break ;
72+ }
73+ }
74+ }
75+ }
76+ }
7077 }
71- }
72- }
7378
74- if ( funcAst . Parameters != null )
75- {
76- foreach ( var paramAst in funcAst . Parameters )
77- {
78- if ( Helper . Instance . IsUninitialized ( paramAst . Name , funcAst ) && ! dscVariables . Contains ( paramAst . Name . VariablePath . UserPath ) )
79+ if ( ! mandatory )
80+ {
81+ break ;
82+ }
83+
84+ if ( paramAst . DefaultValue != null )
7985 {
80- yield return new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . ProvideDefaultParameterValueError , paramAst . Name . VariablePath . UserPath ) ,
86+ yield return new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . AvoidDefaultValueForMandatoryParameterError , paramAst . Name . VariablePath . UserPath ) ,
8187 paramAst . Name . Extent , GetName ( ) , DiagnosticSeverity . Warning , fileName , paramAst . Name . VariablePath . UserPath ) ;
8288 }
8389 }
@@ -91,7 +97,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
9197 /// <returns>The name of this rule</returns>
9298 public string GetName ( )
9399 {
94- return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . ProvideDefaultParameterValueName ) ;
100+ return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . AvoidDefaultValueForMandatoryParameterName ) ;
95101 }
96102
97103 /// <summary>
@@ -100,7 +106,7 @@ public string GetName()
100106 /// <returns>The common name of this rule</returns>
101107 public string GetCommonName ( )
102108 {
103- return string . Format ( CultureInfo . CurrentCulture , Strings . ProvideDefaultParameterValueCommonName ) ;
109+ return string . Format ( CultureInfo . CurrentCulture , Strings . AvoidDefaultValueForMandatoryParameterCommonName ) ;
104110 }
105111
106112 /// <summary>
@@ -109,7 +115,7 @@ public string GetCommonName()
109115 /// <returns>The description of this rule</returns>
110116 public string GetDescription ( )
111117 {
112- return string . Format ( CultureInfo . CurrentCulture , Strings . ProvideDefaultParameterValueDescription ) ;
118+ return string . Format ( CultureInfo . CurrentCulture , Strings . AvoidDefaultValueForMandatoryParameterDescription ) ;
113119 }
114120
115121 /// <summary>
0 commit comments