33
44using System ;
55using System . Collections . Generic ;
6+ using System . Linq ;
67using System . Management . Automation . Language ;
78#if ! CORECLR
89using System . ComponentModel . Composition ;
@@ -27,59 +28,73 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
2728 {
2829 if ( ast == null ) throw new ArgumentNullException ( Strings . NullAstErrorMessage ) ;
2930
30- // Finds all functionAst
31- IEnumerable < Ast > functionAsts = ast . FindAll ( testAst => testAst is FunctionDefinitionAst , true ) ;
32-
33- foreach ( FunctionDefinitionAst funcAst in functionAsts )
31+ // Find all ParameterAst which are children of a ParamBlockAst. This
32+ // doesn't pick up where they appear as children of a
33+ // FunctionDefinitionAst. i.e.
34+ //
35+ // function foo ($a,$b){} -> $a and $b are `ParameterAst`
36+ //
37+ // Include only parameters which have a default value (as without
38+ // one this rule would never alert)
39+ // Include only parameters where ALL parameter attributes have the
40+ // mandatory named argument set to true (implicitly or explicitly)
41+
42+ var mandatoryParametersWithDefaultValues =
43+ ast . FindAll ( testAst => testAst is ParamBlockAst , true )
44+ . Cast < ParamBlockAst > ( )
45+ . Where ( pb => pb . Parameters ? . Count > 0 )
46+ . SelectMany ( pb => pb . Parameters )
47+ . Where ( paramAst =>
48+ paramAst . DefaultValue != null &&
49+ HasMandatoryInAllParameterAttributes ( paramAst )
50+ ) ;
51+
52+ // Report diagnostics for each parameter that violates the rule
53+ foreach ( var parameter in mandatoryParametersWithDefaultValues )
3454 {
35- if ( funcAst . Body != null && funcAst . Body . ParamBlock != null
36- && funcAst . Body . ParamBlock . Attributes != null && funcAst . Body . ParamBlock . Parameters != null )
37- {
38- foreach ( var paramAst in funcAst . Body . ParamBlock . Parameters )
39- {
40- bool mandatory = false ;
41-
42- // check that param is mandatory
43- foreach ( var paramAstAttribute in paramAst . Attributes )
44- {
45- if ( paramAstAttribute is AttributeAst )
46- {
47- var namedArguments = ( paramAstAttribute as AttributeAst ) . NamedArguments ;
48- if ( namedArguments != null )
49- {
50- foreach ( NamedAttributeArgumentAst namedArgument in namedArguments )
51- {
52- if ( String . Equals ( namedArgument . ArgumentName , "mandatory" , StringComparison . OrdinalIgnoreCase ) )
53- {
54- // 3 cases: [Parameter(Mandatory)], [Parameter(Mandatory=$true)] and [Parameter(Mandatory=value)] where value is not equal to 0.
55- if ( namedArgument . ExpressionOmitted
56- || ( String . Equals ( namedArgument . Argument . Extent . Text , "$true" , StringComparison . OrdinalIgnoreCase ) )
57- || ( int . TryParse ( namedArgument . Argument . Extent . Text , out int mandatoryValue ) && mandatoryValue != 0 ) )
58- {
59- mandatory = true ;
60- break ;
61- }
62- }
63- }
64- }
65- }
66- }
67-
68- if ( ! mandatory )
69- {
70- break ;
71- }
72-
73- if ( paramAst . DefaultValue != null )
74- {
75- yield return new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . AvoidDefaultValueForMandatoryParameterError , paramAst . Name . VariablePath . UserPath ) ,
76- paramAst . Name . Extent , GetName ( ) , DiagnosticSeverity . Warning , fileName , paramAst . Name . VariablePath . UserPath ) ;
77- }
78- }
79- }
55+ yield return new DiagnosticRecord (
56+ string . Format (
57+ CultureInfo . CurrentCulture ,
58+ Strings . AvoidDefaultValueForMandatoryParameterError ,
59+ parameter . Name . VariablePath . UserPath
60+ ) ,
61+ parameter . Name . Extent ,
62+ GetName ( ) ,
63+ DiagnosticSeverity . Warning ,
64+ fileName ,
65+ parameter . Name . VariablePath . UserPath
66+ ) ;
8067 }
8168 }
8269
70+ /// <summary>
71+ /// Determines if a parameter is mandatory in all of its Parameter attributes.
72+ /// A parameter may have multiple [Parameter] attributes for different parameter sets.
73+ /// This method returns true only if ALL [Parameter] attributes have Mandatory=true.
74+ /// </summary>
75+ /// <param name="paramAst">The parameter AST to examine</param>
76+ /// <param name="comparer">String comparer for case-insensitive attribute name matching</param>
77+ /// <returns>
78+ /// True if the parameter has at least one [Parameter] attribute and ALL of them
79+ /// have the Mandatory named argument set to true (explicitly or implicitly).
80+ /// False if the parameter has no [Parameter] attributes or if any [Parameter]
81+ /// attribute does not have Mandatory=true.
82+ /// </returns>
83+ private static bool HasMandatoryInAllParameterAttributes ( ParameterAst paramAst )
84+ {
85+ var parameterAttributes = paramAst . Attributes . OfType < AttributeAst > ( )
86+ . Where ( attr => string . Equals ( attr . TypeName ? . Name , "parameter" , StringComparison . OrdinalIgnoreCase ) ) ;
87+
88+ return parameterAttributes . Any ( ) &&
89+ parameterAttributes . All ( attr =>
90+ attr . NamedArguments . OfType < NamedAttributeArgumentAst > ( )
91+ . Any ( namedArg =>
92+ string . Equals ( namedArg . ArgumentName , "mandatory" , StringComparison . OrdinalIgnoreCase ) &&
93+ Helper . Instance . GetNamedArgumentAttributeValue ( namedArg )
94+ )
95+ ) ;
96+ }
97+
8398 /// <summary>
8499 /// GetName: Retrieves the name of this rule.
85100 /// </summary>
@@ -134,6 +149,3 @@ public string GetSourceName()
134149 }
135150}
136151
137-
138-
139-
0 commit comments