Skip to content

Commit dcb94b3

Browse files
author
Kapil Borle
authored
Merge pull request #615 from PowerShell/kapilmb/RuleCreateHashtableWithLiteralInitializer
Create rule to warn when using constructor for hashtable
2 parents 219794d + 974b1d1 commit dcb94b3

10 files changed

+658
-83
lines changed

Engine/Generic/CorrectionExtent.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,16 @@ public string Description
7373
private int endColumnNumber;
7474
private string text;
7575
private string description;
76-
76+
7777
public CorrectionExtent(
78-
int startLineNumber,
79-
int endLineNumber,
80-
int startColumnNumber,
81-
int endColumnNumber,
82-
string text,
83-
string file)
78+
int startLineNumber,
79+
int endLineNumber,
80+
int startColumnNumber,
81+
int endColumnNumber,
82+
string text,
83+
string file)
8484
: this(
85-
startLineNumber,
85+
startLineNumber,
8686
endLineNumber,
8787
startColumnNumber,
8888
endColumnNumber,
@@ -93,11 +93,11 @@ public CorrectionExtent(
9393
}
9494

9595
public CorrectionExtent(
96-
int startLineNumber,
97-
int endLineNumber,
98-
int startColumnNumber,
99-
int endColumnNumber,
100-
string text,
96+
int startLineNumber,
97+
int endLineNumber,
98+
int startColumnNumber,
99+
int endColumnNumber,
100+
string text,
101101
string file,
102102
string description)
103103
{
@@ -115,9 +115,8 @@ public CorrectionExtent(
115115

116116
private void ThrowIfInvalidArguments()
117117
{
118-
ThrowIfNull<string>(file, "filename");
119118
ThrowIfNull<string>(text, "text");
120-
ThrowIfDecreasing(startLineNumber, endLineNumber, "start line number cannot be less than end line number");
119+
ThrowIfDecreasing(startLineNumber, endLineNumber, "start line number cannot be less than end line number");
121120
if (startLineNumber == endLineNumber)
122121
{
123122
ThrowIfDecreasing(StartColumnNumber, endColumnNumber, "start column number cannot be less than end column number for a one line extent");

Engine/Helper.cs

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,52 @@ public static bool IsMissingManifestMemberException(ErrorRecord errorRecord)
342342
&& string.Equals("MissingMemberException", errorRecord.CategoryInfo.Reason, StringComparison.OrdinalIgnoreCase);
343343
}
344344

345+
public IEnumerable<string> GetStringsFromExpressionAst(ExpressionAst exprAst)
346+
{
347+
if (exprAst == null)
348+
{
349+
throw new ArgumentNullException("exprAst");
350+
}
351+
352+
var result = new List<string>();
353+
if (exprAst is StringConstantExpressionAst)
354+
{
355+
result.Add((exprAst as StringConstantExpressionAst).Value);
356+
}
357+
// Array of the form "v-n", "v-n1"
358+
else if (exprAst is ArrayLiteralAst)
359+
{
360+
result.AddRange(Helper.Instance.GetStringsFromArrayLiteral(exprAst as ArrayLiteralAst));
361+
}
362+
// Array of the form @("v-n", "v-n1")
363+
else if (exprAst is ArrayExpressionAst)
364+
{
365+
ArrayExpressionAst arrExAst = exprAst as ArrayExpressionAst;
366+
if (arrExAst.SubExpression != null && arrExAst.SubExpression.Statements != null)
367+
{
368+
foreach (StatementAst stAst in arrExAst.SubExpression.Statements)
369+
{
370+
if (stAst is PipelineAst)
371+
{
372+
PipelineAst pipeAst = stAst as PipelineAst;
373+
if (pipeAst.PipelineElements != null)
374+
{
375+
foreach (CommandBaseAst cmdBaseAst in pipeAst.PipelineElements)
376+
{
377+
if (cmdBaseAst is CommandExpressionAst)
378+
{
379+
result.AddRange(Helper.Instance.GetStringsFromArrayLiteral((cmdBaseAst as CommandExpressionAst).Expression as ArrayLiteralAst));
380+
}
381+
}
382+
}
383+
}
384+
}
385+
}
386+
}
387+
388+
return result;
389+
}
390+
345391
/// <summary>
346392
/// Get the list of exported function by analyzing the ast
347393
/// </summary>
@@ -433,41 +479,7 @@ public HashSet<string> GetExportedFunction(Ast ast)
433479

434480
if (exprAst != null)
435481
{
436-
// One string so just add this to the list
437-
if (exprAst is StringConstantExpressionAst)
438-
{
439-
exportedFunctions.Add((exprAst as StringConstantExpressionAst).Value);
440-
}
441-
// Array of the form "v-n", "v-n1"
442-
else if (exprAst is ArrayLiteralAst)
443-
{
444-
exportedFunctions.UnionWith(Helper.Instance.GetStringsFromArrayLiteral(exprAst as ArrayLiteralAst));
445-
}
446-
// Array of the form @("v-n", "v-n1")
447-
else if (exprAst is ArrayExpressionAst)
448-
{
449-
ArrayExpressionAst arrExAst = exprAst as ArrayExpressionAst;
450-
if (arrExAst.SubExpression != null && arrExAst.SubExpression.Statements != null)
451-
{
452-
foreach (StatementAst stAst in arrExAst.SubExpression.Statements)
453-
{
454-
if (stAst is PipelineAst)
455-
{
456-
PipelineAst pipeAst = stAst as PipelineAst;
457-
if (pipeAst.PipelineElements != null)
458-
{
459-
foreach (CommandBaseAst cmdBaseAst in pipeAst.PipelineElements)
460-
{
461-
if (cmdBaseAst is CommandExpressionAst)
462-
{
463-
exportedFunctions.UnionWith(Helper.Instance.GetStringsFromArrayLiteral((cmdBaseAst as CommandExpressionAst).Expression as ArrayLiteralAst));
464-
}
465-
}
466-
}
467-
}
468-
}
469-
}
470-
}
482+
exportedFunctions.UnionWith(Helper.Instance.GetStringsFromExpressionAst(exprAst));
471483
}
472484

473485
i += 1;

RuleDocumentation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
|[UseDeclaredVarsMoreThanAssignments](./UseDeclaredVarsMoreThanAssignments.md) | Warning|
3939
|[UseIdenticalMandatoryParametersDSC](./UseIdenticalMandatoryParametersDSC.md) | Error |
4040
|[UseIdenticalParametersDSC](./UseIdenticalParametersDSC.md) | Error |
41+
|[UseLiteralInitializerForHashtable](./UseLiteralInitializerForHashtable.md) | Warning |
4142
|[UseOutputTypeCorrectly](./UseOutputTypeCorrectly.md) | Information|
4243
|[UsePSCredentialType](./UsePSCredentialType.md) | Warning|
4344
|[UseShouldProcessCorrectly](./UseShouldProcessCorrectly.md) | Warning|
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# UseLiteralInitializerForHashtable
2+
**Severity Level: Warning**
3+
4+
## Description
5+
Creating a hashtable by either `[hashtable]::new()` or `New-Object -TypeName hashtable` will create a hashtable wherein the keys are looked-up in a case-sensitive manner, unless an `IEqualityComparer` object is passed as an argument. However, PowerShell is case-insensitive in nature and it is best to create hashtables with case-insensitive key look-up. This rule is intended to warn the author of the case-sensitive nature of the hashtable if he/she creates a hashtable using the `new` member or the `New-Object` cmdlet.
6+
7+
## How to Fix
8+
Use the full cmdlet name and not an alias.
9+
10+
## Example
11+
### Wrong:
12+
``` PowerShell
13+
$hashtable = [hashtable]::new()
14+
```
15+
16+
### Wrong:
17+
``` PowerShell
18+
$hashtable = New-Object -TypeName hashtable
19+
```
20+
21+
### Correct:
22+
``` PowerShell
23+
$hashtable = @{}
24+
```

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="UseLiteralInitializerForHashtable.cs" />
9899
<Compile Include="UseToExportFieldsInManifest.cs" />
99100
<Compile Include="UseOutputTypeCorrectly.cs" />
100101
<Compile Include="MissingModuleManifestField.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: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->
@@ -822,4 +822,16 @@
822822
<data name="UseToExportFieldsInManifestCorrectionDescription" xml:space="preserve">
823823
<value>Replace {0} with {1}</value>
824824
</data>
825-
</root>
825+
<data name="UseLiteralInitilializerForHashtableCommonName" xml:space="preserve">
826+
<value>Create hashtables with literal initializers</value>
827+
</data>
828+
<data name="UseLiteralInitilializerForHashtableDescription" xml:space="preserve">
829+
<value>Use literal initializer, @{{}}, for creating a hashtable as they are case-insensitive by default</value>
830+
</data>
831+
<data name="UseLiteralInitilializerForHashtableError" xml:space="preserve">
832+
<value>Create hashtables with literal initliazers</value>
833+
</data>
834+
<data name="UseLiteralInitilializerForHashtableName" xml:space="preserve">
835+
<value>UseLiteralInitializerForHashtable</value>
836+
</data>
837+
</root>

0 commit comments

Comments
 (0)