Skip to content

Commit e925335

Browse files
author
Kapil Borle
committed
Add rule to check hashtable initialization
1 parent 219794d commit e925335

4 files changed

Lines changed: 245 additions & 28 deletions

File tree

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>Create a hashtable using a constructor without specifying IEqualityComparer for string type keys result in case-sensitive lookup of keys.</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>
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
#if !CORECLR
17+
using System.ComponentModel.Composition;
18+
#endif
19+
using System.Globalization;
20+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
21+
22+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
23+
{
24+
#if !CORECLR
25+
[Export(typeof(IScriptRule))]
26+
#endif
27+
class UseLiteralInitializerForHashtable : AstVisitor, IScriptRule
28+
{
29+
private List<DiagnosticRecord> diagnosticRecords;
30+
private HashSet<string> presetTypeNameSet;
31+
private string fileName;
32+
33+
public UseLiteralInitializerForHashtable()
34+
{
35+
var presetTypeNames = new string[]
36+
{
37+
"system.collection.hashtable",
38+
"collection.hashtable",
39+
"hashtable"
40+
};
41+
presetTypeNameSet = new HashSet<string>(presetTypeNames, StringComparer.OrdinalIgnoreCase);
42+
diagnosticRecords = new List<DiagnosticRecord>();
43+
}
44+
45+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
46+
{
47+
if (ast == null)
48+
{
49+
throw new ArgumentNullException("ast");
50+
}
51+
this.fileName = fileName;
52+
diagnosticRecords.Clear();
53+
ast.Visit(this);
54+
return diagnosticRecords;
55+
}
56+
57+
public override AstVisitAction VisitCommand(CommandAst commandAst)
58+
{
59+
if (commandAst == null
60+
|| commandAst.CommandElements.Count < 2)
61+
{
62+
return AstVisitAction.SkipChildren;
63+
}
64+
65+
if (!commandAst.GetCommandName().Equals("new-object", StringComparison.OrdinalIgnoreCase))
66+
{
67+
return AstVisitAction.Continue;
68+
}
69+
AnalyzeNewObjectCommand(commandAst);
70+
return AstVisitAction.Continue;
71+
}
72+
73+
private void AnalyzeNewObjectCommand(CommandAst commandAst)
74+
{
75+
//new-object hashtable
76+
var typeNameAst = commandAst.CommandElements[1] as StringConstantExpressionAst;
77+
if (typeNameAst != null)
78+
{
79+
if (presetTypeNameSet.Contains(typeNameAst.Value))
80+
{
81+
var dr = new DiagnosticRecord(
82+
Strings.UseLiteralInitilializerForHashtableDescription,
83+
commandAst.Extent,
84+
GetName(),
85+
GetDiagnosticSeverity(),
86+
fileName,
87+
ruleId: null,
88+
suggestedCorrections: null);
89+
diagnosticRecords.Add(dr);
90+
}
91+
}
92+
}
93+
94+
public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst)
95+
{
96+
if (methodCallAst == null)
97+
{
98+
return AstVisitAction.SkipChildren;
99+
}
100+
101+
var typeExprAst = methodCallAst.Expression as TypeExpressionAst;
102+
if (typeExprAst == null
103+
|| !presetTypeNameSet.Contains(typeExprAst.TypeName.FullName))
104+
{
105+
return AstVisitAction.Continue;
106+
}
107+
108+
var memberStringConstantExprAst = methodCallAst.Member as StringConstantExpressionAst;
109+
if (memberStringConstantExprAst == null
110+
|| !memberStringConstantExprAst.Value.Equals("new", StringComparison.OrdinalIgnoreCase))
111+
{
112+
return AstVisitAction.Continue;
113+
}
114+
115+
// no arguments provided to new
116+
if (methodCallAst.Arguments == null)
117+
{
118+
119+
var dr = new DiagnosticRecord(
120+
Strings.UseLiteralInitilializerForHashtableDescription,
121+
methodCallAst.Extent,
122+
GetName(),
123+
GetDiagnosticSeverity(),
124+
fileName,
125+
ruleId: null,
126+
suggestedCorrections: null);
127+
diagnosticRecords.Add(dr);
128+
}
129+
130+
return AstVisitAction.Continue;
131+
}
132+
133+
public string GetCommonName()
134+
{
135+
return Strings.UseLiteralInitilializerForHashtableCommonName;
136+
}
137+
138+
public string GetDescription()
139+
{
140+
return Strings.UseLiteralInitilializerForHashtableDescription;
141+
}
142+
143+
public string GetName()
144+
{
145+
return Strings.UseLiteralInitilializerForHashtableName;
146+
}
147+
148+
public RuleSeverity GetSeverity()
149+
{
150+
return RuleSeverity.Warning;
151+
}
152+
153+
private DiagnosticSeverity GetDiagnosticSeverity()
154+
{
155+
return DiagnosticSeverity.Warning;
156+
}
157+
158+
public string GetSourceName()
159+
{
160+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
161+
}
162+
163+
public SourceType GetSourceType()
164+
{
165+
return SourceType.Builtin;
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)