Skip to content

Commit da1190b

Browse files
David ReynoldsDavid Reynolds
authored andcommitted
Added XML documentation for functions, fixed test It blocks, other requirest PR improvements
1 parent 5a3c66b commit da1190b

File tree

7 files changed

+95
-27
lines changed

7 files changed

+95
-27
lines changed

RuleDocumentation/AvoidGlobalAliases.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ Use other scope modifiers for new aliases.
1313
##Example
1414
###Wrong:
1515
``` PowerShell
16-
nal -Name Name -Value Value -Scope "Global"
16+
New-Alias -Name Name -Value Value -Scope "Global"
1717
```
1818

1919
###Correct:
2020
``` PowerShell
2121
New-Alias -Name Name1 -Value Value
22-
```
22+
```

RuleDocumentation/AvoidGlobalFunctions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ function global:functionName {}
1919
###Correct:
2020
``` PowerShell
2121
function functionName {}
22-
```
22+
```

Rules/AvoidGlobalAliases.cs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
2-
using System;
1+
using System;
32
using System.Collections.Generic;
43
#if !CORECLR
54
using System.ComponentModel.Composition;
65
#endif
76
using System.Globalization;
87
using System.Management.Automation.Language;
8+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
99

1010
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1111
{
@@ -17,6 +17,12 @@ class AvoidGlobalAliases : AstVisitor, IScriptRule
1717
private List<DiagnosticRecord> records;
1818
private string fileName;
1919

20+
/// <summary>
21+
/// Analyzes the ast to check that global aliases are not used.
22+
/// </summary>
23+
/// <param name="ast">The script's ast</param>
24+
/// <param name="fileName">The script's file name</param>
25+
/// <returns>A List of diagnostic results of this rule</returns>
2026
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
2127
{
2228
if (ast == null)
@@ -27,12 +33,20 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
2733
records = new List<DiagnosticRecord>();
2834
this.fileName = fileName;
2935

30-
ast.Visit(this);
36+
if (IsScriptModule())
37+
{
38+
ast.Visit(this);
39+
}
3140

3241
return records;
3342
}
3443

3544
#region VisitCommand functions
45+
/// <summary>
46+
/// Analyzes a CommandAst, if it is a New-Alias command, the AST is further analyzed.
47+
/// </summary>
48+
/// <param name="commandAst">The CommandAst to be analyzed</param>
49+
/// <returns>AstVisitAction to continue to analyze the ast's children</returns>
3650
public override AstVisitAction VisitCommand(CommandAst commandAst)
3751
{
3852
if (!IsNewAliasCmdlet(commandAst))
@@ -43,11 +57,19 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
4357
return AstVisitAction.Continue;
4458
}
4559

60+
/// <summary>
61+
/// Analyzes a CommandParameterAst for the global scope.
62+
/// </summary>
63+
/// <param name="commandParameterAst">The CommandParameterAst to be analyzed</param>
64+
/// <returns>AstVisitAction to skip child ast processing after creating any diagnostic records</returns>
4665
public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
4766
{
4867
if (IsScopeParameterForNewAliasCmdlet(commandParameterAst))
4968
{
50-
if ((commandParameterAst.Argument != null) // if the cmdlet looks like -Scope:Global check Parameter.Argument
69+
// Check the commandParameterAst Argument property if it exist. This covers the case
70+
// of the cmdlet looking like "New-Alias -Scope:Global"
71+
72+
if ((commandParameterAst.Argument != null)
5173
&& (commandParameterAst.Argument.ToString().Equals("Global", StringComparison.OrdinalIgnoreCase)))
5274
{
5375
records.Add(new DiagnosticRecord(
@@ -60,18 +82,22 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command
6082
}
6183
else
6284
{
63-
var nextAst = FindNextAst(commandParameterAst);
85+
// If the commandParameterAst Argument property is null the next ast in the tree
86+
// can still be a string const. This covers the case of the cmdlet looking like
87+
// "New-Alias -Scope Global"
88+
89+
var nextAst = FindNextAst(commandParameterAst) as StringConstantExpressionAst;
6490

65-
if ((nextAst is StringConstantExpressionAst) // if the cmdlet looks like -Scope Global
66-
&& (((StringConstantExpressionAst)nextAst).Value.ToString().Equals("Global", StringComparison.OrdinalIgnoreCase)))
91+
if ((nextAst != null)
92+
&& ((nextAst).Value.ToString().Equals("Global", StringComparison.OrdinalIgnoreCase)))
6793
{
6894
records.Add(new DiagnosticRecord(
6995
string.Format(CultureInfo.CurrentCulture, Strings.AvoidGlobalAliasesError),
70-
((StringConstantExpressionAst)nextAst).Extent,
96+
(nextAst).Extent,
7197
GetName(),
7298
DiagnosticSeverity.Warning,
7399
fileName,
74-
((StringConstantExpressionAst)nextAst).Value));
100+
(nextAst).Value));
75101
}
76102
}
77103
}
@@ -80,6 +106,11 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command
80106
}
81107
#endregion
82108

109+
/// <summary>
110+
/// Returns the next ast of the same level in the ast tree.
111+
/// </summary>
112+
/// <param name="ast">Ast used as a base</param>
113+
/// <returns>Next ast of the same level in the ast tree</returns>
83114
private Ast FindNextAst(Ast ast)
84115
{
85116
IEnumerable<Ast> matchingLevelAsts = ast.Parent.FindAll(item => item is Ast, true);
@@ -106,6 +137,12 @@ private Ast FindNextAst(Ast ast)
106137
return currentClosest;
107138
}
108139

140+
/// <summary>
141+
/// Determines if ast1 is after ast2 in the ast tree.
142+
/// </summary>
143+
/// <param name="ast1">First ast</param>
144+
/// <param name="ast2">Second ast</param>
145+
/// <returns>True if ast2 is after ast1 in the ast tree</returns>
109146
private bool IsAstAfter(Ast ast1, Ast ast2)
110147
{
111148
if (ast1.Extent.EndLineNumber > ast2.Extent.StartLineNumber) // ast1 ends on a line after ast2 starts
@@ -129,23 +166,31 @@ private bool IsAstAfter(Ast ast1, Ast ast2)
129166
}
130167
}
131168

169+
/// <summary>
170+
/// Determines if CommandParameterAst is for the "Scope" parameter.
171+
/// </summary>
172+
/// <param name="commandParameterAst">CommandParameterAst to validate</param>
173+
/// <returns>True if the CommandParameterAst is for the Scope parameter</returns>
132174
private bool IsScopeParameterForNewAliasCmdlet(CommandParameterAst commandParameterAst)
133175
{
134176
if (commandParameterAst == null || commandParameterAst.ParameterName == null)
135177
{
136178
return false;
137179
}
138180

139-
if (commandParameterAst.ParameterName.Equals("Scope", StringComparison.OrdinalIgnoreCase)
140-
&& (commandParameterAst.Parent is CommandAst)
141-
&& IsNewAliasCmdlet((CommandAst)commandParameterAst.Parent))
181+
if (commandParameterAst.ParameterName.Equals("Scope", StringComparison.OrdinalIgnoreCase))
142182
{
143183
return true;
144184
}
145185

146186
return false;
147187
}
148188

189+
/// <summary>
190+
/// Determines if CommandAst is for the "New-Alias" command, checking aliases.
191+
/// </summary>
192+
/// <param name="commandAst">CommandAst to validate</param>
193+
/// <returns>True if the CommandAst is for the "New-Alias" command</returns>
149194
private bool IsNewAliasCmdlet(CommandAst commandAst)
150195
{
151196
if (commandAst == null || commandAst.GetCommandName() == null)
@@ -162,6 +207,15 @@ private bool IsNewAliasCmdlet(CommandAst commandAst)
162207
return false;
163208
}
164209

210+
/// <summary>
211+
/// Determines if analyzing a script module.
212+
/// </summary>
213+
/// <returns>True is file name ends with ".psm1"</returns>
214+
private bool IsScriptModule()
215+
{
216+
return fileName.EndsWith(".psm1");
217+
}
218+
165219
public string GetCommonName()
166220
{
167221
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidGlobalAliasesCommonName);

Rules/AvoidGlobalFunctions.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
2-
using System;
1+
using System;
32
using System.Collections.Generic;
43
#if !CORECLR
54
using System.ComponentModel.Composition;
65
#endif
76
using System.Globalization;
87
using System.Management.Automation.Language;
8+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
99

1010
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1111
{
@@ -17,6 +17,12 @@ public class AvoidGlobalFunctions : AstVisitor, IScriptRule
1717
private List<DiagnosticRecord> records;
1818
private string fileName;
1919

20+
/// <summary>
21+
/// Analyzes the ast to check that global functions are not used within modules.
22+
/// </summary>
23+
/// <param name="ast">The script's ast</param>
24+
/// <param name="fileName">The script's file name</param>
25+
/// <returns>A List of diagnostic results of this rule</returns>
2026
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
2127
{
2228
if (ast == null)
@@ -27,15 +33,23 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
2733
records = new List<DiagnosticRecord>();
2834
this.fileName = fileName;
2935

30-
ast.Visit(this);
36+
if (IsScriptModule())
37+
{
38+
ast.Visit(this);
39+
}
3140

3241
return records;
3342
}
3443

3544
#region VisitCommand functions
45+
/// <summary>
46+
/// Analyzes a FunctionDefinitionAst, if it is declared global a diagnostic record is created.
47+
/// </summary>
48+
/// <param name="functionDefinitionAst">FunctionDefinitionAst to be analyzed</param>
49+
/// <returns>AstVisitAction to continue analysis</returns>
3650
public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
3751
{
38-
if (functionDefinitionAst.Name.StartsWith("Global:", StringComparison.OrdinalIgnoreCase) && IsModule())
52+
if (functionDefinitionAst.Name.StartsWith("Global:", StringComparison.OrdinalIgnoreCase))
3953
{
4054
var functionNameExtent = Helper.Instance.GetScriptExtentForFunctionName(functionDefinitionAst);
4155

@@ -52,7 +66,11 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
5266
}
5367
#endregion
5468

55-
private bool IsModule()
69+
/// <summary>
70+
/// Determines if analyzing a script module.
71+
/// </summary>
72+
/// <returns>True is file name ends with ".psm1"</returns>
73+
private bool IsScriptModule()
5674
{
5775
return fileName.EndsWith(".psm1");
5876
}

Tests/Rules/AvoidGlobalAliases.tests.ps1

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,19 @@ $AvoidGlobalAliasesError = "Avoid creating aliases with a Global scope."
44
$violationName = "AvoidGlobalAliases"
55

66
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
7-
$violations = Invoke-ScriptAnalyzer $directory\AvoidGlobalAliases.ps1 | Where-Object {$_.RuleName -eq $violationName}
7+
$violations = Invoke-ScriptAnalyzer $directory\AvoidGlobalAliases.psm1 | Where-Object {$_.RuleName -eq $violationName}
88
$noViolations = Invoke-ScriptAnalyzer $directory\AvoidGlobalAliasesNoViolations.ps1 | Where-Object {$_.RuleName -eq $violationName}
99

1010

1111
Describe "$violationName " {
1212
Context "When there are violations" {
13-
It "Has 5 avoid using empty Catch block violations" {
13+
It "Has 4 avoid global alias violations" {
1414
$violations.Count | Should Be 4
1515
}
1616

1717
It "Has the correct description message" {
1818
$violations[0].Message | Should Match $AvoidGlobalAliasesError
19-
$violations[1].Message | Should Match $AvoidGlobalAliasesError
20-
$violations[2].Message | Should Match $AvoidGlobalAliasesError
21-
$violations[3].Message | Should Match $AvoidGlobalAliasesError
2219
}
23-
2420
}
2521

2622
Context "When there are no violations" {

Tests/Rules/AvoidGlobalFunctions.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $noViolations = Invoke-ScriptAnalyzer $directory\AvoidGlobalFunctionsNoViolation
1010

1111
Describe "$violationName " {
1212
Context "When there are violations" {
13-
It "Has 5 avoid using empty Catch block violations" {
13+
It "Has 1 avoid global function violations" {
1414
$violations.Count | Should Be 1
1515
}
1616

0 commit comments

Comments
 (0)