Skip to content

Commit 02db72f

Browse files
author
Kapil Borle
committed
Add Settings class to manage pssa settings
1 parent fe17cb1 commit 02db72f

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

Engine/Generic/Settings.cs

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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;
15+
using System.Collections.Generic;
16+
using System.IO;
17+
using System.Linq;
18+
using System.Management.Automation.Language;
19+
20+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
21+
{
22+
public class Settings
23+
{
24+
private List<string> includeRules;
25+
private List<string> excludeRules;
26+
private List<string> severities;
27+
private Dictionary<string, Dictionary<string, object>> ruleArguments;
28+
29+
public IEnumerable<string> IncludeRules { get { return includeRules; }}
30+
public IEnumerable<string> ExcludeRules { get { return excludeRules; }}
31+
public IEnumerable<string> Severity { get { return severities; }}
32+
public Dictionary<string, Dictionary<string, object>> RuleArguments { get { return ruleArguments; }}
33+
34+
public Settings(object settings, Func<string, string> presetResolver)
35+
{
36+
if (settings == null)
37+
{
38+
throw new ArgumentNullException("settings");
39+
}
40+
41+
var settingsFilePath = settings as string;
42+
43+
//it can either be a preset or path to a file or a hashtable
44+
if (settingsFilePath != null)
45+
{
46+
if (presetResolver != null)
47+
{
48+
var resolvedFilePath = presetResolver(settingsFilePath);
49+
if (resolvedFilePath != null)
50+
{
51+
settingsFilePath = resolvedFilePath;
52+
}
53+
}
54+
55+
if (File.Exists(settingsFilePath))
56+
{
57+
parseSettingsFile(settingsFilePath);
58+
}
59+
else
60+
{
61+
throw new ArgumentException(String.Format("File does not exist: {0}", settingsFilePath));
62+
}
63+
}
64+
else
65+
{
66+
var settingsHashtable = settings as Hashtable;
67+
if (settingsHashtable != null)
68+
{
69+
parseSettingsHashtable(settingsHashtable);
70+
}
71+
else
72+
{
73+
throw new ArgumentException("Input object should either be a string or a hashtable");
74+
}
75+
}
76+
}
77+
78+
public Settings(object settings) : this(settings, null)
79+
{
80+
}
81+
82+
/// <summary>
83+
/// Recursively convert hashtable to dictionary
84+
/// </summary>
85+
/// <param name="hashtable"></param>
86+
/// <returns>Dictionary that maps string to object</returns>
87+
private Dictionary<string, object> GetDictionaryFromHashtable(Hashtable hashtable)
88+
{
89+
var dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
90+
foreach (var obj in hashtable.Keys)
91+
{
92+
string key = obj as string;
93+
if (key == null)
94+
{
95+
throw new InvalidDataException("key not string");
96+
// writer.WriteError(
97+
// new ErrorRecord(
98+
// new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)),
99+
// Strings.ConfigurationKeyNotAString,
100+
// ErrorCategory.InvalidData,
101+
// hashtable));
102+
// hasError = true;
103+
}
104+
var valueHashtableObj = hashtable[obj];
105+
if (valueHashtableObj == null)
106+
{
107+
throw new InvalidDataException("wrong hash table value");
108+
// writer.WriteError(
109+
// new ErrorRecord(
110+
// new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, valueHashtableObj, key)),
111+
// Strings.WrongConfigurationKey,
112+
// ErrorCategory.InvalidData,
113+
// hashtable));
114+
// hasError = true;
115+
// return null;
116+
}
117+
var valueHashtable = valueHashtableObj as Hashtable;
118+
if (valueHashtable == null)
119+
{
120+
dictionary.Add(key, valueHashtableObj);
121+
}
122+
else
123+
{
124+
dictionary.Add(key, GetDictionaryFromHashtable(valueHashtable));
125+
}
126+
}
127+
return dictionary;
128+
}
129+
130+
private bool IsStringOrStringArray(object val)
131+
{
132+
if (val is string)
133+
{
134+
return true;
135+
}
136+
137+
var valArr = val as object[];
138+
return val == null ? false : valArr.All(x => x is string);
139+
}
140+
141+
private List<string> GetData(object val, string key)
142+
{
143+
// value must be either string or or an array of strings
144+
if (val == null)
145+
{
146+
throw new InvalidDataException(
147+
String.Format(
148+
"value should be a string or string array for {0} key",
149+
key));
150+
// writer.WriteError(
151+
// new ErrorRecord(
152+
// new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)),
153+
// Strings.WrongConfigurationKey,
154+
// ErrorCategory.InvalidData,
155+
// profile));
156+
// hasError = true;
157+
// break;
158+
}
159+
160+
List<string> values = new List<string>();
161+
var valueStr = val as string;
162+
if (valueStr != null)
163+
{
164+
values.Add(valueStr);
165+
}
166+
else
167+
{
168+
var valueArr = val as object[];
169+
if (valueArr != null)
170+
{
171+
foreach (var item in valueArr)
172+
{
173+
var itemStr = item as string;
174+
if (itemStr != null)
175+
{
176+
values.Add(itemStr);
177+
}
178+
else
179+
{
180+
throw new InvalidDataException("array items should be of string type");
181+
// writer.WriteError(
182+
// new ErrorRecord(
183+
// new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)),
184+
// Strings.WrongConfigurationKey,
185+
// ErrorCategory.InvalidData,
186+
// profile));
187+
// hasError = true;
188+
// break;
189+
}
190+
}
191+
}
192+
else
193+
{
194+
throw new InvalidDataException("array items should be of string type");
195+
}
196+
}
197+
198+
return values;
199+
}
200+
201+
/// <summary>
202+
/// Sets the arguments for consumption by rules
203+
/// </summary>
204+
/// <param name="ruleArgs">A hashtable with rule names as keys</param>
205+
public Dictionary<string, Dictionary<string, object>> ConvertToRuleArgumentType(object ruleArguments)
206+
{
207+
var ruleArgs = ruleArguments as Dictionary<string, object>;
208+
if (ruleArgs == null)
209+
{
210+
throw new ArgumentException(
211+
"input should be a dictionary",
212+
"ruleArguments");
213+
}
214+
215+
if (ruleArgs.Comparer != StringComparer.OrdinalIgnoreCase)
216+
{
217+
throw new ArgumentException(
218+
"Input dictionary should have OrdinalIgnoreCase comparer.",
219+
"ruleArguments");
220+
}
221+
222+
var ruleArgsDict = new Dictionary<string, Dictionary<string, object>>(StringComparer.OrdinalIgnoreCase);
223+
foreach (var rule in ruleArgs.Keys)
224+
{
225+
var argsDict = ruleArgs[rule] as Dictionary<string, object>;
226+
if (argsDict == null)
227+
{
228+
throw new ArgumentException(
229+
"input should be a dictionary",
230+
"ruleArguments");
231+
}
232+
ruleArgsDict[rule] = argsDict;
233+
}
234+
235+
return ruleArgsDict;
236+
}
237+
238+
private void parseSettingsHashtable(Hashtable settingsHashtable)
239+
{
240+
HashSet<string> validKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
241+
var settings = GetDictionaryFromHashtable(settingsHashtable);
242+
foreach (var settingKey in settings.Keys)
243+
{
244+
var key = settingKey.ToLower();
245+
object val = settings[key];
246+
switch (key)
247+
{
248+
case "severity":
249+
severities = GetData(val, key);
250+
break;
251+
252+
case "includerules":
253+
includeRules = GetData(val, key);
254+
break;
255+
256+
case "excluderules":
257+
excludeRules = GetData(val, key);
258+
break;
259+
260+
case "rules":
261+
ruleArguments = ConvertToRuleArgumentType(val);
262+
break;
263+
264+
default:
265+
throw new InvalidDataException(String.Format("Invalid key: {0}", key));
266+
// writer.WriteError(
267+
// new ErrorRecord(
268+
// new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)),
269+
// Strings.WrongConfigurationKey,
270+
// ErrorCategory.InvalidData,
271+
// profile));
272+
// hasError = true;
273+
// break;
274+
}
275+
}
276+
}
277+
278+
private void parseSettingsFile(string settingsFilePath)
279+
{
280+
Token[] parserTokens = null;
281+
ParseError[] parserErrors = null;
282+
Ast profileAst = Parser.ParseFile(settingsFilePath, out parserTokens, out parserErrors);
283+
IEnumerable<Ast> hashTableAsts = profileAst.FindAll(item => item is HashtableAst, false);
284+
285+
// no hashtable, raise warning
286+
if (hashTableAsts.Count() == 0)
287+
{
288+
throw new ArgumentException("Given file does not contain a hashtable");
289+
// writer.WriteError(new ErrorRecord(new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)),
290+
// Strings.ConfigurationFileHasNoHashTable, ErrorCategory.ResourceUnavailable, profile));
291+
// hasError = true;
292+
}
293+
294+
HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst;
295+
Hashtable hashtable;
296+
try
297+
{
298+
hashtable = hashTableAst.SafeGetValue() as Hashtable;
299+
}
300+
catch (InvalidOperationException e)
301+
{
302+
throw new ArgumentException("input file has invalid hashtable", e);
303+
}
304+
305+
if (hashtable == null)
306+
{
307+
throw new ArgumentException("input file has invalid hashtable");
308+
}
309+
310+
parseSettingsHashtable(hashtable);
311+
}
312+
}
313+
}

Engine/ScriptAnalyzerEngine.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
<Compile Include="Generic\ConfigurableRule.cs" />
7575
<Compile Include="Generic\ModuleDependencyHandler.cs" />
7676
<Compile Include="Generic\CorrectionExtent.cs" />
77+
<Compile Include="Generic\Settings.cs" />
7778
<Compile Include="Generic\SuppressedRecord.cs" />
7879
<Compile Include="Generic\DiagnosticRecord.cs" />
7980
<Compile Include="Generic\ExternalRule.cs" />

0 commit comments

Comments
 (0)