Skip to content

Commit eb6a259

Browse files
author
Kapil Borle
committed
Merge pull request #443 from PowerShell/AddRuleHelpMessageShouldNotBeEmpty
Adds a rule to check if HelpMessage parameter attribute is non-null and non-empty.
2 parents 96a0f3d + ce9b6d7 commit eb6a259

9 files changed

+428
-1
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#AvoidNullOrEmtpyHelpMessageAttribute
2+
**Severity Level: Warning**
3+
4+
5+
##Description
6+
7+
Setting the HelpMessage attribute to an empty string or null value causes PowerShell interpreter to throw an error while executing the corresponding function.
8+
9+
##How to Fix
10+
11+
To fix a violation of this rule, please set its value to a non-empty string.
12+
13+
##Example
14+
15+
Wrong:
16+
17+
Function BadFuncEmtpyHelpMessageEmpty
18+
{
19+
Param(
20+
[Parameter(HelpMessage='')]
21+
[String] $Param
22+
)
23+
24+
$Param
25+
}
26+
27+
Function BadFuncEmtpyHelpMessageNull
28+
{
29+
Param(
30+
[Parameter(HelpMessage=$null)]
31+
[String] $Param
32+
)
33+
34+
$Param
35+
}
36+
37+
Function BadFuncEmtpyHelpMessageNoAssignment
38+
{
39+
Param(
40+
[Parameter(HelpMessage)]
41+
[String] $Param
42+
)
43+
44+
$Param
45+
}
46+
47+
48+
Correct:
49+
50+
Function GoodFuncEmtpyHelpMessage
51+
{
52+
Param(
53+
[Parameter(HelpMessage='This is helpful')]
54+
[String] $Param
55+
)
56+
57+
$Param
58+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
//
4+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+
// THE SOFTWARE.
11+
//
12+
13+
using System;
14+
using System.Collections.Generic;
15+
using System.Linq;
16+
using System.Management.Automation.Language;
17+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
18+
using System.ComponentModel.Composition;
19+
using System.Globalization;
20+
21+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
22+
{
23+
/// <summary>
24+
/// AvoidUsingNullOrEmptyHelpMessageAttribute: Check if the HelpMessage parameter is set to a non-empty string.
25+
/// </summary>
26+
[Export(typeof(IScriptRule))]
27+
public class AvoidNullOrEmptyHelpMessageAttribute : IScriptRule
28+
{
29+
/// <summary>
30+
/// AvoidUsingNullOrEmptyHelpMessageAttribute: Check if the HelpMessage parameter is set to a non-empty string.
31+
/// </summary>
32+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
33+
{
34+
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
35+
36+
// Finds all functionAst
37+
IEnumerable<Ast> functionAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true);
38+
39+
foreach (FunctionDefinitionAst funcAst in functionAsts)
40+
{
41+
if (funcAst.Body == null || funcAst.Body.ParamBlock == null
42+
|| funcAst.Body.ParamBlock.Attributes == null || funcAst.Body.ParamBlock.Parameters == null)
43+
{
44+
continue;
45+
}
46+
47+
foreach (var paramAst in funcAst.Body.ParamBlock.Parameters)
48+
{
49+
foreach (var paramAstAttribute in paramAst.Attributes)
50+
{
51+
if (!(paramAstAttribute is AttributeAst))
52+
{
53+
continue;
54+
}
55+
56+
var namedArguments = (paramAstAttribute as AttributeAst).NamedArguments;
57+
58+
if (namedArguments == null)
59+
{
60+
continue;
61+
}
62+
63+
foreach (NamedAttributeArgumentAst namedArgument in namedArguments)
64+
{
65+
if (!(namedArgument.ArgumentName.Equals("HelpMessage", StringComparison.OrdinalIgnoreCase)))
66+
{
67+
continue;
68+
}
69+
70+
string errCondition;
71+
if (namedArgument.ExpressionOmitted
72+
|| namedArgument.Argument.Extent.Text.Equals("\"\"")
73+
|| namedArgument.Argument.Extent.Text.Equals("\'\'"))
74+
{
75+
errCondition = "empty";
76+
}
77+
else if (namedArgument.Argument.Extent.Text.Equals("$null", StringComparison.OrdinalIgnoreCase))
78+
{
79+
errCondition = "null";
80+
}
81+
else
82+
{
83+
errCondition = null;
84+
}
85+
86+
if (!String.IsNullOrEmpty(errCondition))
87+
{
88+
string message = string.Format(CultureInfo.CurrentCulture,
89+
Strings.AvoidNullOrEmptyHelpMessageAttributeError,
90+
paramAst.Name.VariablePath.UserPath);
91+
yield return new DiagnosticRecord(message,
92+
paramAst.Extent,
93+
GetName(),
94+
DiagnosticSeverity.Warning,
95+
fileName,
96+
paramAst.Name.VariablePath.UserPath);
97+
}
98+
}
99+
}
100+
}
101+
102+
}
103+
}
104+
105+
/// <summary>
106+
/// GetName: Retrieves the name of this rule.
107+
/// </summary>
108+
/// <returns>The name of this rule</returns>
109+
public string GetName()
110+
{
111+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidNullOrEmptyHelpMessageAttributeName);
112+
}
113+
114+
/// <summary>
115+
/// GetCommonName: Retrieves the common name of this rule.
116+
/// </summary>
117+
/// <returns>The common name of this rule</returns>
118+
public string GetCommonName()
119+
{
120+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidNullOrEmptyHelpMessageAttributeCommonName);
121+
}
122+
123+
/// <summary>
124+
/// GetDescription: Retrieves the description of this rule.
125+
/// </summary>
126+
/// <returns>The description of this rule</returns>
127+
public string GetDescription()
128+
{
129+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidNullOrEmptyHelpMessageAttributeDescription);
130+
}
131+
132+
/// <summary>
133+
/// GetSourceType: Retrieves the type of the rule, builtin, managed or module.
134+
/// </summary>
135+
public SourceType GetSourceType()
136+
{
137+
return SourceType.Builtin;
138+
}
139+
140+
/// <summary>
141+
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
142+
/// </summary>
143+
/// <returns></returns>
144+
public RuleSeverity GetSeverity()
145+
{
146+
return RuleSeverity.Warning;
147+
}
148+
149+
/// <summary>
150+
/// GetSourceName: Retrieves the module/assembly name the rule is from.
151+
/// </summary>
152+
public string GetSourceName()
153+
{
154+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
155+
}
156+
}
157+
}

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<Compile Include="AvoidEmptyCatchBlock.cs" />
7373
<Compile Include="AvoidGlobalVars.cs" />
7474
<Compile Include="AvoidInvokingEmptyMembers.cs" />
75+
<Compile Include="AvoidNullOrEmptyHelpMessageAttribute.cs" />
7576
<Compile Include="AvoidPositionalParameters.cs" />
7677
<Compile Include="AvoidReservedCharInCmdlet.cs" />
7778
<Compile Include="AvoidReservedParams.cs" />

Rules/Strings.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,4 +786,16 @@
786786
<data name="MisleadingBacktickError" xml:space="preserve">
787787
<value>This line has a backtick at the end trailed by a whitespace character. Did you mean for this to be a line continuation?</value>
788788
</data>
789+
<data name="AvoidNullOrEmptyHelpMessageAttributeCommonName" xml:space="preserve">
790+
<value>Avoid using null or empty HelpMessage parameter attribute.</value>
791+
</data>
792+
<data name="AvoidNullOrEmptyHelpMessageAttributeDescription" xml:space="preserve">
793+
<value>Setting the HelpMessage attribute to an empty string or null value causes PowerShell interpreter to throw an error while executing the corresponding function.</value>
794+
</data>
795+
<data name="AvoidNullOrEmptyHelpMessageAttributeError" xml:space="preserve">
796+
<value>HelpMessage parameter attribute should not be null or empty. To fix a violation of this rule, please set its value to a non-empty string.</value>
797+
</data>
798+
<data name="AvoidNullOrEmptyHelpMessageAttributeName" xml:space="preserve">
799+
<value>AvoidNullOrEmptyHelpMessageAttribute</value>
800+
</data>
789801
</root>

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Describe "Test Name parameters" {
5656

5757
It "Get Rules with no parameters supplied" {
5858
$defaultRules = Get-ScriptAnalyzerRule
59-
$defaultRules.Count | Should be 39
59+
$defaultRules.Count | Should be 40
6060
}
6161
}
6262

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
function BadFuncNullHelpMessage
2+
{
3+
[CmdletBinding()]
4+
[OutputType([String])]
5+
param(
6+
# this one null value
7+
[Parameter(HelpMessage=$null)]
8+
[string] $Param1="String",
9+
10+
# this parameter has no help message
11+
[Parameter(HelpMessage="This is helpful.")]
12+
[string] $Param2
13+
)
14+
$Param1
15+
$Param2 = "test"
16+
}
17+
18+
function BadFuncEmptyHelpMessage
19+
{
20+
[CmdletBinding()]
21+
[OutputType([String])]
22+
param(
23+
# this has an empty string
24+
[Parameter(HelpMessage="")]
25+
[string] $Param1="String",
26+
27+
# this parameter has no default value
28+
[Parameter(HelpMessage="This is helpful.")]
29+
[string] $Param2
30+
)
31+
$Param1
32+
$Param2 = "test"
33+
}
34+
35+
function GoodFunc1($Param1)
36+
{
37+
$Param1
38+
}
39+
40+
# same as BadFunc but this one has no cmdletbinding
41+
function BadFuncNullHelpMessageNoCmdletBinding
42+
{
43+
param(
44+
# this one null value
45+
[Parameter(HelpMessage=$null)]
46+
[string] $Param1="String",
47+
48+
# this parameter has no help message
49+
[Parameter(HelpMessage="This is helpful.")]
50+
[string] $Param2
51+
)
52+
$Param1
53+
$Param2 = "test"
54+
}
55+
56+
# same as BadFunc but this one has no cmdletbinding
57+
function BadFuncEmptyHelpMessageNoCmdletBinding
58+
{
59+
param(
60+
# this has an empty string
61+
[Parameter(HelpMessage="")]
62+
[string] $Param1="String",
63+
64+
# this parameter has no default value
65+
[Parameter(HelpMessage="This is helpful.")]
66+
[string] $Param2
67+
)
68+
$Param1
69+
$Param2 = "test"
70+
}
71+
72+
73+
# same as BadFunc but this one has no cmdletbinding
74+
function BadFuncEmptyHelpMessageNoCmdletBindingSingleQoutes
75+
{
76+
param(
77+
# this has an empty string
78+
[Parameter(HelpMessage='')]
79+
[string] $Param1="String",
80+
81+
# this parameter has no default value
82+
[Parameter(HelpMessage="This is helpful.")]
83+
[string] $Param2
84+
)
85+
$Param1
86+
$Param2 = "test"
87+
}
88+
89+
# same as BadFunc but this one has no cmdletbinding
90+
function BadFuncEmptyHelpMessageNoCmdletBindingNoAssignment
91+
{
92+
param(
93+
# this has an empty string
94+
[Parameter(HelpMessage)]
95+
[string] $Param1="String",
96+
97+
# this parameter has no default value
98+
[Parameter(HelpMessage="This is helpful.")]
99+
[string] $Param2
100+
)
101+
$Param1
102+
$Param2 = "test"
103+
}

0 commit comments

Comments
 (0)