Skip to content

Commit d8757e7

Browse files
author
Kapil Borle
committed
Initial commit of a rule that checks the *ToExport manifest fields for explicit lists.
1 parent 55f38b3 commit d8757e7

File tree

7 files changed

+208
-0
lines changed

7 files changed

+208
-0
lines changed

RuleDocumentation/UseManifestExportFields.md

Whitespace-only changes.

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<DependentUpon>Strings.resx</DependentUpon>
9696
</Compile>
9797
<Compile Include="UseBOMForUnicodeEncodedFile.cs" />
98+
<Compile Include="UseManifestExportFields.cs" />
9899
<Compile Include="UseOutputTypeCorrectly.cs" />
99100
<Compile Include="MissingModuleManifestField.cs" />
100101
<Compile Include="PossibleIncorrectComparisonWithNull.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
@@ -798,4 +798,16 @@
798798
<data name="AvoidNullOrEmptyHelpMessageAttributeName" xml:space="preserve">
799799
<value>AvoidNullOrEmptyHelpMessageAttribute</value>
800800
</data>
801+
<data name="UseManifestExportFieldsCommonName" xml:space="preserve">
802+
<value>Use the *ToExport module manifest fields.</value>
803+
</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>
806+
</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>
809+
</data>
810+
<data name="UseManifestExportFieldsName" xml:space="preserve">
811+
<value>UseManifestExportFields</value>
812+
</data>
801813
</root>

Rules/UseManifestExportFields.cs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.Management.Automation.Language;
16+
using System.Management.Automation;
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+
/// UseManifestExportFields: Run Test Module Manifest to check that no deprecated fields are being used.
25+
/// </summary>
26+
[Export(typeof(IScriptRule))]
27+
public class UseManifestExportFields : IScriptRule
28+
{
29+
/// <summary>
30+
/// AnalyzeScript: Run Test Module Manifest to check that no deprecated fields are being used.
31+
/// </summary>
32+
/// <param name="ast">The script's ast</param>
33+
/// <param name="fileName">The script's file name</param>
34+
/// <returns>A List of diagnostic results of this rule</returns>
35+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
36+
{
37+
if (ast == null)
38+
{
39+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
40+
}
41+
42+
if (!fileName.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase))
43+
{
44+
yield break;
45+
}
46+
47+
String[] manifestFields = {"FunctionsToExport", "CmdletsToExport", "VariablesToExport", "AliasesToExport"};
48+
var hashtableAst = ast.Find(x => x is HashtableAst, false) as HashtableAst;
49+
50+
if (hashtableAst == null)
51+
{
52+
//Should we emit a warning if the parser cannot find a hashtable?
53+
yield break;
54+
}
55+
56+
foreach(String field in manifestFields)
57+
{
58+
IScriptExtent extent;
59+
if (!HasAcceptableExportField(field, hashtableAst, out extent) && extent != null)
60+
{
61+
yield return new DiagnosticRecord(GetError(field), extent, GetName(), DiagnosticSeverity.Warning, fileName);
62+
}
63+
}
64+
65+
}
66+
67+
private bool HasAcceptableExportField(string key, HashtableAst hast, out IScriptExtent extent)
68+
{
69+
extent = null;
70+
foreach (var pair in hast.KeyValuePairs)
71+
{
72+
if (key.Equals(pair.Item1.Extent.Text.Trim(), StringComparison.OrdinalIgnoreCase))
73+
{
74+
var arrayAst = pair.Item2.Find(x => x is ArrayLiteralAst, true) as ArrayLiteralAst;
75+
if (arrayAst == null)
76+
{
77+
extent = GetScriptExtent(pair);
78+
return false;
79+
}
80+
else
81+
{
82+
return true;
83+
}
84+
}
85+
}
86+
return true;
87+
}
88+
89+
90+
private ScriptExtent GetScriptExtent(Tuple<ExpressionAst, StatementAst> pair)
91+
{
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));
100+
}
101+
102+
public string GetError(string field)
103+
{
104+
return string.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsError, field);
105+
}
106+
107+
/// <summary>
108+
/// GetName: Retrieves the name of this rule.
109+
/// </summary>
110+
/// <returns>The name of this rule</returns>
111+
public string GetName()
112+
{
113+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseManifestExportFieldsName);
114+
}
115+
116+
/// <summary>
117+
/// GetCommonName: Retrieves the common name of this rule.
118+
/// </summary>
119+
/// <returns>The common name of this rule</returns>
120+
public string GetCommonName()
121+
{
122+
return String.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsCommonName);
123+
}
124+
125+
/// <summary>
126+
/// GetDescription: Retrieves the description of this rule.
127+
/// </summary>
128+
/// <returns>The description of this rule</returns>
129+
public string GetDescription()
130+
{
131+
return String.Format(CultureInfo.CurrentCulture, Strings.UseManifestExportFieldsDescription);
132+
}
133+
134+
/// <summary>
135+
/// Method: Retrieves the type of the rule: builtin, managed or module.
136+
/// </summary>
137+
public SourceType GetSourceType()
138+
{
139+
return SourceType.Builtin;
140+
}
141+
142+
/// <summary>
143+
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
144+
/// </summary>
145+
/// <returns></returns>
146+
public RuleSeverity GetSeverity()
147+
{
148+
return RuleSeverity.Warning;
149+
}
150+
151+
/// <summary>
152+
/// Method: Retrieves the module/assembly name the rule is from.
153+
/// </summary>
154+
public string GetSourceName()
155+
{
156+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
157+
}
158+
}
159+
}

Tests/Rules/UseManifestExportFields.ps1

Whitespace-only changes.

Tests/Rules/UseManifestExportFields.tests.ps1

Whitespace-only changes.

0 commit comments

Comments
 (0)