Skip to content

Commit 44dac6e

Browse files
committed
Merge pull request #450 from PowerShell/development
Take Development to Master for v1.4.0 release
2 parents a09b015 + 891955d commit 44dac6e

26 files changed

+724
-31
lines changed

Engine/Generic/RuleInfo.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,12 @@ public RuleInfo(string name, string commonName, string description, SourceType s
102102
Description = description;
103103
SourceType = sourceType;
104104
SourceName = sourceName;
105-
Severity = severity;
105+
Severity = severity;
106106
}
107+
108+
public override string ToString()
109+
{
110+
return RuleName;
111+
}
107112
}
108113
}

Engine/Helper.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,28 @@ public bool SkipBlock(string keyword, Ast namedBlockAst)
645645
return false;
646646
}
647647

648+
// Obtain script extent for the function - just around the function name
649+
public IScriptExtent GetScriptExtentForFunctionName(FunctionDefinitionAst functionDefinitionAst)
650+
{
651+
if (null == functionDefinitionAst)
652+
{
653+
return null;
654+
}
655+
656+
// Obtain the index where the function name is in Tokens
657+
int funcTokenIndex = Tokens.Select((s, index) => new { s, index })
658+
.Where(x => x.s.Extent.StartOffset == functionDefinitionAst.Extent.StartOffset)
659+
.Select(x => x.index).FirstOrDefault();
660+
661+
if (funcTokenIndex > 0 && funcTokenIndex < Helper.Instance.Tokens.Count())
662+
{
663+
// return the extent of the next token - this is the extent for the function name
664+
return Tokens[++funcTokenIndex].Extent;
665+
}
666+
667+
return null;
668+
}
669+
648670
private void FindClosingParenthesis(string keyword)
649671
{
650672
if (Tokens == null || Tokens.Length == 0)

Engine/PSScriptAnalyzer.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Author = 'Microsoft Corporation'
1111
RootModule = 'PSScriptAnalyzer.psm1'
1212

1313
# Version number of this module.
14-
ModuleVersion = '1.3.0'
14+
ModuleVersion = '1.4.0'
1515

1616
# ID used to uniquely identify this module
1717
GUID = '324fc715-36bf-4aee-8e58-72e9b4a08ad9'

Engine/ScriptAnalyzer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -926,8 +926,8 @@ internal IEnumerable<DiagnosticRecord> GetExternalRecord(Ast ast, Token[] token,
926926

927927
// DiagnosticRecord may not be correctly returned from external rule.
928928
try
929-
{
930-
Enum.TryParse<DiagnosticSeverity>(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity);
929+
{
930+
severity = (DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), psobject.Properties["Severity"].Value.ToString());
931931
message = psobject.Properties["Message"].Value.ToString();
932932
extent = (IScriptExtent)psobject.Properties["Extent"].Value;
933933
ruleName = psobject.Properties["RuleName"].Value.ToString();
@@ -1244,7 +1244,7 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
12441244
if (File.Exists(filePath))
12451245
{
12461246
// processing for non help script
1247-
if (!(Path.GetFileName(filePath).StartsWith("about_") && Path.GetFileName(filePath).EndsWith(".help.txt")))
1247+
if (!(Path.GetFileName(filePath).ToLower().StartsWith("about_") && Path.GetFileName(filePath).ToLower().EndsWith(".help.txt")))
12481248
{
12491249
try
12501250
{

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Announcements
1+
Announcements
22
=============
33

44
##### ISE Add-On for ScriptAnalyzer is available in PowerShell Gallery!
@@ -11,19 +11,20 @@ Announcements
1111

1212
##### ScriptAnalyzer community meeting schedule:
1313

14-
- Next Meeting - Feb 2 2016 - 11am to 12pm PDT
14+
- Next Meeting - Mar 1 2016 - 11am to 12pm PDT
1515
- [iCalender invite](http://1drv.ms/1VvAaxO)
1616
- [Notes and recordings from earlier meetings](https://github.com/PowerShell/PSScriptAnalyzer/wiki)
1717

1818

1919
=============
20-
20+
#####Recent Builds
2121
|Master | Development |
2222
|:------:|:------:|:-------:|:-------:|
2323
[![Build status](https://ci.appveyor.com/api/projects/status/h5mot3vqtvxw5d7l/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/psscriptanalyzer/branch/master)|[![Build status](https://ci.appveyor.com/api/projects/status/h5mot3vqtvxw5d7l/branch/development?svg=true)](https://ci.appveyor.com/project/PowerShell/psscriptanalyzer/branch/development) |
2424

2525
=============
26-
26+
#####Code Review Dashboard on [reviewable.io](https://reviewable.io/reviews/PowerShell/PSScriptAnalyzer#-)
27+
=============
2728

2829
Introduction
2930
============
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/AvoidShouldContinueWithoutForce.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4646
foreach (ParameterAst paramAst in paramAsts)
4747
{
4848
if (String.Equals(paramAst.Name.VariablePath.ToString(), "force", StringComparison.OrdinalIgnoreCase)
49-
&& String.Equals(paramAst.StaticType.FullName, "System.Boolean", StringComparison.OrdinalIgnoreCase))
49+
&& (String.Equals(paramAst.StaticType.FullName, "System.Boolean", StringComparison.OrdinalIgnoreCase)
50+
|| String.Equals(paramAst.StaticType.FullName, "System.Management.Automation.SwitchParameter", StringComparison.OrdinalIgnoreCase)))
5051
{
5152
hasForce = true;
5253
break;

Rules/AvoidUserNameAndPasswordParams.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,31 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5353
// Iterates all ParamAsts and check if their names are on the list.
5454
foreach (ParameterAst paramAst in paramAsts)
5555
{
56+
// this will be null if there is no [pscredential] attached to the parameter
5657
var psCredentialType = paramAst.Attributes.FirstOrDefault(paramAttribute =>
5758
(paramAttribute.TypeName.IsArray && (paramAttribute.TypeName as ArrayTypeName).ElementType.GetReflectionType() == typeof(PSCredential))
5859
|| paramAttribute.TypeName.GetReflectionType() == typeof(PSCredential));
5960

61+
// this will be null if there are no [credential()] attribute attached
6062
var credentialAttribute = paramAst.Attributes.FirstOrDefault(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute));
6163

62-
String paramName = paramAst.Name.VariablePath.ToString();
64+
// this will be null if there are no [securestring] attached to the parameter
65+
var secureStringType = paramAst.Attributes.FirstOrDefault(paramAttribute =>
66+
(paramAttribute.TypeName.IsArray && (paramAttribute.TypeName as ArrayTypeName).ElementType.GetReflectionType() == typeof (System.Security.SecureString))
67+
|| paramAttribute.TypeName.GetReflectionType() == typeof(System.Security.SecureString));
6368

64-
// if this is pscredential type with credential attribute where pscredential type comes first
65-
if (psCredentialType != null && credentialAttribute != null && psCredentialType.Extent.EndOffset < credentialAttribute.Extent.StartOffset)
66-
{
67-
continue;
68-
}
69+
String paramName = paramAst.Name.VariablePath.ToString();
6970

7071
foreach (String password in passwords)
7172
{
7273
if (paramName.IndexOf(password, StringComparison.OrdinalIgnoreCase) != -1)
7374
{
75+
// if this is a secure string, pscredential or credential attribute, don't count
76+
if (secureStringType != null || credentialAttribute != null || psCredentialType != null)
77+
{
78+
continue;
79+
}
80+
7481
hasPwd = true;
7582
break;
7683
}

0 commit comments

Comments
 (0)