Skip to content

Commit 961dd5e

Browse files
author
Kapil Borle
committed
Adds tests and documetation for the UseToExportFieldsInManifest
1 parent d8757e7 commit 961dd5e

17 files changed

+212
-70
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#UseToExportFieldsInManifest
2+
**Severity Level: Warning**
3+
4+
5+
##Description
6+
7+
In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module.
8+
9+
##How to Fix
10+
11+
Please consider using an explicit list.
12+
13+
##Example 1
14+
15+
Wrong:
16+
FunctionsToExport = $null
17+
18+
Correct:
19+
FunctionToExport = @()
20+
21+
##Example 2
22+
Suppose there are only two functions in your module, Get-Foo and Set-Foo that you want to export. Then,
23+
24+
Wrong:
25+
FunctionsToExport = '*'
26+
27+
Correct:
28+
FunctionToExport = @(Get-Foo, Set-Foo)

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
<DependentUpon>Strings.resx</DependentUpon>
9696
</Compile>
9797
<Compile Include="UseBOMForUnicodeEncodedFile.cs" />
98-
<Compile Include="UseManifestExportFields.cs" />
98+
<Compile Include="UseToExportFieldsInManifest.cs" />
9999
<Compile Include="UseOutputTypeCorrectly.cs" />
100100
<Compile Include="MissingModuleManifestField.cs" />
101101
<Compile Include="PossibleIncorrectComparisonWithNull.cs" />

Rules/Strings.Designer.cs

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

Rules/Strings.resx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -798,16 +798,16 @@
798798
<data name="AvoidNullOrEmptyHelpMessageAttributeName" xml:space="preserve">
799799
<value>AvoidNullOrEmptyHelpMessageAttribute</value>
800800
</data>
801-
<data name="UseManifestExportFieldsCommonName" xml:space="preserve">
801+
<data name="UseToExportFieldsInManifestCommonName" xml:space="preserve">
802802
<value>Use the *ToExport module manifest fields.</value>
803803
</data>
804-
<data name="UseManifestExportFieldsDescription" xml:space="preserve">
805-
<value>Specify entries for AliasToExport, CmdletsToExport, FunctionsToExport and do not use wildcards and $null in these entries. During module auto-discovery, if any of these entries are missing or $null, PowerShell do some potentially expensive work to analyze the rest of the module</value>
804+
<data name="UseToExportFieldsInManifestDescription" xml:space="preserve">
805+
<value>In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module.</value>
806806
</data>
807-
<data name="UseManifestExportFieldsError" xml:space="preserve">
808-
<value>Do not use wildcards and $null in these entry. Explicitly specify a list for {0}. </value>
807+
<data name="UseToExportFieldsInManifestError" xml:space="preserve">
808+
<value>Do not use wildcard or $null in this field. Explicitly specify a list for {0}. </value>
809809
</data>
810-
<data name="UseManifestExportFieldsName" xml:space="preserve">
811-
<value>UseManifestExportFields</value>
810+
<data name="UseToExportFieldsInManifestName" xml:space="preserve">
811+
<value>UseToExportFieldsInManifest</value>
812812
</data>
813813
</root>

Rules/UseManifestExportFields.cs renamed to Rules/UseToExportFieldsInManifest.cs

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
1818
using System.ComponentModel.Composition;
1919
using System.Globalization;
20+
using System.Text.RegularExpressions;
2021

2122
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
2223
{
2324
/// <summary>
24-
/// UseManifestExportFields: Run Test Module Manifest to check that no deprecated fields are being used.
25+
/// UseToExportFieldsInManifest: Checks if AliasToExport, CmdletsToExport, FunctionsToExport and VariablesToExport
26+
/// fields do not use wildcards and $null in their entries.
2527
/// </summary>
2628
[Export(typeof(IScriptRule))]
27-
public class UseManifestExportFields : IScriptRule
29+
public class UseToExportFieldsInManifest : IScriptRule
2830
{
2931
/// <summary>
30-
/// AnalyzeScript: Run Test Module Manifest to check that no deprecated fields are being used.
32+
/// AnalyzeScript: Analyzes the AST to check if AliasToExport, CmdletsToExport, FunctionsToExport
33+
/// and VariablesToExport fields do not use wildcards and $null in their entries.
3134
/// </summary>
3235
/// <param name="ast">The script's ast</param>
3336
/// <param name="fileName">The script's file name</param>
@@ -44,37 +47,62 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4447
yield break;
4548
}
4649

50+
if (!IsValidManifest(ast, fileName))
51+
{
52+
yield break;
53+
}
54+
4755
String[] manifestFields = {"FunctionsToExport", "CmdletsToExport", "VariablesToExport", "AliasesToExport"};
4856
var hashtableAst = ast.Find(x => x is HashtableAst, false) as HashtableAst;
49-
57+
5058
if (hashtableAst == null)
51-
{
52-
//Should we emit a warning if the parser cannot find a hashtable?
59+
{
5360
yield break;
5461
}
5562

5663
foreach(String field in manifestFields)
5764
{
5865
IScriptExtent extent;
59-
if (!HasAcceptableExportField(field, hashtableAst, out extent) && extent != null)
66+
if (!HasAcceptableExportField(field, hashtableAst, ast.Extent.Text, out extent) && extent != null)
6067
{
6168
yield return new DiagnosticRecord(GetError(field), extent, GetName(), DiagnosticSeverity.Warning, fileName);
6269
}
6370
}
6471

6572
}
6673

67-
private bool HasAcceptableExportField(string key, HashtableAst hast, out IScriptExtent extent)
74+
/// <summary>
75+
/// Checks if the manifest file is valid.
76+
/// </summary>
77+
/// <param name="ast"></param>
78+
/// <param name="fileName"></param>
79+
/// <returns>A boolean value indicating the validity of the manifest file.</returns>
80+
private bool IsValidManifest(Ast ast, string fileName)
81+
{
82+
var missingManifestRule = new MissingModuleManifestField();
83+
return !missingManifestRule.AnalyzeScript(ast, fileName).GetEnumerator().MoveNext();
84+
85+
}
86+
87+
/// <summary>
88+
/// Checks if the *ToExport fields are explicitly set to lists, @(...)
89+
/// </summary>
90+
/// <param name="key"></param>
91+
/// <param name="hast"></param>
92+
/// <param name="scriptText"></param>
93+
/// <param name="extent"></param>
94+
/// <returns>A boolean value indicating if the the ToExport fields are explicitly set to lists or not.</returns>
95+
private bool HasAcceptableExportField(string key, HashtableAst hast, string scriptText, out IScriptExtent extent)
6896
{
6997
extent = null;
7098
foreach (var pair in hast.KeyValuePairs)
7199
{
72100
if (key.Equals(pair.Item1.Extent.Text.Trim(), StringComparison.OrdinalIgnoreCase))
73101
{
74-
var arrayAst = pair.Item2.Find(x => x is ArrayLiteralAst, true) as ArrayLiteralAst;
102+
var arrayAst = pair.Item2.Find(x => x is ArrayLiteralAst || x is ArrayExpressionAst, true);
75103
if (arrayAst == null)
76104
{
77-
extent = GetScriptExtent(pair);
105+
extent = GetScriptExtent(pair, scriptText);
78106
return false;
79107
}
80108
else
@@ -85,23 +113,31 @@ private bool HasAcceptableExportField(string key, HashtableAst hast, out IScript
85113
}
86114
return true;
87115
}
88-
89-
90-
private ScriptExtent GetScriptExtent(Tuple<ExpressionAst, StatementAst> pair)
116+
117+
/// <summary>
118+
/// Gets the script extent.
119+
/// </summary>
120+
/// <param name="pair"></param>
121+
/// <param name="scriptText"></param>
122+
/// <returns></returns>
123+
private ScriptExtent GetScriptExtent(Tuple<ExpressionAst, StatementAst> pair, string scriptText)
91124
{
92-
return new ScriptExtent(new ScriptPosition(pair.Item1.Extent.StartScriptPosition.File,
93-
pair.Item1.Extent.StartScriptPosition.LineNumber,
94-
pair.Item1.Extent.StartScriptPosition.Offset,
95-
pair.Item1.Extent.StartScriptPosition.Line),
96-
new ScriptPosition(pair.Item2.Extent.EndScriptPosition.File,
97-
pair.Item2.Extent.EndScriptPosition.LineNumber,
98-
pair.Item2.Extent.EndScriptPosition.Offset,
99-
pair.Item2.Extent.EndScriptPosition.Line));
125+
string[] scriptLines = Regex.Split(scriptText, "\r\n|\r|\n");
126+
return new ScriptExtent(new ScriptPosition(pair.Item1.Extent.File,
127+
pair.Item1.Extent.StartLineNumber,
128+
pair.Item1.Extent.StartColumnNumber,
129+
scriptLines[pair.Item1.Extent.StartLineNumber - 1]), //line number begins with 1
130+
new ScriptPosition(pair.Item2.Extent.File,
131+
pair.Item2.Extent.EndLineNumber,
132+
pair.Item2.Extent.EndColumnNumber,
133+
scriptLines[pair.Item2.Extent.EndLineNumber - 1])); //line number begins with 1
134+
135+
100136
}
101137

102138
public string GetError(string field)
103139
{
104-
return string.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsError, field);
140+
return string.Format(CultureInfo.CurrentCulture, Strings.UseToExportFieldsInManifestError, field);
105141
}
106142

107143
/// <summary>
@@ -110,7 +146,7 @@ public string GetError(string field)
110146
/// <returns>The name of this rule</returns>
111147
public string GetName()
112148
{
113-
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseManifestExportFieldsName);
149+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseToExportFieldsInManifestName);
114150
}
115151

116152
/// <summary>
@@ -119,7 +155,7 @@ public string GetName()
119155
/// <returns>The common name of this rule</returns>
120156
public string GetCommonName()
121157
{
122-
return String.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsCommonName);
158+
return String.Format(CultureInfo.CurrentCulture, Strings.UseToExportFieldsInManifestCommonName);
123159
}
124160

125161
/// <summary>
@@ -128,7 +164,7 @@ public string GetCommonName()
128164
/// <returns>The description of this rule</returns>
129165
public string GetDescription()
130166
{
131-
return String.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsDescription);
167+
return String.Format(CultureInfo.CurrentCulture, Strings.UseToExportFieldsInManifestDescription);
132168
}
133169

134170
/// <summary>

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 40
59+
$defaultRules.Count | Should be 41
6060
}
6161
}
6262

Binary file not shown.
6.41 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)