22
22
using System . Globalization ;
23
23
using System . Threading ;
24
24
using System . Reflection ;
25
+ using System . Management . Automation ;
25
26
26
27
namespace Microsoft . Windows . Powershell . ScriptAnalyzer . BuiltinRules
27
28
{
@@ -31,6 +32,8 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
31
32
[ Export ( typeof ( IScriptRule ) ) ]
32
33
public class ProvideCommentHelp : SkipTypeDefinition , IScriptRule
33
34
{
35
+ private HashSet < string > exportedFunctions ;
36
+
34
37
/// <summary>
35
38
/// AnalyzeScript: Analyzes the ast to check that cmdlets have help.
36
39
/// </summary>
@@ -42,6 +45,131 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) {
42
45
43
46
DiagnosticRecords . Clear ( ) ;
44
47
this . fileName = fileName ;
48
+ exportedFunctions = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
49
+ List < string > exportFunctionsCmdlet = Helper . Instance . CmdletNameAndAliases ( "export-modulemember" ) ;
50
+
51
+ // find functions exported
52
+ IEnumerable < Ast > cmdAsts = ast . FindAll ( item => item is CommandAst
53
+ && exportFunctionsCmdlet . Contains ( ( item as CommandAst ) . GetCommandName ( ) , StringComparer . OrdinalIgnoreCase ) , true ) ;
54
+
55
+ CommandInfo exportMM = Helper . Instance . GetCommandInfo ( "export-modulemember" , CommandTypes . Cmdlet ) ;
56
+
57
+ // switch parameters
58
+ IEnumerable < ParameterMetadata > switchParams = ( exportMM != null ) ? exportMM . Parameters . Values . Where < ParameterMetadata > ( pm => pm . SwitchParameter ) : Enumerable . Empty < ParameterMetadata > ( ) ;
59
+
60
+ if ( exportMM == null )
61
+ {
62
+ return DiagnosticRecords ;
63
+ }
64
+
65
+ foreach ( CommandAst cmdAst in cmdAsts )
66
+ {
67
+ if ( cmdAst . CommandElements == null || cmdAst . CommandElements . Count < 2 )
68
+ {
69
+ continue ;
70
+ }
71
+
72
+ int i = 1 ;
73
+
74
+ while ( i < cmdAst . CommandElements . Count )
75
+ {
76
+ CommandElementAst ceAst = cmdAst . CommandElements [ i ] ;
77
+ ExpressionAst exprAst = null ;
78
+
79
+ if ( ceAst is CommandParameterAst )
80
+ {
81
+ var paramAst = ceAst as CommandParameterAst ;
82
+ var param = exportMM . ResolveParameter ( paramAst . ParameterName ) ;
83
+
84
+ if ( param == null )
85
+ {
86
+ i += 1 ;
87
+ continue ;
88
+ }
89
+
90
+ if ( string . Equals ( param . Name , "function" , StringComparison . OrdinalIgnoreCase ) )
91
+ {
92
+ // checks for the case of -Function:"verb-nouns"
93
+ if ( paramAst . Argument != null )
94
+ {
95
+ exprAst = paramAst . Argument ;
96
+ }
97
+ // checks for the case of -Function "verb-nouns"
98
+ else if ( i < cmdAst . CommandElements . Count - 1 )
99
+ {
100
+ i += 1 ;
101
+ exprAst = cmdAst . CommandElements [ i ] as ExpressionAst ;
102
+ }
103
+ }
104
+ // some other parameter. we just checks whether the one after this is positional
105
+ else if ( i < cmdAst . CommandElements . Count - 1 )
106
+ {
107
+ // the next element is a parameter like -module so just move to that one
108
+ if ( cmdAst . CommandElements [ i + 1 ] is CommandParameterAst )
109
+ {
110
+ i += 1 ;
111
+ continue ;
112
+ }
113
+
114
+ // not a switch parameter so the next element is definitely the argument to this parameter
115
+ if ( paramAst . Argument == null && ! switchParams . Contains ( param ) )
116
+ {
117
+ // skips the next element
118
+ i += 1 ;
119
+ }
120
+
121
+ i += 1 ;
122
+ continue ;
123
+ }
124
+ }
125
+ else if ( ceAst is ExpressionAst )
126
+ {
127
+ exprAst = ceAst as ExpressionAst ;
128
+ }
129
+
130
+ if ( exprAst != null )
131
+ {
132
+ // One string so just add this to the list
133
+ if ( exprAst is StringConstantExpressionAst )
134
+ {
135
+ exportedFunctions . Add ( ( exprAst as StringConstantExpressionAst ) . Value ) ;
136
+ }
137
+ // Array of the form "v-n", "v-n1"
138
+ else if ( exprAst is ArrayLiteralAst )
139
+ {
140
+ exportedFunctions . UnionWith ( Helper . Instance . GetStringsFromArrayLiteral ( exprAst as ArrayLiteralAst ) ) ;
141
+ }
142
+ // Array of the form @("v-n", "v-n1")
143
+ else if ( exprAst is ArrayExpressionAst )
144
+ {
145
+ ArrayExpressionAst arrExAst = exprAst as ArrayExpressionAst ;
146
+ if ( arrExAst . SubExpression != null && arrExAst . SubExpression . Statements != null )
147
+ {
148
+ foreach ( StatementAst stAst in arrExAst . SubExpression . Statements )
149
+ {
150
+ if ( stAst is PipelineAst )
151
+ {
152
+ PipelineAst pipeAst = stAst as PipelineAst ;
153
+ if ( pipeAst . PipelineElements != null )
154
+ {
155
+ foreach ( CommandBaseAst cmdBaseAst in pipeAst . PipelineElements )
156
+ {
157
+ if ( cmdBaseAst is CommandExpressionAst )
158
+ {
159
+ exportedFunctions . UnionWith ( Helper . Instance . GetStringsFromArrayLiteral ( ( cmdBaseAst as CommandExpressionAst ) . Expression as ArrayLiteralAst ) ) ;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ i += 1 ;
170
+ }
171
+ }
172
+
45
173
ast . Visit ( this ) ;
46
174
47
175
return DiagnosticRecords ;
@@ -59,10 +187,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
59
187
return AstVisitAction . SkipChildren ;
60
188
}
61
189
62
- if ( ! string . Equals ( funcAst . Name , "Get-TargetResource" , StringComparison . OrdinalIgnoreCase ) && ! string . Equals ( funcAst . Name , "Set-TargetResource" , StringComparison . OrdinalIgnoreCase ) &&
63
- ! string . Equals ( funcAst . Name , "Test-TargetResource" , StringComparison . OrdinalIgnoreCase ) )
190
+ if ( exportedFunctions . Contains ( funcAst . Name ) )
64
191
{
65
-
66
192
if ( funcAst . GetHelpContent ( ) == null )
67
193
{
68
194
DiagnosticRecords . Add (
0 commit comments