From 76b184999e70e671ac6da0072f3e98f0665992c6 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 30 Aug 2020 17:53:45 +1000 Subject: [PATCH] Code quality updates (#533) --- docs/scenarios/benchmark/results-v0.17.0.md | 24 ++++ docs/scenarios/benchmark/results-v0.19.0.md | 24 ++++ docs/scenarios/benchmark/results-v0.20.0.md | 24 ++++ ...ule-report-github.md => results-v0.3.0.md} | 0 pipeline.build.ps1 | 2 +- src/PSRule.Benchmark/Program.cs | 53 ++++++-- src/PSRule/Commands/AssertAllOfCommand.cs | 6 +- src/PSRule/Commands/AssertAnyOfCommand.cs | 6 +- src/PSRule/Commands/AssertExistsCommand.cs | 6 +- src/PSRule/Commands/AssertMatchCommand.cs | 4 +- src/PSRule/Commands/AssertTypeOfCommand.cs | 7 +- src/PSRule/Commands/AssertWithinCommand.cs | 5 +- src/PSRule/Commands/InvokeRuleBlockCommand.cs | 9 +- src/PSRule/Commands/LanguageBlock.cs | 6 +- src/PSRule/Commands/RuleKeyword.cs | 34 ++--- src/PSRule/Commands/WriteReasonCommand.cs | 4 +- src/PSRule/Commands/WriteRecommendCommand.cs | 6 +- .../{Environment.cs => EnvironmentHelper.cs} | 2 +- src/PSRule/Host/DependencyGraph.cs | 36 +++-- src/PSRule/Host/DependencyGraphBuilder.cs | 7 +- src/PSRule/Host/HostHelper.cs | 11 +- src/PSRule/Parser/MetadataLexer.cs | 4 +- src/PSRule/Parser/RuleLexer.cs | 9 +- src/PSRule/Pipeline/GetTargetPipeline.cs | 6 +- src/PSRule/Pipeline/HostContext.cs | 4 +- src/PSRule/Pipeline/InvokeRulePipeline.cs | 6 +- src/PSRule/Pipeline/PathBuilder.cs | 7 +- src/PSRule/Pipeline/PathFilter.cs | 5 +- src/PSRule/Pipeline/RulePipeline.cs | 2 +- src/PSRule/Pipeline/RunspaceContext.cs | 25 ++-- src/PSRule/Pipeline/TargetBinder.cs | 2 +- .../Resources/PSRuleResources.Designer.cs | 18 +++ src/PSRule/Resources/PSRuleResources.resx | 6 + src/PSRule/Rules/RuleRecord.cs | 14 +- src/PSRule/Runtime/ObjectHelper.cs | 1 - src/PSRule/Runtime/PSRule.cs | 2 +- src/PSRule/Runtime/RuleConditionResult.cs | 125 +++++++++--------- tests/PSRule.Tests/AssertTests.cs | 4 +- 38 files changed, 325 insertions(+), 191 deletions(-) create mode 100644 docs/scenarios/benchmark/results-v0.17.0.md create mode 100644 docs/scenarios/benchmark/results-v0.19.0.md create mode 100644 docs/scenarios/benchmark/results-v0.20.0.md rename docs/scenarios/benchmark/{PSRule.Benchmark.PSRule-report-github.md => results-v0.3.0.md} (100%) rename src/PSRule/Common/{Environment.cs => EnvironmentHelper.cs} (97%) diff --git a/docs/scenarios/benchmark/results-v0.17.0.md b/docs/scenarios/benchmark/results-v0.17.0.md new file mode 100644 index 00000000..0d8b5527 --- /dev/null +++ b/docs/scenarios/benchmark/results-v0.17.0.md @@ -0,0 +1,24 @@ +``` ini + +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.778 (1909/November2018Update/19H2) +Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores +.NET Core SDK=3.1.201 + [Host] : .NET Core 2.1.17 (CoreCLR 4.6.28619.01, CoreFX 4.6.28619.01), X64 RyuJIT + DefaultJob : .NET Core 2.1.17 (CoreCLR 4.6.28619.01, CoreFX 4.6.28619.01), X64 RyuJIT + + +``` +| Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated | +|------------------------- |-----------:|----------:|----------:|-----------:|-----------:|------:|------:|------------:| +| Invoke | 111.140 ms | 2.1935 ms | 4.5786 ms | 109.312 ms | 8200.0000 | - | - | 16839.42 KB | +| InvokeIf | 117.141 ms | 2.2703 ms | 2.2298 ms | 116.398 ms | 9600.0000 | - | - | 19980.62 KB | +| InvokeType | 108.648 ms | 0.7983 ms | 0.7467 ms | 108.584 ms | 8200.0000 | - | - | 16870.67 KB | +| InvokeSummary | 107.300 ms | 0.8612 ms | 0.8056 ms | 107.115 ms | 8000.0000 | - | - | 16784.76 KB | +| Get | 9.003 ms | 0.0643 ms | 0.0602 ms | 9.010 ms | 140.6250 | - | - | 307.96 KB | +| GetHelp | 8.902 ms | 0.0831 ms | 0.0649 ms | 8.899 ms | 140.6250 | - | - | 306.34 KB | +| Within | 179.522 ms | 1.5483 ms | 1.4483 ms | 179.981 ms | 15666.6667 | - | - | 32400.38 KB | +| WithinBulk | 247.883 ms | 2.6279 ms | 2.1944 ms | 248.124 ms | 28500.0000 | - | - | 59306.73 KB | +| WithinLike | 238.815 ms | 2.5538 ms | 1.9939 ms | 239.245 ms | 29333.3333 | - | - | 60580.58 KB | +| DefaultTargetNameBinding | 2.124 ms | 0.0214 ms | 0.0200 ms | 2.129 ms | 85.9375 | - | - | 179.69 KB | +| CustomTargetNameBinding | 2.463 ms | 0.0483 ms | 0.0452 ms | 2.458 ms | 179.6875 | - | - | 375 KB | +| NestedTargetNameBinding | 2.433 ms | 0.0370 ms | 0.0328 ms | 2.420 ms | 179.6875 | - | - | 375 KB | diff --git a/docs/scenarios/benchmark/results-v0.19.0.md b/docs/scenarios/benchmark/results-v0.19.0.md new file mode 100644 index 00000000..a4af7691 --- /dev/null +++ b/docs/scenarios/benchmark/results-v0.19.0.md @@ -0,0 +1,24 @@ +``` ini + +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1) +Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=3.1.401 + [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + DefaultJob : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + + +``` +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|------------------------- |-------------:|------------:|------------:|-----------:|---------:|------:|------------:| +| Invoke | 40,943.5 μs | 581.23 μs | 515.25 μs | 4000.0000 | 500.0000 | - | 16452.28 KB | +| InvokeIf | 42,806.0 μs | 477.29 μs | 423.11 μs | 4500.0000 | 500.0000 | - | 18703.12 KB | +| InvokeType | 40,470.1 μs | 484.16 μs | 429.19 μs | 4000.0000 | 538.4615 | - | 16452.27 KB | +| InvokeSummary | 39,768.8 μs | 462.14 μs | 385.91 μs | 4000.0000 | 153.8462 | - | 16397.82 KB | +| Get | 11,145.4 μs | 402.59 μs | 1,187.03 μs | 46.8750 | - | - | 252.11 KB | +| GetHelp | 10,169.1 μs | 625.02 μs | 1,842.88 μs | 46.8750 | - | - | 250.51 KB | +| Within | 78,993.5 μs | 799.51 μs | 667.63 μs | 8000.0000 | 400.0000 | - | 32791.83 KB | +| WithinBulk | 118,800.8 μs | 1,637.36 μs | 1,531.59 μs | 14333.3333 | 333.3333 | - | 59817.29 KB | +| WithinLike | 106,796.3 μs | 2,067.20 μs | 2,538.71 μs | 11333.3333 | - | - | 47311.07 KB | +| DefaultTargetNameBinding | 698.2 μs | 7.51 μs | 7.02 μs | 38.0859 | - | - | 156.25 KB | +| CustomTargetNameBinding | 884.7 μs | 7.11 μs | 6.65 μs | 85.9375 | - | - | 351.56 KB | +| NestedTargetNameBinding | 883.9 μs | 14.44 μs | 12.80 μs | 85.9375 | - | - | 351.56 KB | diff --git a/docs/scenarios/benchmark/results-v0.20.0.md b/docs/scenarios/benchmark/results-v0.20.0.md new file mode 100644 index 00000000..342da4cb --- /dev/null +++ b/docs/scenarios/benchmark/results-v0.20.0.md @@ -0,0 +1,24 @@ +``` ini + +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1) +Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=3.1.401 + [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + DefaultJob : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + + +``` +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|------------------------- |-------------:|------------:|------------:|-----------:|----------:|------:|------------:| +| Invoke | 42,162.8 μs | 827.36 μs | 1,263.47 μs | 3833.3333 | - | - | 15952 KB | +| InvokeIf | 45,646.4 μs | 912.31 μs | 1,924.38 μs | 4416.6667 | 416.6667 | - | 18202.98 KB | +| InvokeType | 41,825.5 μs | 810.73 μs | 901.12 μs | 3833.3333 | - | - | 15952 KB | +| InvokeSummary | 41,133.3 μs | 777.97 μs | 895.91 μs | 3833.3333 | 500.0000 | - | 15897.56 KB | +| Get | 10,054.3 μs | 396.83 μs | 1,170.07 μs | 46.8750 | - | - | 252.11 KB | +| GetHelp | 10,581.4 μs | 448.15 μs | 1,321.38 μs | 46.8750 | - | - | 250.51 KB | +| Within | 81,215.1 μs | 1,532.85 μs | 1,433.83 μs | 7750.0000 | 250.0000 | - | 32290.62 KB | +| WithinBulk | 123,301.6 μs | 2,451.51 μs | 3,958.73 μs | 14000.0000 | 1000.0000 | - | 59317.29 KB | +| WithinLike | 109,738.9 μs | 1,933.95 μs | 1,809.02 μs | 11333.3333 | 1000.0000 | - | 46811.07 KB | +| DefaultTargetNameBinding | 696.0 μs | 12.06 μs | 10.69 μs | 38.0859 | - | - | 156.25 KB | +| CustomTargetNameBinding | 845.6 μs | 11.75 μs | 10.42 μs | 85.9375 | - | - | 351.56 KB | +| NestedTargetNameBinding | 856.0 μs | 12.29 μs | 10.90 μs | 85.9375 | - | - | 351.56 KB | diff --git a/docs/scenarios/benchmark/PSRule.Benchmark.PSRule-report-github.md b/docs/scenarios/benchmark/results-v0.3.0.md similarity index 100% rename from docs/scenarios/benchmark/PSRule.Benchmark.PSRule-report-github.md rename to docs/scenarios/benchmark/results-v0.3.0.md diff --git a/pipeline.build.ps1 b/pipeline.build.ps1 index 0031db14..5d02f77d 100644 --- a/pipeline.build.ps1 +++ b/pipeline.build.ps1 @@ -294,7 +294,7 @@ task Rules { task Benchmark { if ($Benchmark -or $BuildTask -eq 'Benchmark') { - dotnet run -p src/PSRule.Benchmark -f netcoreapp2.1 -c Release -- benchmark --output $PWD; + dotnet run -p src/PSRule.Benchmark -f netcoreapp3.1 -c Release -- benchmark --output $PWD; } } diff --git a/src/PSRule.Benchmark/Program.cs b/src/PSRule.Benchmark/Program.cs index ec166ac2..1a2bb294 100644 --- a/src/PSRule.Benchmark/Program.cs +++ b/src/PSRule.Benchmark/Program.cs @@ -12,6 +12,8 @@ using BenchmarkDotNet.Running; #endif using Microsoft.Extensions.CommandLineUtils; +using System; +using System.Diagnostics; namespace PSRule.Benchmark { @@ -27,7 +29,8 @@ namespace PSRule.Benchmark #if !BENCHMARK // Do profiling - DebugProfile(); + DebugProfile(app); + app.Execute(args); #endif #if BENCHMARK @@ -71,34 +74,64 @@ namespace PSRule.Benchmark #endif - private static void DebugProfile() + private const int DebugIterations = 100; + + private static void DebugProfile(CommandLineApplication app) + { + app.Command("benchmark", cmd => + { + cmd.OnExecute(() => + { + Console.WriteLine("Press ENTER to start."); + Console.ReadLine(); + RunDebug(); + return 0; + }); + }); + } + + private static void RunDebug() { var profile = new PSRule(); profile.Prepare(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.Invoke(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.InvokeIf(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.InvokeType(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.InvokeSummary(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.Get(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.DefaultTargetNameBinding(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.CustomTargetNameBinding(); - for (var i = 0; i < 100; i++) + ProfileBlock(); + for (var i = 0; i < DebugIterations; i++) profile.NestedTargetNameBinding(); } + + [DebuggerStepThrough] + private static void ProfileBlock() + { + // Do nothing + } } } diff --git a/src/PSRule/Commands/AssertAllOfCommand.cs b/src/PSRule/Commands/AssertAllOfCommand.cs index cfca492f..d3ec0be1 100644 --- a/src/PSRule/Commands/AssertAllOfCommand.cs +++ b/src/PSRule/Commands/AssertAllOfCommand.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using PSRule.Pipeline; -using PSRule.Resources; using PSRule.Runtime; using System.Management.Automation; -using System.Threading; namespace PSRule.Commands { @@ -21,9 +19,9 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsConditionScope()) - throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordConditionScope, LanguageKeywords.AllOf)); + throw ConditionScopeException(LanguageKeywords.AllOf); - var invokeResult = RuleConditionResult.Create(Body.Invoke()); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); var result = invokeResult.AllOf(); RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.AllOf, pass: invokeResult.Pass, count: invokeResult.Count, outcome: result); diff --git a/src/PSRule/Commands/AssertAnyOfCommand.cs b/src/PSRule/Commands/AssertAnyOfCommand.cs index 372f3793..ba0ff18f 100644 --- a/src/PSRule/Commands/AssertAnyOfCommand.cs +++ b/src/PSRule/Commands/AssertAnyOfCommand.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using PSRule.Pipeline; -using PSRule.Resources; using PSRule.Runtime; using System.Management.Automation; -using System.Threading; namespace PSRule.Commands { @@ -21,9 +19,9 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsConditionScope()) - throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordConditionScope, LanguageKeywords.AnyOf)); + throw ConditionScopeException(LanguageKeywords.AnyOf); - var invokeResult = RuleConditionResult.Create(Body.Invoke()); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); var result = invokeResult.AnyOf(); RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.AnyOf, pass: invokeResult.Pass, count: invokeResult.Count, outcome: result); diff --git a/src/PSRule/Commands/AssertExistsCommand.cs b/src/PSRule/Commands/AssertExistsCommand.cs index c360a226..7bd3ca00 100644 --- a/src/PSRule/Commands/AssertExistsCommand.cs +++ b/src/PSRule/Commands/AssertExistsCommand.cs @@ -47,8 +47,8 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsRuleScope()) - throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordRuleScope, RuleLanguageNouns.Exists)); - + throw RuleScopeException(LanguageKeywords.Exists); + var targetObject = InputObject ?? GetTargetObject(); var foundFields = new List(); var notFoundFields = new List(); @@ -57,7 +57,7 @@ namespace PSRule.Commands for (var i = 0; i < Field.Length && found < required; i++) { - if (ObjectHelper.GetField(bindingContext: PipelineContext.CurrentThread, targetObject: targetObject, name: Field[i], caseSensitive: CaseSensitive, value: out object fieldValue)) + if (ObjectHelper.GetField(bindingContext: PipelineContext.CurrentThread, targetObject: targetObject, name: Field[i], caseSensitive: CaseSensitive, value: out _)) { RunspaceContext.CurrentThread.VerboseConditionMessage(condition: RuleLanguageNouns.Exists, message: PSRuleResources.ExistsTrue, args: Field[i]); foundFields.Add(Field[i]); diff --git a/src/PSRule/Commands/AssertMatchCommand.cs b/src/PSRule/Commands/AssertMatchCommand.cs index b0229a8a..757ea5e1 100644 --- a/src/PSRule/Commands/AssertMatchCommand.cs +++ b/src/PSRule/Commands/AssertMatchCommand.cs @@ -59,9 +59,7 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsRuleScope()) - { - throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordRuleScope, RuleLanguageNouns.Match)); - } + throw RuleScopeException(LanguageKeywords.Match); var targetObject = InputObject ?? GetTargetObject(); bool expected = !Not; diff --git a/src/PSRule/Commands/AssertTypeOfCommand.cs b/src/PSRule/Commands/AssertTypeOfCommand.cs index 22e9b963..45cc5b8c 100644 --- a/src/PSRule/Commands/AssertTypeOfCommand.cs +++ b/src/PSRule/Commands/AssertTypeOfCommand.cs @@ -5,6 +5,7 @@ using PSRule.Pipeline; using PSRule.Resources; using System.Linq; using System.Management.Automation; +using System.Threading; namespace PSRule.Commands { @@ -26,9 +27,7 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsRuleScope()) - { - throw new RuleRuntimeException(string.Format(PSRuleResources.KeywordRuleScope, RuleLanguageNouns.TypeOf)); - } + throw RuleScopeException(LanguageKeywords.TypeOf); var inputObject = InputObject ?? GetTargetObject(); var result = false; @@ -42,7 +41,7 @@ namespace PSRule.Commands RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.TypeOf, outcome: result); if (!(result || TryReason(Reason))) { - WriteReason(string.Format(ReasonStrings.TypeOf, string.Join(", ", TypeName))); + WriteReason(string.Format(Thread.CurrentThread.CurrentCulture, ReasonStrings.TypeOf, string.Join(", ", TypeName))); } WriteObject(result); } diff --git a/src/PSRule/Commands/AssertWithinCommand.cs b/src/PSRule/Commands/AssertWithinCommand.cs index 87dfdab9..4a0d68f1 100644 --- a/src/PSRule/Commands/AssertWithinCommand.cs +++ b/src/PSRule/Commands/AssertWithinCommand.cs @@ -6,6 +6,7 @@ using PSRule.Resources; using PSRule.Runtime; using System; using System.Management.Automation; +using System.Threading; namespace PSRule.Commands { @@ -59,7 +60,7 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsRuleScope()) - throw new RuleRuntimeException(string.Format(PSRuleResources.KeywordRuleScope, LanguageKeywords.Within)); + throw RuleScopeException(LanguageKeywords.Within); var targetObject = InputObject ?? GetTargetObject(); bool expected = !Not; @@ -109,7 +110,7 @@ namespace PSRule.Commands RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Within, outcome: result); if (!(result || TryReason(Reason))) { - WriteReason(Not ? string.Format(ReasonStrings.WithinNot, found) : ReasonStrings.Within); + WriteReason(Not ? string.Format(Thread.CurrentThread.CurrentCulture, ReasonStrings.WithinNot, found) : ReasonStrings.Within); } WriteObject(result); } diff --git a/src/PSRule/Commands/InvokeRuleBlockCommand.cs b/src/PSRule/Commands/InvokeRuleBlockCommand.cs index ec39ad67..d0074728 100644 --- a/src/PSRule/Commands/InvokeRuleBlockCommand.cs +++ b/src/PSRule/Commands/InvokeRuleBlockCommand.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using PSRule.Pipeline; +using PSRule.Resources; using PSRule.Runtime; using System; using System.Management.Automation; @@ -32,7 +33,7 @@ namespace PSRule.Commands // Evalute type pre-condition if (!AcceptsType()) { - RunspaceContext.CurrentThread.Writer.DebugMessage("Target failed Type precondition"); + RunspaceContext.CurrentThread.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); return; } @@ -40,17 +41,17 @@ namespace PSRule.Commands if (If != null) { PipelineContext.CurrentThread.ExecutionScope = ExecutionScope.Precondition; - var ifResult = RuleConditionResult.Create(If.Invoke()); + var ifResult = RuleConditionHelper.Create(If.Invoke()); if (!ifResult.AllOf()) { - RunspaceContext.CurrentThread.Writer.DebugMessage("Target failed If precondition"); + RunspaceContext.CurrentThread.Writer.DebugMessage(PSRuleResources.DebugTargetIfMismatch); return; } } // Evaluate script block PipelineContext.CurrentThread.ExecutionScope = ExecutionScope.Condition; - var invokeResult = RuleConditionResult.Create(Body.Invoke()); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); WriteObject(invokeResult); } finally diff --git a/src/PSRule/Commands/LanguageBlock.cs b/src/PSRule/Commands/LanguageBlock.cs index 065f9972..27905395 100644 --- a/src/PSRule/Commands/LanguageBlock.cs +++ b/src/PSRule/Commands/LanguageBlock.cs @@ -15,17 +15,17 @@ namespace PSRule.Commands /// internal abstract class LanguageBlock : PSCmdlet { - protected CommentMetadata GetMetadata(string path, int lineNumber, int offset) + protected static CommentMetadata GetMetadata(string path, int lineNumber, int offset) { return HostHelper.GetCommentMeta(path, lineNumber - 2, offset); } - protected TagSet GetTag(Hashtable hashtable) + protected static TagSet GetTag(Hashtable hashtable) { return TagSet.FromHashtable(hashtable); } - protected bool IsScriptScope() + protected static bool IsScriptScope() { return PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Script; } diff --git a/src/PSRule/Commands/RuleKeyword.cs b/src/PSRule/Commands/RuleKeyword.cs index 796e324c..4e9e0e8b 100644 --- a/src/PSRule/Commands/RuleKeyword.cs +++ b/src/PSRule/Commands/RuleKeyword.cs @@ -2,10 +2,12 @@ // Licensed under the MIT License. using PSRule.Pipeline; +using PSRule.Resources; using PSRule.Rules; using System; using System.Collections; using System.Management.Automation; +using System.Threading; namespace PSRule.Commands { @@ -14,12 +16,12 @@ namespace PSRule.Commands /// internal abstract class RuleKeyword : PSCmdlet { - protected RuleRecord GetResult() + protected static RuleRecord GetResult() { return RunspaceContext.CurrentThread.RuleRecord; } - protected PSObject GetTargetObject() + protected static PSObject GetTargetObject() { return RunspaceContext.CurrentThread.TargetObject; } @@ -80,50 +82,52 @@ namespace PSRule.Commands return false; } - protected object GetBaseObject(object value) + protected static object GetBaseObject(object value) { if (value == null) - { return null; - } if (value is PSObject pso) { var baseObject = pso.BaseObject; - if (baseObject != null) - { return baseObject; - } } - return value; } - protected void WriteReason(string text) + protected static void WriteReason(string text) { RunspaceContext.CurrentThread.WriteReason(text); } - protected bool TryReason(string text) + protected static bool TryReason(string text) { if (string.IsNullOrEmpty(text)) - { return false; - } WriteReason(text); return true; } - protected bool IsRuleScope() + protected static bool IsRuleScope() { return PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Condition || PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Precondition; } - protected bool IsConditionScope() + protected static bool IsConditionScope() { return PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Condition; } + + protected static RuleRuntimeException RuleScopeException(string keyword) + { + return new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordRuleScope, keyword)); + } + + protected static RuleRuntimeException ConditionScopeException(string keyword) + { + return new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordConditionScope, keyword)); + } } } diff --git a/src/PSRule/Commands/WriteReasonCommand.cs b/src/PSRule/Commands/WriteReasonCommand.cs index e1c6647d..50e8c4c6 100644 --- a/src/PSRule/Commands/WriteReasonCommand.cs +++ b/src/PSRule/Commands/WriteReasonCommand.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using PSRule.Pipeline; -using PSRule.Resources; using System.Management.Automation; namespace PSRule.Commands @@ -19,7 +18,8 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsConditionScope()) - throw new RuleRuntimeException(string.Format(PSRuleResources.KeywordConditionScope, LanguageKeywords.Reason)); + throw ConditionScopeException(LanguageKeywords.Reason); + if (MyInvocation.BoundParameters.ContainsKey(nameof(Text))) RunspaceContext.CurrentThread.WriteReason(text: Text); diff --git a/src/PSRule/Commands/WriteRecommendCommand.cs b/src/PSRule/Commands/WriteRecommendCommand.cs index 2ec8443a..226d339d 100644 --- a/src/PSRule/Commands/WriteRecommendCommand.cs +++ b/src/PSRule/Commands/WriteRecommendCommand.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Pipeline; -using PSRule.Resources; using System.Management.Automation; namespace PSRule.Commands @@ -20,9 +18,7 @@ namespace PSRule.Commands protected override void ProcessRecord() { if (!IsConditionScope()) - { - throw new RuleRuntimeException(string.Format(PSRuleResources.KeywordConditionScope, LanguageKeywords.Recommend)); - } + throw ConditionScopeException(LanguageKeywords.Recommend); var result = GetResult(); diff --git a/src/PSRule/Common/Environment.cs b/src/PSRule/Common/EnvironmentHelper.cs similarity index 97% rename from src/PSRule/Common/Environment.cs rename to src/PSRule/Common/EnvironmentHelper.cs index 037389d5..0eef368c 100644 --- a/src/PSRule/Common/Environment.cs +++ b/src/PSRule/Common/EnvironmentHelper.cs @@ -6,7 +6,7 @@ using System.Security; namespace PSRule { - internal sealed class EnvironmentHelper + internal static class EnvironmentHelper { private const char UNDERSCORE = '_'; diff --git a/src/PSRule/Host/DependencyGraph.cs b/src/PSRule/Host/DependencyGraph.cs index bd291b78..8a843066 100644 --- a/src/PSRule/Host/DependencyGraph.cs +++ b/src/PSRule/Host/DependencyGraph.cs @@ -12,7 +12,7 @@ namespace PSRule.Host private readonly DependencyTarget[] _Targets; // Track whether Dispose has been called. - private bool _Disposed = false; + private bool _Disposed; public DependencyGraph(T[] targets) { @@ -40,12 +40,12 @@ namespace PSRule.Host DependencyFail = 3 } - public sealed class DependencyTarget + internal sealed class DependencyTarget { public readonly DependencyGraph Graph; public readonly T Value; - internal DependencyTargetState State; + private DependencyTargetState State; public DependencyTarget(DependencyGraph graph, T value) { @@ -58,6 +58,16 @@ namespace PSRule.Host get { return State == DependencyTargetState.DependencyFail; } } + public bool Failed + { + get { return State == DependencyTargetState.Fail || State == DependencyTargetState.DependencyFail; } + } + + public bool Passed + { + get { return State == DependencyTargetState.Pass; } + } + public void Pass() { State = DependencyTargetState.Pass; @@ -67,34 +77,38 @@ namespace PSRule.Host { State = DependencyTargetState.Fail; } + + public void DependencyFail() + { + State = DependencyTargetState.DependencyFail; + } } public IEnumerable GetSingleTarget() { - foreach (var target in _Targets) + for (var t = 0; t < _Targets.Length; t++) { + var target = _Targets[t]; if (target.Value.DependsOn != null && target.Value.DependsOn.Length > 0) { // Process each dependency - foreach (var d in target.Value.DependsOn) + for (var d = 0; d < target.Value.DependsOn.Length; d++) { - var dTarget = _Index[d]; + var dTarget = _Index[target.Value.DependsOn[d]]; // Check if dependency was already completed - if (dTarget.State == DependencyTargetState.Pass) + if (dTarget.Passed) { continue; } - else if (dTarget.State == DependencyTargetState.Fail || dTarget.State == DependencyTargetState.DependencyFail) + else if (dTarget.Failed) { - target.State = DependencyTargetState.DependencyFail; + target.DependencyFail(); break; } - yield return dTarget; } } - yield return target; } } diff --git a/src/PSRule/Host/DependencyGraphBuilder.cs b/src/PSRule/Host/DependencyGraphBuilder.cs index 02dd2d1a..6726cb08 100644 --- a/src/PSRule/Host/DependencyGraphBuilder.cs +++ b/src/PSRule/Host/DependencyGraphBuilder.cs @@ -6,6 +6,7 @@ using PSRule.Resources; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace PSRule.Host { @@ -32,7 +33,7 @@ namespace PSRule.Host foreach (var item in items) { if (index.ContainsKey(item.RuleId)) - throw new RuleRuntimeException(message: string.Format(PSRuleResources.DuplicateRuleId, item.RuleId)); + throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DuplicateRuleId, item.RuleId)); index.Add(item.RuleId, item); } @@ -63,7 +64,7 @@ namespace PSRule.Host // Check for circular dependencies if (_Stack.Contains(value: ruleId, comparer: _Comparer)) - throw new RuleRuntimeException(message: string.Format(PSRuleResources.DependencyCircularReference, parentId, ruleId)); + throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyCircularReference, parentId, ruleId)); try { @@ -76,7 +77,7 @@ namespace PSRule.Host foreach (var d in item.DependsOn) { if (!index.ContainsKey(d)) - throw new RuleRuntimeException(message: string.Format(PSRuleResources.DependencyNotFound, d, ruleId)); + throw new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyNotFound, d, ruleId)); // Handle dependencies if (!_Targets.ContainsKey(d)) diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index af925205..494c1c08 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -88,10 +88,10 @@ namespace PSRule.Host { foreach (var comment in comments) { - if (comment.StartsWith("# Description: ")) + if (comment.StartsWith("# Description: ", StringComparison.OrdinalIgnoreCase)) metadata.Synopsis = comment.Substring(15); - if (comment.StartsWith("# Synopsis: ")) + if (comment.StartsWith("# Synopsis: ", StringComparison.OrdinalIgnoreCase)) metadata.Synopsis = comment.Substring(12); } } @@ -156,11 +156,8 @@ namespace PSRule.Host foreach (var ir in invokeResults) { - if (ir.BaseObject is RuleBlock) - { - var block = ir.BaseObject as RuleBlock; + if (ir.BaseObject is RuleBlock block) results.Add(block); - } } } } @@ -250,7 +247,7 @@ namespace PSRule.Host { ruleRecord.OutcomeReason = RuleOutcomeReason.Inconclusive; ruleRecord.Outcome = RuleOutcome.Fail; - context.WarnRuleInconclusive(ruleId: ruleRecord.RuleId); + context.WarnRuleInconclusive(ruleRecord.RuleId); } else { diff --git a/src/PSRule/Parser/MetadataLexer.cs b/src/PSRule/Parser/MetadataLexer.cs index 4e814b48..3d58550c 100644 --- a/src/PSRule/Parser/MetadataLexer.cs +++ b/src/PSRule/Parser/MetadataLexer.cs @@ -9,10 +9,8 @@ namespace PSRule.Parser { public Dictionary Process(TokenStream stream) { - stream.MoveTo(0); - // Look for yaml header - + stream.MoveTo(0); return YamlHeader(stream); } } diff --git a/src/PSRule/Parser/RuleLexer.cs b/src/PSRule/Parser/RuleLexer.cs index de005c01..2218f3e6 100644 --- a/src/PSRule/Parser/RuleLexer.cs +++ b/src/PSRule/Parser/RuleLexer.cs @@ -6,6 +6,7 @@ using PSRule.Resources; using System; using System.Collections.Generic; using System.Text; +using System.Threading; namespace PSRule.Parser { @@ -26,11 +27,9 @@ namespace PSRule.Parser public RuleDocument Process(TokenStream stream) { - stream.MoveTo(0); - // Look for yaml header + stream.MoveTo(0); var metadata = YamlHeader(stream); - RuleDocument doc = null; // Process sections @@ -151,7 +150,7 @@ namespace PSRule.Parser return true; } - private TextBlock TextBlock(TokenStream stream) + private static TextBlock TextBlock(TokenStream stream) { var useBreak = stream.Current.IsDoubleLineEnding(); stream.Next(); @@ -177,7 +176,7 @@ namespace PSRule.Parser AppendEnding(sb, stream.Peak(-1)); sb.Append(stream.Current.Meta); if (!string.IsNullOrEmpty(stream.Current.Text)) - sb.AppendFormat(" ({0})", stream.Current.Text); + sb.AppendFormat(Thread.CurrentThread.CurrentCulture, " ({0})", stream.Current.Text); } else if (stream.IsTokenType(MarkdownTokenType.LinkReference)) { diff --git a/src/PSRule/Pipeline/GetTargetPipeline.cs b/src/PSRule/Pipeline/GetTargetPipeline.cs index 3ab486ee..d84024e8 100644 --- a/src/PSRule/Pipeline/GetTargetPipeline.cs +++ b/src/PSRule/Pipeline/GetTargetPipeline.cs @@ -127,15 +127,13 @@ namespace PSRule.Pipeline internal GetTargetPipeline(PipelineContext context, PipelineReader reader, PipelineWriter writer) : base(context, null, reader, writer) { } - public override void Process(PSObject targetObject) + public override void Process(PSObject sourceObject) { try { - Reader.Enqueue(targetObject); + Reader.Enqueue(sourceObject); while (Reader.TryDequeue(out PSObject next)) - { Writer.WriteObject(next, false); - } } catch (Exception) { diff --git a/src/PSRule/Pipeline/HostContext.cs b/src/PSRule/Pipeline/HostContext.cs index e896932c..c9dce649 100644 --- a/src/PSRule/Pipeline/HostContext.cs +++ b/src/PSRule/Pipeline/HostContext.cs @@ -25,9 +25,7 @@ namespace PSRule.Pipeline if (commandRuntime != null) ShouldProcess = commandRuntime.ShouldProcess; - InSession = executionContext == null ? false : executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null; + InSession = executionContext != null && executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null; } - - } } diff --git a/src/PSRule/Pipeline/InvokeRulePipeline.cs b/src/PSRule/Pipeline/InvokeRulePipeline.cs index 143a2c08..b1f431b2 100644 --- a/src/PSRule/Pipeline/InvokeRulePipeline.cs +++ b/src/PSRule/Pipeline/InvokeRulePipeline.cs @@ -179,7 +179,7 @@ namespace PSRule.Pipeline private readonly RuleSuppressionFilter _SuppressionFilter; // Track whether Dispose has been called. - private bool _Disposed = false; + private bool _Disposed; internal InvokeRulePipeline(PipelineContext context, Source[] source, PipelineReader reader, PipelineWriter writer, RuleOutcome outcome) : base(context, source, reader, writer) @@ -199,11 +199,11 @@ namespace PSRule.Pipeline public int RuleCount { get; private set; } - public override void Process(PSObject targetObject) + public override void Process(PSObject sourceObject) { try { - Reader.Enqueue(targetObject); + Reader.Enqueue(sourceObject); while (Reader.TryDequeue(out PSObject next)) { var result = ProcessTargetObject(next); diff --git a/src/PSRule/Pipeline/PathBuilder.cs b/src/PSRule/Pipeline/PathBuilder.cs index 6cfc75ca..39d6dca8 100644 --- a/src/PSRule/Pipeline/PathBuilder.cs +++ b/src/PSRule/Pipeline/PathBuilder.cs @@ -41,7 +41,7 @@ namespace PSRule.Pipeline private readonly string _DefaultSearchPattern; private readonly PathFilter _GlobalFilter; - internal PathBuilder(ILogger logger, string basePath, string searchPattern, PathFilter filter) + protected PathBuilder(ILogger logger, string basePath, string searchPattern, PathFilter filter) { _Logger = logger; _Files = new List(); @@ -171,11 +171,6 @@ namespace PSRule.Pipeline if (string.IsNullOrEmpty(searchPattern)) searchPattern = _DefaultSearchPattern; - // If a path separator is within the pattern use a resursive search - //if (relativeAnchor || !string.IsNullOrEmpty(pathLiteral)) - //if (relativeAnchor && string.IsNullOrEmpty(searchPattern)) - // searchOption = SearchOption.TopDirectoryOnly; - return GetRootedPath(pathLiteral); } diff --git a/src/PSRule/Pipeline/PathFilter.cs b/src/PSRule/Pipeline/PathFilter.cs index 6ac71f16..e042e9a4 100644 --- a/src/PSRule/Pipeline/PathFilter.cs +++ b/src/PSRule/Pipeline/PathFilter.cs @@ -61,13 +61,10 @@ namespace PSRule.Pipeline private const char Question = '?'; // Match any character except '/' private const char Hash = '#'; // Comment private const char Exclamation = '!'; // Include a previously excluded path - - private const string GitIgnoreFileName = ".gitignore"; private readonly string _BasePath; private readonly PathFilterExpression[] _Expression; - - private bool _MatchResult; + private readonly bool _MatchResult; private PathFilter(string basePath, PathFilterExpression[] expression, bool matchResult) { diff --git a/src/PSRule/Pipeline/RulePipeline.cs b/src/PSRule/Pipeline/RulePipeline.cs index eeeba0cf..294e1cfa 100644 --- a/src/PSRule/Pipeline/RulePipeline.cs +++ b/src/PSRule/Pipeline/RulePipeline.cs @@ -16,7 +16,7 @@ namespace PSRule.Pipeline protected readonly PipelineWriter Writer; // Track whether Dispose has been called. - private bool _Disposed = false; + private bool _Disposed; protected RulePipeline(PipelineContext context, Source[] source, PipelineReader reader, PipelineWriter writer) { diff --git a/src/PSRule/Pipeline/RunspaceContext.cs b/src/PSRule/Pipeline/RunspaceContext.cs index 31d7a44c..08990b61 100644 --- a/src/PSRule/Pipeline/RunspaceContext.cs +++ b/src/PSRule/Pipeline/RunspaceContext.cs @@ -43,7 +43,7 @@ namespace PSRule.Pipeline private readonly OutcomeLogStream _FailStream; private readonly OutcomeLogStream _PassStream; - private bool _RaisedUsingInvariantCulture = false; + private bool _RaisedUsingInvariantCulture; // Pipeline logging private string _LogPrefix; @@ -53,7 +53,7 @@ namespace PSRule.Pipeline private readonly List _Reason; // Track whether Dispose has been called. - private bool _Disposed = false; + private bool _Disposed; internal RunspaceContext(PipelineContext pipeline, PipelineWriter writer) { @@ -77,13 +77,13 @@ namespace PSRule.Pipeline return; if (_PassStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) - Writer.WriteWarning(string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName)); + Writer.WriteWarning(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName); if (_PassStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) - Writer.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName)), SOURCE_OUTCOME_PASS, ErrorCategory.InvalidData, null)); + Writer.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName)), SOURCE_OUTCOME_PASS, ErrorCategory.InvalidData, null)); if (_PassStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) - Writer.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName), source: SOURCE_OUTCOME_PASS)); + Writer.WriteInformation(new InformationRecord(messageData: string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Pipeline.Binder.TargetName), source: SOURCE_OUTCOME_PASS)); } public void Fail() @@ -92,13 +92,13 @@ namespace PSRule.Pipeline return; if (_FailStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) - Writer.WriteWarning(string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName)); + Writer.WriteWarning(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName); if (_FailStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) - Writer.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName)), SOURCE_OUTCOME_FAIL, ErrorCategory.InvalidData, null)); + Writer.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName)), SOURCE_OUTCOME_FAIL, ErrorCategory.InvalidData, null)); if (_FailStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) - Writer.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName), source: SOURCE_OUTCOME_FAIL)); + Writer.WriteInformation(new InformationRecord(messageData: string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Pipeline.Binder.TargetName), source: SOURCE_OUTCOME_FAIL)); } public void WarnRuleInconclusive(string ruleId) @@ -106,7 +106,7 @@ namespace PSRule.Pipeline if (Writer == null || !Writer.ShouldWriteWarning() || !_InconclusiveWarning) return; - Writer.WriteWarning(string.Format(PSRuleResources.RuleInconclusive, ruleId, Pipeline.Binder.TargetName)); + Writer.WriteWarning(PSRuleResources.RuleInconclusive, ruleId, Pipeline.Binder.TargetName); } public void WarnObjectNotProcessed() @@ -114,7 +114,7 @@ namespace PSRule.Pipeline if (Writer == null || !Writer.ShouldWriteWarning() || !_NotProcessedWarning) return; - Writer.WriteWarning(string.Format(PSRuleResources.ObjectNotProcessed, Pipeline.Binder.TargetName)); + Writer.WriteWarning(PSRuleResources.ObjectNotProcessed, Pipeline.Binder.TargetName); } public void WarnRuleNotFound() @@ -335,7 +335,7 @@ namespace PSRule.Pipeline return string.Concat( record.ScriptStackTrace, Environment.NewLine, - string.Format(PSRuleResources.RuleStackTrace, RuleBlock.RuleName, RuleBlock.Extent.File, RuleBlock.Extent.StartLineNumber) + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.RuleStackTrace, RuleBlock.RuleName, RuleBlock.Extent.File, RuleBlock.Extent.StartLineNumber) ); } @@ -385,7 +385,7 @@ namespace PSRule.Pipeline { _ObjectNumber++; TargetObject = targetObject; - Pipeline.Binder.Bind(Pipeline.Baseline, targetObject); + Pipeline.Binder.Bind(Pipeline.Baseline, TargetObject); if (Pipeline.ContentCache.Count > 0) Pipeline.ContentCache.Clear(); } @@ -395,7 +395,6 @@ namespace PSRule.Pipeline /// public RuleRecord EnterRuleBlock(RuleBlock ruleBlock) { - Pipeline.Binder.Bind(Pipeline.Baseline, TargetObject); RuleBlock = ruleBlock; RuleRecord = new RuleRecord( ruleId: ruleBlock.RuleId, diff --git a/src/PSRule/Pipeline/TargetBinder.cs b/src/PSRule/Pipeline/TargetBinder.cs index 99ae04d6..5dad3a38 100644 --- a/src/PSRule/Pipeline/TargetBinder.cs +++ b/src/PSRule/Pipeline/TargetBinder.cs @@ -119,10 +119,10 @@ namespace PSRule.Pipeline /// private void BindField(FieldMap[] map, bool caseSensitive, PSObject targetObject) { - var hashtable = new ImmutableHashtable(); if (map == null || map.Length == 0) return; + var hashtable = new ImmutableHashtable(); for (var i = 0; i < map.Length; i++) { if (map[i] == null || map[i].Count == 0) diff --git a/src/PSRule/Resources/PSRuleResources.Designer.cs b/src/PSRule/Resources/PSRuleResources.Designer.cs index 1755d434..591f50dd 100644 --- a/src/PSRule/Resources/PSRuleResources.Designer.cs +++ b/src/PSRule/Resources/PSRuleResources.Designer.cs @@ -78,6 +78,24 @@ namespace PSRule.Resources { } } + /// + /// Looks up a localized string similar to Target failed If precondition. + /// + internal static string DebugTargetIfMismatch { + get { + return ResourceManager.GetString("DebugTargetIfMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Target failed Type precondition. + /// + internal static string DebugTargetTypeMismatch { + get { + return ResourceManager.GetString("DebugTargetTypeMismatch", resourceCulture); + } + } + /// /// Looks up a localized string similar to A circular rule dependency was detected. The rule '{0}' depends on '{1}' which also depend on '{0}'.. /// diff --git a/src/PSRule/Resources/PSRuleResources.resx b/src/PSRule/Resources/PSRuleResources.resx index 83af853a..55c16862 100644 --- a/src/PSRule/Resources/PSRuleResources.resx +++ b/src/PSRule/Resources/PSRuleResources.resx @@ -124,6 +124,12 @@ Binding functions are not supported in this language mode. + + Target failed If precondition + + + Target failed Type precondition + A circular rule dependency was detected. The rule '{0}' depends on '{1}' which also depend on '{0}'. Occurs when rules interdepend on each other. diff --git a/src/PSRule/Rules/RuleRecord.cs b/src/PSRule/Rules/RuleRecord.cs index 10da41b1..f3108f76 100644 --- a/src/PSRule/Rules/RuleRecord.cs +++ b/src/PSRule/Rules/RuleRecord.cs @@ -34,7 +34,8 @@ namespace PSRule.Rules if (field != null && field.Count > 0) Field = field; - Data = new Hashtable(); + // Limit allocations for most scenarios. Runtime calls GetData(). + Data = null; } /// @@ -143,5 +144,16 @@ namespace PSRule.Rules return sb.ToString(); } + + /// + /// Safe call to Data. + /// + internal Hashtable GetData() + { + if (Data == null) + Data = new Hashtable(); + + return Data; + } } } diff --git a/src/PSRule/Runtime/ObjectHelper.cs b/src/PSRule/Runtime/ObjectHelper.cs index e50a8d29..04df5fdc 100644 --- a/src/PSRule/Runtime/ObjectHelper.cs +++ b/src/PSRule/Runtime/ObjectHelper.cs @@ -286,7 +286,6 @@ namespace PSRule.Runtime value = null; var bindingFlags = caseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase; var fieldInfo = baseType.GetField(fieldName, bindingAttr: bindingFlags | BindingFlags.Instance | BindingFlags.Public); - if (fieldInfo == null) return false; diff --git a/src/PSRule/Runtime/PSRule.cs b/src/PSRule/Runtime/PSRule.cs index 4cf6f351..f6ce9a1f 100644 --- a/src/PSRule/Runtime/PSRule.cs +++ b/src/PSRule/Runtime/PSRule.cs @@ -32,7 +32,7 @@ namespace PSRule.Runtime { get { - return _Context.RuleRecord.Data; + return _Context.RuleRecord.GetData(); } } diff --git a/src/PSRule/Runtime/RuleConditionResult.cs b/src/PSRule/Runtime/RuleConditionResult.cs index 828af309..f2b967ec 100644 --- a/src/PSRule/Runtime/RuleConditionResult.cs +++ b/src/PSRule/Runtime/RuleConditionResult.cs @@ -7,13 +7,76 @@ using System.Management.Automation; namespace PSRule.Runtime { + internal static class RuleConditionHelper + { + private readonly static RuleConditionResult Empty = new RuleConditionResult(pass: 0, count: 0, hadErrors: false); + + internal static RuleConditionResult Create(IEnumerable value) + { + if (value == null) + return Empty; + + var count = 0; + var pass = 0; + var hasErrors = false; + foreach (var v in value) + { + count++; + if (v == null) + continue; + + var baseObject = GetBaseObject(v); + if (!(TryAssertResult(baseObject, out bool result) || TryBoolean(baseObject, out result))) + { + RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); + hasErrors = true; + } + else if (result) + { + pass++; + } + } + return new RuleConditionResult(pass, count, hasErrors); + } + + private static object GetBaseObject(object o) + { + return o is PSObject pso ? pso.BaseObject : o; + } + + private static bool TryBoolean(object o, out bool result) + { + result = false; + if (o == null || !(o is bool bresult)) + return false; + + result = bresult; + return true; + } + + private static bool TryAssertResult(object o, out bool result) + { + result = false; + if (o == null || !(o is AssertResult assert)) + return false; + + result = assert.Result; + + // Complete results + if (PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Condition) + assert.Complete(); + + return true; + } + } + internal sealed class RuleConditionResult { public readonly int Pass; public readonly int Count; public readonly bool HadErrors; - private RuleConditionResult(int pass, int count, bool hadErrors) + internal RuleConditionResult(int pass, int count, bool hadErrors) { Pass = pass; Count = count; @@ -29,65 +92,5 @@ namespace PSRule.Runtime { return Pass > 0; } - - internal static RuleConditionResult Create(IEnumerable value) - { - if (value == null) - return new RuleConditionResult(pass: 0, count: 0, hadErrors: false); - - var count = 0; - var pass = 0; - var hasError = false; - foreach (var v in value) - { - count++; - if (v == null) - continue; - - if (!(TryAssertResult(v, out bool result) || TryBoolean(v, out result))) - { - RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); - hasError = true; - } - else if (result) - { - pass++; - } - } - return new RuleConditionResult(pass: pass, count: count, hadErrors: hasError); - } - - private static bool TryBoolean(object o, out bool result) - { - result = false; - if (o == null) - return false; - - var baseObject = o is PSObject pso ? pso.BaseObject : o; - if (!(baseObject is bool bresult)) - return false; - - result = bresult; - return true; - } - - private static bool TryAssertResult(object o, out bool result) - { - result = false; - if (o == null) - return false; - - var baseObject = o is PSObject pso ? pso.BaseObject : o; - if (!(baseObject is AssertResult assert)) - return false; - - result = assert.Result; - - // Complete results - if (PipelineContext.CurrentThread.ExecutionScope == ExecutionScope.Condition) - assert.Complete(); - - return true; - } } } diff --git a/tests/PSRule.Tests/AssertTests.cs b/tests/PSRule.Tests/AssertTests.cs index 86b3a22c..b7849a74 100644 --- a/tests/PSRule.Tests/AssertTests.cs +++ b/tests/PSRule.Tests/AssertTests.cs @@ -47,11 +47,11 @@ namespace PSRule { SetContext(); var assert = GetAssertionHelper(); - var actual1 = PSRule.Runtime.RuleConditionResult.Create(new object[] { PSObject.AsPSObject(assert.Create(true, "Test reason")), PSObject.AsPSObject(assert.Create(false, "Test reason")) }); + var actual1 = PSRule.Runtime.RuleConditionHelper.Create(new object[] { PSObject.AsPSObject(assert.Create(true, "Test reason")), PSObject.AsPSObject(assert.Create(false, "Test reason")) }); Assert.True(actual1.AnyOf()); Assert.False(actual1.AllOf()); - var actual2 = PSRule.Runtime.RuleConditionResult.Create(new object[] { assert.Create(true, "Test reason"), assert.Create(false, "Test reason") }); + var actual2 = PSRule.Runtime.RuleConditionHelper.Create(new object[] { assert.Create(true, "Test reason"), assert.Create(false, "Test reason") }); Assert.True(actual2.AnyOf()); Assert.False(actual2.AllOf()); }