diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index c2cced646..4a7a61413 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -210,6 +210,12 @@ public SwitchParameter SaveDscDependency /// protected override void BeginProcessing() { + // Initialize helper + Helper.Instance = new Helper( + SessionState.InvokeCommand, + this); + Helper.Instance.Initialize(); + string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); diff --git a/Engine/Helper.cs b/Engine/Helper.cs index fa3419347..c8b26c274 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -35,6 +35,7 @@ public class Helper private IOutputWriter outputWriter; private Object getCommandLock = new object(); private readonly static Version minSupportedPSVersion = new Version(3, 0); + private Dictionary> ruleArguments; #endregion @@ -113,14 +114,14 @@ internal set /// private Helper() { - + } /// /// Initializes the Helper class. /// /// - /// A CommandInvocationIntrinsics instance for use in gathering + /// A CommandInvocationIntrinsics instance for use in gathering /// information about available commands and aliases. /// /// @@ -145,6 +146,7 @@ public void Initialize() AliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); KeywordBlockDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); VariableAnalysisDictionary = new Dictionary(); + ruleArguments = new Dictionary>(StringComparer.OrdinalIgnoreCase); IEnumerable aliases = this.invokeCommand.GetCommands("*", CommandTypes.Alias, true); @@ -163,6 +165,59 @@ public void Initialize() } } + /// + /// Returns all the rule arguments + /// + /// Dictionary that maps between rule name to their named arguments + public Dictionary> GetRuleArguments() + { + return ruleArguments; + } + + /// + /// Get the parameters corresponding to the given rule name + /// + /// + /// Dictionary of argument names mapped to values. If ruleName is not a valid key, returns null + public Dictionary GetRuleArguments(string ruleName) + { + if (ruleArguments.ContainsKey(ruleName)) + { + return ruleArguments[ruleName]; + } + return null; + } + + /// + /// Sets the arguments for consumption by rules + /// + /// A hashtable with rule names as keys + public void SetRuleArguments(Dictionary ruleArgs) + { + if (ruleArgs == null) + { + return; + } + + if (ruleArgs.Comparer != StringComparer.OrdinalIgnoreCase) + { + throw new ArgumentException( + "Input dictionary should have OrdinalIgnoreCase comparer.", + "ruleArgs"); + } + var ruleArgsDict = new Dictionary>(); + foreach (var rule in ruleArgs.Keys) + { + var argsDict = ruleArgs[rule] as Dictionary; + if (argsDict == null) + { + return; + } + ruleArgsDict[rule] = argsDict; + } + ruleArguments = ruleArgsDict; + } + /// /// Given a cmdlet, return the list of all the aliases. /// Also include the original name in the list. @@ -231,7 +286,7 @@ public bool IsDscResourceModule(string filePath) return false; } - + /// /// Gets the module manifest /// @@ -471,13 +526,13 @@ private string NameWithoutScope(string name, string[] scopes) if (String.IsNullOrWhiteSpace(name) || scopes == null) { return name; - } + } // checks whether function name starts with scope foreach (string scope in scopes) { // trim the scope part - if (name.IndexOf(scope, StringComparison.OrdinalIgnoreCase) == 0) + if (name.IndexOf(scope, StringComparison.OrdinalIgnoreCase) == 0) { return name.Substring(scope.Length); @@ -598,7 +653,7 @@ public bool PositionalParameterUsed(CommandAst cmdAst, bool moreThanThreePositio { arguments += 1; } - + } // if not the first element in a pipeline, increase the number of arguments by 1 @@ -724,10 +779,10 @@ public IScriptExtent GetScriptExtentForFunctionName(FunctionDefinitionAst functi if (null == functionDefinitionAst) { return null; - } + } var funcNameTokens = Tokens.Where( - token => - ContainsExtent(functionDefinitionAst.Extent, token.Extent) + token => + ContainsExtent(functionDefinitionAst.Extent, token.Extent) && token.Text.Equals(functionDefinitionAst.Name)); var funcNameToken = funcNameTokens.FirstOrDefault(); return funcNameToken == null ? null : funcNameToken.Extent; @@ -919,7 +974,7 @@ internal VariableAnalysis InitializeVariableAnalysisHelper(Ast ast, VariableAnal /// /// /// - + #if PSV3 public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret) @@ -990,7 +1045,7 @@ public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret, /// /// /// - + #if PSV3 public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast scopeAst) @@ -1050,9 +1105,9 @@ public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast /// /// /// - + #if PSV3 - + internal string GetTypeFromMemberExpressionAstHelper(MemberExpressionAst memberAst, VariableAnalysisDetails analysisDetails) #else @@ -1241,8 +1296,8 @@ internal List GetSuppressionsClass(TypeDefinitionAst typeAst) if (typeAst.Members == null) { - return result; - } + return result; + } foreach (var member in typeAst.Members) { @@ -1469,11 +1524,11 @@ public static string[] ProcessCustomRulePaths(string[] rulePaths, SessionState s } outPaths.Add(path); } - + return outPaths.ToArray(); - + } - + /// /// Check if the function name starts with one of potentailly state changing verbs /// @@ -1671,8 +1726,8 @@ public static IEnumerable GetDeprecatedModuleManifestKeys() /// /// Get a mapping between string type keys and StatementAsts from module manifest hashtable ast - /// - /// This is a workaround as SafeGetValue is not supported on PS v5 and below. + /// + /// This is a workaround as SafeGetValue is not supported on PS v4 and below. /// /// Hashtable Ast obtained from module manifest /// A dictionary that maps string keys to values of StatementAst type @@ -1693,7 +1748,7 @@ private static Dictionary GetMapFromHashtableAst(Hashtable /// /// Checks if the version is supported - /// + /// /// PowerShell versions with Major greater than 3 are supported /// /// PowerShell version @@ -1910,7 +1965,7 @@ private object VisitStatementHelper(StatementAst statementAst) #if PSV3 statementAst.Visit(this); - + #else TypeDefinitionAst typeAst = statementAst as TypeDefinitionAst; @@ -2664,7 +2719,7 @@ static FindPipelineOutput() /// Find the pipeline output /// /// - + #if PSV3 public FindPipelineOutput(FunctionDefinitionAst ast) @@ -2695,7 +2750,7 @@ public FindPipelineOutput(FunctionDefinitionAst ast, IEnumerable /// - + #if PSV3 public static List> OutputTypes(FunctionDefinitionAst funcAst) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index a9d3c1387..16d650e44 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -40,6 +40,7 @@ public sealed class ScriptAnalyzer #region Private members private IOutputWriter outputWriter; + private Dictionary settings; #if !CORECLR private CompositionContainer container; #endif // !CORECLR @@ -87,13 +88,10 @@ public static ScriptAnalyzer Instance #else [ImportMany] public IEnumerable ScriptRules { get; private set; } - [ImportMany] public IEnumerable TokenRules { get; private set; } - [ImportMany] public IEnumerable Loggers { get; private set; } - [ImportMany] public IEnumerable DSCResourceRules { get; private set; } // Initializes via ImportMany @@ -168,6 +166,13 @@ public void Initialize( throw new ArgumentNullException("runspace"); } + //initialize helper + Helper.Instance = new Helper( + runspace.SessionStateProxy.InvokeCommand, + outputWriter); + Helper.Instance.Initialize(); + + this.Initialize( outputWriter, runspace.SessionStateProxy.Path, @@ -199,11 +204,13 @@ internal bool ParseProfile(object profileObject, PathIntrinsics path, IOutputWri // profile was not given if (profileObject == null) { + writer.WriteVerbose("No settings hashtable or file to consume"); return true; } if (!(profileObject is string || profileObject is Hashtable)) { + writer.WriteWarning("Settings parameter should be a hashtable or a filepath"); return false; } @@ -239,7 +246,12 @@ internal bool ParseProfile(object profileObject, PathIntrinsics path, IOutputWri this.severity = (severityList.Count() == 0) ? null : severityList.ToArray(); this.includeRule = (includeRuleList.Count() == 0) ? null : includeRuleList.ToArray(); this.excludeRule = (excludeRuleList.Count() == 0) ? null : excludeRuleList.ToArray(); - + if (settings != null + && settings.ContainsKey("Rules")) + { + var ruleArgs = settings["Rules"] as Dictionary; + Helper.Instance.SetRuleArguments(ruleArgs); + } return true; } @@ -273,87 +285,284 @@ private bool AddProfileItem( return true; } - private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutputWriter writer, - List severityList, List includeRuleList, List excludeRuleList) + private Dictionary GetDictionaryFromHashTableAst( + HashtableAst hashTableAst, + IOutputWriter writer, + string profile, + out bool hasError) { - bool hasError = false; - - HashSet validKeys = new HashSet(StringComparer.OrdinalIgnoreCase); - validKeys.Add("severity"); - validKeys.Add("includerules"); - validKeys.Add("excluderules"); - - foreach (var obj in profile.Keys) + hasError = false; + var output = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in hashTableAst.KeyValuePairs) { - string key = obj as string; - - // key should be a string - if (key == null) + var keyAst = kvp.Item1 as StringConstantExpressionAst; + if (keyAst == null) { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), - Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); + // first item (the key) should be a string + writer.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongKeyFormat, + kvp.Item1.Extent.StartLineNumber, + kvp.Item1.Extent.StartColumnNumber, + profile)), + Strings.ConfigurationKeyNotAString, + ErrorCategory.InvalidData, + profile)); hasError = true; continue; } + var key = keyAst.Value; + // parse the item2 as array + PipelineAst pipeAst = kvp.Item2 as PipelineAst; + List rhsList = new List(); + if (pipeAst != null) + { + ExpressionAst pureExp = pipeAst.GetPureExpression(); + if (pureExp is StringConstantExpressionAst) + { + rhsList.Add((pureExp as StringConstantExpressionAst).Value); + } + else if (pureExp is HashtableAst) + { + output[key] = GetDictionaryFromHashTableAst( + pureExp as HashtableAst, + writer, + profile, + out hasError); + continue; + } + else + { + ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst; + if (arrayLitAst == null && pureExp is ArrayExpressionAst) + { + ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst; + // Statements property is never null + if (arrayExp.SubExpression != null) + { + StatementAst stateAst = arrayExp.SubExpression.Statements.FirstOrDefault(); + if (stateAst != null && stateAst is PipelineAst) + { + CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); + if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) + { + CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; + if (cmdExpAst.Expression is StringConstantExpressionAst) + { + rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); + } + else + { + arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; + } + } + } + } + } + + if (arrayLitAst != null) + { + foreach (var element in arrayLitAst.Elements) + { + // all the values in the array needs to be string + if (!(element is StringConstantExpressionAst)) + { + outputWriter.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongValueFormat, + element.Extent.StartLineNumber, + element.Extent.StartColumnNumber, + "")), + Strings.ConfigurationValueNotAString, + ErrorCategory.InvalidData, + null)); + hasError = true; + continue; + } + + rhsList.Add((element as StringConstantExpressionAst).Value); + } + } + } + } - // checks whether it falls into list of valid keys - if (!validKeys.Contains(key)) + if (rhsList.Count == 0) { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + writer.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongValueFormat, + kvp.Item2.Extent.StartLineNumber, + kvp.Item2.Extent.StartColumnNumber, + profile)), + Strings.ConfigurationValueWrongFormat, + ErrorCategory.InvalidData, + profile)); hasError = true; continue; } + output[key] = rhsList; + } + return output; + } - object value = profile[obj]; - - // value must be either string or collections of string or array - if (value == null || !(value is string || value is IEnumerable || value.GetType().IsArray)) + /// + /// Recursively convert hashtable to dictionary + /// + /// + /// Dictionary that maps string to object + private Dictionary GetDictionaryFromHashtable( + Hashtable hashtable, + IOutputWriter writer, + out bool hasError) + { + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + hasError = false; + foreach (var obj in hashtable.Keys) + { + string key = obj as string; + if (key == null) { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), + Strings.ConfigurationKeyNotAString, + ErrorCategory.InvalidData, + hashtable)); hasError = true; - continue; + return null; } - - // if we get here then everything is good - - List values = new List(); - - if (value is string) + var valueHashtableObj = hashtable[obj]; + if (valueHashtableObj == null) { - values.Add(value as string); + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, valueHashtableObj, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + hashtable)); + hasError = true; + return null; } - else if (value is IEnumerable) + var valueHashtable = valueHashtableObj as Hashtable; + if (valueHashtable == null) { - values.Union(value as IEnumerable); + dictionary.Add(key, valueHashtableObj); } - else if (value.GetType().IsArray) + else { - // for array case, sometimes we won't be able to cast it directly to IEnumerable - foreach (var val in value as IEnumerable) + var dict = GetDictionaryFromHashtable(valueHashtable, writer, out hasError); + if (dict != null) { - if (val is string) + dictionary.Add(key, dict); + } + else + { + return null; + } + } + + } + return dictionary; + } + + private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutputWriter writer, + List severityList, List includeRuleList, List excludeRuleList) + { + bool hasError = false; + + HashSet validKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + validKeys.Add("severity"); + validKeys.Add("includerules"); + validKeys.Add("excluderules"); + validKeys.Add("rules"); + + settings = GetDictionaryFromHashtable(profile, writer, out hasError); + if (hasError) + { + return hasError; + } + foreach (var settingKey in settings.Keys) + { + var key = settingKey.ToLower(); + object value = settings[key]; + switch (key) + { + case "severity": + case "includerules": + case "excluderules": + // value must be either string or collections of string or array + if (value == null + || !(value is string + || value is IEnumerable + || value.GetType().IsArray)) { - values.Add(val as string); + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); + hasError = true; + break; } - else + List values = new List(); + if (value is string) { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; + values.Add(value as string); } - } + else if (value is IEnumerable) + { + values.Union(value as IEnumerable); + } + else if (value.GetType().IsArray) + { + // for array case, sometimes we won't be able to cast it directly to IEnumerable + foreach (var val in value as IEnumerable) + { + if (val is string) + { + values.Add(val as string); + } + else + { + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); + hasError = true; + break; + } + } + } + AddProfileItem(key, values, severityList, includeRuleList, excludeRuleList); + settings[key] = values; + break; + + case "rules": + break; + + default: + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); + hasError = true; + break; } - - AddProfileItem(key, values, severityList, includeRuleList, excludeRuleList); - } - return hasError; } @@ -390,91 +599,31 @@ private bool ParseProfileString(string profile, PathIntrinsics path, IOutputWrit else { HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst; - - foreach (var kvp in hashTableAst.KeyValuePairs) + settings = GetDictionaryFromHashTableAst( + hashTableAst, + writer, + profile, + out hasError); + foreach (var key in settings.Keys) { - if (!(kvp.Item1 is StringConstantExpressionAst)) - { - // first item (the key) should be a string - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), - Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; - } - - // parse the item2 as array - PipelineAst pipeAst = kvp.Item2 as PipelineAst; - List rhsList = new List(); - if (pipeAst != null) - { - ExpressionAst pureExp = pipeAst.GetPureExpression(); - if (pureExp is StringConstantExpressionAst) - { - rhsList.Add((pureExp as StringConstantExpressionAst).Value); - } - else - { - ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst; - if (arrayLitAst == null && pureExp is ArrayExpressionAst) - { - ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst; - // Statements property is never null - if (arrayExp.SubExpression != null) - { - StatementAst stateAst = arrayExp.SubExpression.Statements.FirstOrDefault(); - if (stateAst != null && stateAst is PipelineAst) - { - CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); - if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) - { - CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; - if (cmdExpAst.Expression is StringConstantExpressionAst) - { - rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); - } - else - { - arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; - } - } - } - } - } - - if (arrayLitAst != null) - { - foreach (var element in arrayLitAst.Elements) - { - // all the values in the array needs to be string - if (!(element is StringConstantExpressionAst)) - { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile)), - Strings.ConfigurationValueNotAString, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; - } - - rhsList.Add((element as StringConstantExpressionAst).Value); - } - } - } - } - - if (rhsList.Count == 0) + var rhsList = settings[key] as List; + if (rhsList == null) { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile)), - Strings.ConfigurationValueWrongFormat, ErrorCategory.InvalidData, profile)); - hasError = true; continue; } - - string key = (kvp.Item1 as StringConstantExpressionAst).Value.ToLower(); - - if(!AddProfileItem(key, rhsList, severityList, includeRuleList, excludeRuleList)) + if (!AddProfileItem(key, rhsList, severityList, includeRuleList, excludeRuleList)) { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, key, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + writer.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongKey, + key, + profile)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); hasError = true; } } @@ -517,7 +666,6 @@ private void Initialize( // But for Core CLR we need to load it explicitly this.Loggers = GetInterfaceImplementationsFromAssembly(); #endif - #region Initializes Rules var includeRuleList = new List(); @@ -735,10 +883,6 @@ private void LoadRules(Dictionary> result, CommandInvocatio { List paths = new List(); - // Initialize helper - Helper.Instance = new Helper(invokeCommand, this.outputWriter); - Helper.Instance.Initialize(); - // Clear external rules for each invoke. this.ScriptRules = null; this.TokenRules = null; @@ -1887,7 +2031,7 @@ public IEnumerable AnalyzeSyntaxTree( } } } - + #endregion // Need to reverse the concurrentbag to ensure that results are sorted in the increasing order of line numbers diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 379b35d13..7af798344 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -385,7 +385,7 @@ internal static string WrongConfigurationKey { } /// - /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. + /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable: file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. /// internal static string WrongKey { get { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index aa1a3f779..e1e0de031 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -193,7 +193,7 @@ Cannot find any DiagnosticRecord with the Rule Suppression ID {0}. - {0} is not a valid key in the settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity. + {0} is not a valid key in the settings hashtable: file {3}. Valid keys are ExcludeRules, IncludeRules and Severity. Key in the settings hashtable should be a string: line {0} column {1} in file {2} diff --git a/Rules/AvoidAlias.cs b/Rules/AvoidAlias.cs index c5e325abe..b2a95f3ea 100644 Binary files a/Rules/AvoidAlias.cs and b/Rules/AvoidAlias.cs differ diff --git a/Tests/Rules/AvoidUsingAlias.tests.ps1 b/Tests/Rules/AvoidUsingAlias.tests.ps1 index f304b0318..f6d8005d2 100644 --- a/Tests/Rules/AvoidUsingAlias.tests.ps1 +++ b/Tests/Rules/AvoidUsingAlias.tests.ps1 @@ -31,4 +31,27 @@ Describe "AvoidUsingAlias" { $noViolations.Count | Should Be 0 } } + + Context "Settings file provides whitelist" { + $whiteListTestScriptDef = 'gci; cd; iwr' + + It "honors the whitelist provided as hashtable" { + $settings = @{ + 'Rules' = @{ + 'PSAvoidUsingCmdletAliases' = @{ + 'Whitelist' = @('cd') + } + } + } + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $whiteListTestScriptDef -Settings $settings -IncludeRule $violationName + $violations.Count | Should Be 2 + } + + It "honors the whitelist provided through settings file" { + # even though join-path returns string, if we do not use tostring, then invoke-scriptanalyzer cannot cast it to string type + $settingsFilePath = (Join-Path $directory (Join-Path 'TestSettings' 'AvoidAliasSettings.psd1')).ToString() + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $whiteListTestScriptDef -Settings $settingsFilePath -IncludeRule $violationName + $violations.Count | Should be 2 + } + } } \ No newline at end of file diff --git a/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 b/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 new file mode 100644 index 000000000..e5a4cb1af --- /dev/null +++ b/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 @@ -0,0 +1,7 @@ +@{ + 'Rules' = @{ + 'PSAvoidUsingCmdletAliases' = @{ + 'Whitelist' = @('cd') + } + } +} \ No newline at end of file