Skip to content

Commit 849442c

Browse files
author
quoctruong
committed
Merge pull request #32 from PowerShell/FixProvideCommentHelp
Fix a bug where provide comment help is raised for non exported functions
2 parents b6b2674 + 7c3d001 commit 849442c

File tree

4 files changed

+171
-8
lines changed

4 files changed

+171
-8
lines changed

Engine/Helper.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,11 @@ public bool PositionalParameterUsed(CommandAst cmdAst)
241241
/// Given a command's name, checks whether it exists
242242
/// </summary>
243243
/// <param name="name"></param>
244+
/// <param name="commandType"></param>
244245
/// <returns></returns>
245-
public CommandInfo GetCommandInfo(string name)
246+
public CommandInfo GetCommandInfo(string name, CommandTypes commandType=CommandTypes.All)
246247
{
247-
return Helper.Instance.MyCmdlet.InvokeCommand.GetCommand(name, CommandTypes.All);
248+
return Helper.Instance.MyCmdlet.InvokeCommand.GetCommand(name, commandType);
248249
}
249250

250251
/// <summary>
@@ -259,6 +260,29 @@ public IEnumerable<Ast> DscResourceFunctions(Ast ast)
259260
&& resourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
260261
}
261262

263+
/// <summary>
264+
/// Gets all the strings contained in an array literal ast
265+
/// </summary>
266+
/// <param name="alAst"></param>
267+
/// <returns></returns>
268+
public List<string> GetStringsFromArrayLiteral(ArrayLiteralAst alAst)
269+
{
270+
List<string> result = new List<string>();
271+
272+
if (alAst != null && alAst.Elements != null)
273+
{
274+
foreach (ExpressionAst eAst in alAst.Elements)
275+
{
276+
if (eAst is StringConstantExpressionAst)
277+
{
278+
result.Add((eAst as StringConstantExpressionAst).Value);
279+
}
280+
}
281+
}
282+
283+
return result;
284+
}
285+
262286
/// <summary>
263287
/// Returns true if the block should be skipped as it has a name
264288
/// that matches keyword

Rules/ProvideCommentHelp.cs

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using System.Globalization;
2323
using System.Threading;
2424
using System.Reflection;
25+
using System.Management.Automation;
2526

2627
namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
2728
{
@@ -31,6 +32,8 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
3132
[Export(typeof(IScriptRule))]
3233
public class ProvideCommentHelp : SkipTypeDefinition, IScriptRule
3334
{
35+
private HashSet<string> exportedFunctions;
36+
3437
/// <summary>
3538
/// AnalyzeScript: Analyzes the ast to check that cmdlets have help.
3639
/// </summary>
@@ -42,6 +45,131 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) {
4245

4346
DiagnosticRecords.Clear();
4447
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+
45173
ast.Visit(this);
46174

47175
return DiagnosticRecords;
@@ -59,10 +187,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
59187
return AstVisitAction.SkipChildren;
60188
}
61189

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))
64191
{
65-
66192
if (funcAst.GetHelpContent() == null)
67193
{
68194
DiagnosticRecords.Add(

Tests/Rules/BadCmdlet.ps1

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,17 @@
5050
End
5151
{
5252
}
53-
}
53+
}
54+
55+
# Provide comment help should not be raised here because this is not exported
56+
function NoComment
57+
{
58+
Write-Verbose "No Comment"
59+
}
60+
61+
function Comment
62+
{
63+
Write-Verbose "Should raise providecommenthelp error"
64+
}
65+
66+
Export-ModuleMember Verb-Files, Comment

Tests/Rules/ProvideCommentHelp.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ $noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {
88

99
Describe "ProvideCommentHelp" {
1010
Context "When there are violations" {
11-
It "has 1 provide comment help violation" {
12-
$violations.Count | Should Be 1
11+
It "has 2 provide comment help violations" {
12+
$violations.Count | Should Be 2
1313
}
1414

1515
It "has the correct description message" {

0 commit comments

Comments
 (0)