From f0e44b24d638ef0a37bd8367deac9637256e9bc7 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 20 Apr 2015 16:34:36 -0700 Subject: [PATCH 1/3] Add wildcard support when include/exclude rules --- .../Commands/InvokeScriptAnalyzerCommand.cs | 135 +++++++++++++++--- 1 file changed, 119 insertions(+), 16 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index cd04f621e..4b5731a76 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -10,6 +10,7 @@ // THE SOFTWARE. // +using System.Text.RegularExpressions; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System; using System.Collections.Generic; @@ -272,7 +273,28 @@ private void AnalyzeFile(string filePath) IEnumerable funcDefAsts; // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash - List> cmdInfoTable = new List>(); + List> cmdInfoTable = new List>(); + + //Check wild card input for the Include/ExcludRules and create regex match patterns + List includeRegexList = new List(); + List excludeRegexList = new List(); + if (includeRule != null) + { + foreach (string rule in includeRule) + { + Regex includeRegex = new Regex(rule.Replace("*", ".*?"), RegexOptions.IgnoreCase); + includeRegexList.Add(includeRegex); + } + } + if (excludeRule != null) + { + foreach (string rule in excludeRule) + { + Regex excludeRegex = new Regex(rule.Replace("*", ".*?"), RegexOptions.IgnoreCase); + excludeRegexList.Add(excludeRegex); + } + } + //Parse the file if (File.Exists(filePath)) @@ -316,12 +338,30 @@ private void AnalyzeFile(string filePath) #region Run ScriptRules //Trim down to the leaf element of the filePath and pass it to Diagnostic Record string fileName = System.IO.Path.GetFileName(filePath); + if (ScriptAnalyzer.Instance.ScriptRules != null) { foreach (IScriptRule scriptRule in ScriptAnalyzer.Instance.ScriptRules) { - if ((includeRule == null || includeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase)) && - (excludeRule == null || !excludeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase))) + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(scriptRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(scriptRule.GetName())) + { + excludeRegexMatch = true; + } + } + if ((includeRule == null || includeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && + (excludeRule == null || !excludeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); @@ -334,7 +374,6 @@ private void AnalyzeFile(string filePath) catch (Exception scriptRuleException) { WriteError(new ErrorRecord(scriptRuleException, Strings.RuleError, ErrorCategory.InvalidOperation, filePath)); - continue; } } } @@ -379,8 +418,25 @@ private void AnalyzeFile(string filePath) { foreach (ICommandRule commandRule in ScriptAnalyzer.Instance.CommandRules) { - if ((includeRule == null || includeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase)) && - (excludeRule == null || !excludeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase))) + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(commandRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(commandRule.GetName())) + { + excludeRegexMatch = true; + } + } + if ((includeRule == null || includeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && + (excludeRule == null || !excludeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) { foreach (KeyValuePair commandInfo in cmdInfoTable) { @@ -395,7 +451,6 @@ private void AnalyzeFile(string filePath) catch (Exception commandRuleException) { WriteError(new ErrorRecord(commandRuleException, Strings.RuleError, ErrorCategory.InvalidOperation, fileName)); - continue; } } } @@ -410,8 +465,25 @@ private void AnalyzeFile(string filePath) { foreach (ITokenRule tokenRule in ScriptAnalyzer.Instance.TokenRules) { - if ((includeRule == null || includeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase)) && - (excludeRule == null || !excludeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase))) + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(tokenRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(tokenRule.GetName())) + { + excludeRegexMatch = true; + } + } + if ((includeRule == null || includeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && + (excludeRule == null || !excludeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase)) || !excludeRegexMatch) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, tokenRule.GetName())); @@ -424,7 +496,6 @@ private void AnalyzeFile(string filePath) catch (Exception tokenRuleException) { WriteError(new ErrorRecord(tokenRuleException, Strings.RuleError, ErrorCategory.InvalidOperation, fileName)); - continue; } } } @@ -438,8 +509,25 @@ private void AnalyzeFile(string filePath) // Run DSC Class rule foreach (IDSCResourceRule dscResourceRule in ScriptAnalyzer.Instance.DSCResourceRules) { - if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase)) && - (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase))) + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(dscResourceRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(dscResourceRule.GetName())) + { + excludeRegexMatch = true; + } + } + if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && + (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName())); @@ -452,7 +540,6 @@ private void AnalyzeFile(string filePath) catch (Exception dscResourceRuleException) { WriteError(new ErrorRecord(dscResourceRuleException, Strings.RuleError, ErrorCategory.InvalidOperation, filePath)); - continue; } } } @@ -480,8 +567,25 @@ private void AnalyzeFile(string filePath) // Run all DSC Rules foreach (IDSCResourceRule dscResourceRule in ScriptAnalyzer.Instance.DSCResourceRules) { - if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase)) && - (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase))) + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(dscResourceRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(dscResourceRule.GetName())) + { + excludeRegexMatch = true; + } + } + if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && + (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName())); @@ -494,7 +598,6 @@ private void AnalyzeFile(string filePath) catch (Exception dscResourceRuleException) { WriteError(new ErrorRecord(dscResourceRuleException, Strings.RuleError, ErrorCategory.InvalidOperation, filePath)); - continue; } } } From 0703994edc719cf360f42a937e040ef0589a0cb2 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 20 Apr 2015 17:20:08 -0700 Subject: [PATCH 2/3] Add tests and correct typo --- .../Commands/InvokeScriptAnalyzerCommand.cs | 6 +++++- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 4b5731a76..b91509b53 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -275,7 +275,7 @@ private void AnalyzeFile(string filePath) // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash List> cmdInfoTable = new List>(); - //Check wild card input for the Include/ExcludRules and create regex match patterns + //Check wild card input for the Include/ExcludeRules and create regex match patterns List includeRegexList = new List(); List excludeRegexList = new List(); if (includeRule != null) @@ -358,6 +358,7 @@ private void AnalyzeFile(string filePath) if (exclude.IsMatch(scriptRule.GetName())) { excludeRegexMatch = true; + break; } } if ((includeRule == null || includeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && @@ -433,6 +434,7 @@ private void AnalyzeFile(string filePath) if (exclude.IsMatch(commandRule.GetName())) { excludeRegexMatch = true; + break; } } if ((includeRule == null || includeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && @@ -480,6 +482,7 @@ private void AnalyzeFile(string filePath) if (exclude.IsMatch(tokenRule.GetName())) { excludeRegexMatch = true; + break; } } if ((includeRule == null || includeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && @@ -524,6 +527,7 @@ private void AnalyzeFile(string filePath) if (exclude.IsMatch(dscResourceRule.GetName())) { excludeRegexMatch = true; + break; } } if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 53ddb9a02..9cc6ccdbd 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -3,6 +3,8 @@ $sa = Get-Command Invoke-ScriptAnalyzer $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $singularNouns = "PSUseSingularNouns" $rules = $singularNouns, "PSUseApprovedVerbs" +$avoidRules = "PSAvoid*" +$useRules = "PSUse*" Describe "Test available parameters" { $params = $sa.Parameters @@ -103,6 +105,12 @@ Describe "Test ExcludeRule" { } } + Context "Support wild card" { + It "supports wild card exclusions of input rules"{ + $excludeWildCard = Invoke-ScriptAnalyzer $directory\..\Rules\BadCmdlet.ps1 -ExcludeRule $avoidRules | Where-Object {$_.RuleName -match $avoidRules} + } + } + } Describe "Test IncludeRule" { @@ -124,6 +132,18 @@ Describe "Test IncludeRule" { $wrongInclude.Count | Should Be 0 } } + + Context "IncludeRule supports wild card" { + It "includes 1 wildcard rule"{ + $includeWildcard = Invoke-ScriptAnalyzer $directory\..\Rules\BadCmdlet.ps1 -IncludeRule $avoidRules + $includeWildcard.Count | Should be 5 + } + + it "includes 2 wildcardrules" { + $includeWildcard = Invoke-ScriptAnalyzer $directory\..\Rules\BadCmdlet.ps1 -IncludeRule $avoidRules, $useRules + $includeWildcard.Count | Should be 7 + } + } } Describe "Test Exclude And Include" { From 42b28c8084a5436bbf708f672817b4c3936399db Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 21 Apr 2015 11:45:01 -0700 Subject: [PATCH 3/3] Change the regular expression. Also remove the redundant checking. --- .../Commands/InvokeScriptAnalyzerCommand.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index b91509b53..fb1272d04 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -282,7 +282,7 @@ private void AnalyzeFile(string filePath) { foreach (string rule in includeRule) { - Regex includeRegex = new Regex(rule.Replace("*", ".*?"), RegexOptions.IgnoreCase); + Regex includeRegex = new Regex(String.Format("^{0}$", Regex.Escape(rule).Replace(@"\*", ".*")), RegexOptions.IgnoreCase); includeRegexList.Add(includeRegex); } } @@ -290,7 +290,7 @@ private void AnalyzeFile(string filePath) { foreach (string rule in excludeRule) { - Regex excludeRegex = new Regex(rule.Replace("*", ".*?"), RegexOptions.IgnoreCase); + Regex excludeRegex = new Regex(String.Format("^{0}$", Regex.Escape(rule).Replace(@"\*", ".*")), RegexOptions.IgnoreCase); excludeRegexList.Add(excludeRegex); } } @@ -361,8 +361,7 @@ private void AnalyzeFile(string filePath) break; } } - if ((includeRule == null || includeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && - (excludeRule == null || !excludeRule.Contains(scriptRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); @@ -435,10 +434,9 @@ private void AnalyzeFile(string filePath) { excludeRegexMatch = true; break; - } + } } - if ((includeRule == null || includeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && - (excludeRule == null || !excludeRule.Contains(commandRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) { foreach (KeyValuePair commandInfo in cmdInfoTable) { @@ -485,8 +483,7 @@ private void AnalyzeFile(string filePath) break; } } - if ((includeRule == null || includeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && - (excludeRule == null || !excludeRule.Contains(tokenRule.GetName(), StringComparer.OrdinalIgnoreCase)) || !excludeRegexMatch) + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, tokenRule.GetName())); @@ -530,8 +527,7 @@ private void AnalyzeFile(string filePath) break; } } - if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && - (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || excludeRegexMatch)) + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName())); @@ -588,8 +584,7 @@ private void AnalyzeFile(string filePath) excludeRegexMatch = true; } } - if ((includeRule == null || includeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || includeRegexMatch) && - (excludeRule == null || !excludeRule.Contains(dscResourceRule.GetName(), StringComparer.OrdinalIgnoreCase) || !excludeRegexMatch)) + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) { WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, dscResourceRule.GetName()));