From 4c8f93f081112d0f571cddfc9cc4cfabbf526c2f Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 27 Aug 2023 22:32:50 +1000 Subject: [PATCH] Refactor option context (#1600) --- .github/workflows/analyze.yaml | 20 + .vscode/settings.json | 1 + src/PSRule.Types/Converters/TypeConverter.cs | 24 +- src/PSRule.Types/DictionaryExtensions.cs | 20 +- src/PSRule.Types/Options/BaselineOption.cs | 2 +- src/PSRule.Types/Options/ExecutionOption.cs | 26 +- src/PSRule.Types/TypeExtensions.cs | 24 + src/PSRule/Commands/InvokeRuleBlockCommand.cs | 7 +- src/PSRule/Common/KeyMapDictionary.cs | 13 +- src/PSRule/Configuration/BindingOption.cs | 26 +- src/PSRule/Configuration/ConventionOption.cs | 2 +- src/PSRule/Configuration/FieldMap.cs | 10 + src/PSRule/Configuration/IncludeOption.cs | 4 +- src/PSRule/Configuration/InputOption.cs | 16 +- src/PSRule/Configuration/LoggingOption.cs | 8 +- src/PSRule/Configuration/OutputOption.cs | 24 +- src/PSRule/Configuration/RepositoryOption.cs | 4 +- src/PSRule/Configuration/RequiresOption.cs | 11 + src/PSRule/Configuration/RuleOption.cs | 12 +- src/PSRule/Configuration/SuppressionOption.cs | 11 + src/PSRule/Definitions/Baselines/Baseline.cs | 4 +- .../Conventions/ConventionComparer.cs | 2 +- .../Expressions/LanguageExpressions.cs | 7 +- src/PSRule/Pipeline/IBindingOption.cs | 34 -- src/PSRule/Pipeline/OptionContext.cs | 449 ++---------------- src/PSRule/Pipeline/OptionContextBuilder.cs | 206 +++++++- src/PSRule/Pipeline/OptionScope.cs | 172 +++++++ src/PSRule/Pipeline/OptionScopeComparer.cs | 20 + src/PSRule/Pipeline/PipelineBuilder.cs | 9 +- src/PSRule/Pipeline/PipelineContext.cs | 36 +- src/PSRule/Pipeline/TargetBinder.cs | 34 +- src/PSRule/Runtime/LanguageScope.cs | 47 +- src/PSRule/Runtime/RunspaceContext.cs | 4 +- tests/PSRule.Tests/BaselineTests.cs | 2 +- tests/PSRule.Tests/FunctionBuilderTests.cs | 2 +- tests/PSRule.Tests/FunctionTests.cs | 2 +- tests/PSRule.Tests/MockLanguageScope.cs | 79 +++ tests/PSRule.Tests/ModuleConfigTests.cs | 2 +- tests/PSRule.Tests/OptionContextTests.cs | 166 +++---- tests/PSRule.Tests/OutputWriterTests.cs | 2 +- tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 | 2 +- tests/PSRule.Tests/PSRule.Tests.csproj | 3 + tests/PSRule.Tests/PSRuleOptionTests.cs | 5 +- tests/PSRule.Tests/PipelineTests.cs | 4 +- tests/PSRule.Tests/ResourceValidatorTests.cs | 2 +- tests/PSRule.Tests/RulesTests.cs | 12 +- tests/PSRule.Tests/SelectorTests.cs | 4 +- tests/PSRule.Tests/SuppressionFilterTests.cs | 2 +- tests/PSRule.Tests/SuppressionGroupTests.cs | 4 +- tests/PSRule.Tests/TargetBinderTests.cs | 73 ++- 50 files changed, 907 insertions(+), 748 deletions(-) create mode 100644 src/PSRule.Types/TypeExtensions.cs delete mode 100644 src/PSRule/Pipeline/IBindingOption.cs create mode 100644 src/PSRule/Pipeline/OptionScope.cs create mode 100644 src/PSRule/Pipeline/OptionScopeComparer.cs create mode 100644 tests/PSRule.Tests/MockLanguageScope.cs diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml index 2a736100d6..168500a149 100644 --- a/.github/workflows/analyze.yaml +++ b/.github/workflows/analyze.yaml @@ -42,9 +42,19 @@ jobs: - name: Upload results to security tab uses: github/codeql-action/upload-sarif@v2 + if: always() with: sarif_file: reports/ps-rule-results.sarif + - name: Upload results + uses: actions/upload-artifact@v3 + if: always() + with: + name: PSRule-Sarif + path: reports/ps-rule-results.sarif + retention-days: 1 + if-no-files-found: error + devskim: name: Analyze with DevSkim runs-on: ubuntu-latest @@ -63,9 +73,19 @@ jobs: - name: Upload results to security tab uses: github/codeql-action/upload-sarif@v2 + if: always() with: sarif_file: devskim-results.sarif + - name: Upload results + uses: actions/upload-artifact@v3 + if: always() + with: + name: DevSkim-Sarif + path: devskim-results.sarif + retention-days: 1 + if-no-files-found: error + codeql: name: Analyze with CodeQL runs-on: ubuntu-latest diff --git a/.vscode/settings.json b/.vscode/settings.json index 1402545f85..8e9ed2a1d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -76,6 +76,7 @@ "APPSERVICEMININSTANCECOUNT", "cmdlet", "cmdlets", + "codeql", "concat", "datetime", "deserialize", diff --git a/src/PSRule.Types/Converters/TypeConverter.cs b/src/PSRule.Types/Converters/TypeConverter.cs index 2a76dd6b9a..224a60b7ae 100644 --- a/src/PSRule.Types/Converters/TypeConverter.cs +++ b/src/PSRule.Types/Converters/TypeConverter.cs @@ -8,8 +8,12 @@ namespace PSRule.Converters { internal static class TypeConverter { + private const string PROPERTY_BASEOBJECT = "BaseObject"; + public static bool TryString(object o, out string? value) { + value = null; + if (o == null) return false; if (o is string s) { value = s; @@ -20,7 +24,11 @@ public static bool TryString(object o, out string? value) value = token.Value(); return true; } - value = null; + else if (TryGetValue(o, PROPERTY_BASEOBJECT, out var baseValue) && baseValue is string s_baseValue) + { + value = s_baseValue; + return true; + } return false; } @@ -256,5 +264,19 @@ public static bool TryDouble(object o, bool convert, out double value) value = default; return false; } + + private static bool TryGetValue(object o, string propertyName, out object? value) + { + value = null; + if (o == null) return false; + + var type = o.GetType(); + if (type.TryGetPropertyInfo(propertyName, out var propertyInfo) && propertyInfo != null) + { + value = propertyInfo.GetValue(o); + return true; + } + return false; + } } } diff --git a/src/PSRule.Types/DictionaryExtensions.cs b/src/PSRule.Types/DictionaryExtensions.cs index b62f5646ba..be66a996f0 100644 --- a/src/PSRule.Types/DictionaryExtensions.cs +++ b/src/PSRule.Types/DictionaryExtensions.cs @@ -230,8 +230,26 @@ public static bool TryGetStringArray(this IDictionary dictionary /// Duplicate keys are ignored. /// [DebuggerStepThrough] - public static void AddUnique(this IDictionary dictionary, IEnumerable> values) + public static void AddUnique(this IDictionary dictionary, IEnumerable> values) where T : class { + if (values == null) return; + + foreach (var kv in values) + { + if (!dictionary.ContainsKey(kv.Key)) + dictionary.Add(kv.Key, kv.Value); + } + } + + /// + /// Add unique keys to the dictionary. + /// Duplicate keys are ignored. + /// + [DebuggerStepThrough] + public static void AddUnique(this IDictionary dictionary, IEnumerable> values) + { + if (values == null) return; + foreach (var kv in values) { if (!dictionary.ContainsKey(kv.Key)) diff --git a/src/PSRule.Types/Options/BaselineOption.cs b/src/PSRule.Types/Options/BaselineOption.cs index c1d5963717..cb6a2021e5 100644 --- a/src/PSRule.Types/Options/BaselineOption.cs +++ b/src/PSRule.Types/Options/BaselineOption.cs @@ -88,7 +88,7 @@ public static BaselineOption Combine(BaselineOption o1, BaselineOption o2) { var result = new BaselineOption(o1) { - Group = o1.Group ?? o2.Group, + Group = o1?.Group ?? o2?.Group, }; return result; } diff --git a/src/PSRule.Types/Options/ExecutionOption.cs b/src/PSRule.Types/Options/ExecutionOption.cs index a52f5a58dd..7787ccb70e 100644 --- a/src/PSRule.Types/Options/ExecutionOption.cs +++ b/src/PSRule.Types/Options/ExecutionOption.cs @@ -224,24 +224,24 @@ public override int GetHashCode() } /// - /// Merge two option instances by repacing any unset properties from with values. + /// Merge two option instances by replacing any unset properties from with values. /// Values from that are set are not overridden. /// internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) { var result = new ExecutionOption(o1) { - DuplicateResourceId = o1.DuplicateResourceId ?? o2.DuplicateResourceId, - HashAlgorithm = o1.HashAlgorithm ?? o2.HashAlgorithm, - LanguageMode = o1.LanguageMode ?? o2.LanguageMode, - InitialSessionState = o1.InitialSessionState ?? o2.InitialSessionState, - SuppressionGroupExpired = o1.SuppressionGroupExpired ?? o2.SuppressionGroupExpired, - RuleExcluded = o1.RuleExcluded ?? o2.RuleExcluded, - RuleSuppressed = o1.RuleSuppressed ?? o2.RuleSuppressed, - AliasReference = o1.AliasReference ?? o2.AliasReference, - RuleInconclusive = o1.RuleInconclusive ?? o2.RuleInconclusive, - InvariantCulture = o1.InvariantCulture ?? o2.InvariantCulture, - UnprocessedObject = o1.UnprocessedObject ?? o2.UnprocessedObject, + DuplicateResourceId = o1?.DuplicateResourceId ?? o2?.DuplicateResourceId, + HashAlgorithm = o1?.HashAlgorithm ?? o2?.HashAlgorithm, + LanguageMode = o1?.LanguageMode ?? o2?.LanguageMode, + InitialSessionState = o1?.InitialSessionState ?? o2?.InitialSessionState, + SuppressionGroupExpired = o1?.SuppressionGroupExpired ?? o2?.SuppressionGroupExpired, + RuleExcluded = o1?.RuleExcluded ?? o2?.RuleExcluded, + RuleSuppressed = o1?.RuleSuppressed ?? o2?.RuleSuppressed, + AliasReference = o1?.AliasReference ?? o2?.AliasReference, + RuleInconclusive = o1?.RuleInconclusive ?? o2?.RuleInconclusive, + InvariantCulture = o1?.InvariantCulture ?? o2?.InvariantCulture, + UnprocessedObject = o1?.UnprocessedObject ?? o2?.UnprocessedObject, }; return result; } @@ -249,7 +249,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// /// Determines how to handle duplicate resources identifiers during execution. /// Regardless of the value, only the first resource will be used. - /// By defaut, an error is thrown. + /// By default, an error is thrown. /// When set to Warn, a warning is generated. /// When set to Debug, a message is written to the debug log. /// When set to Ignore, no output will be displayed. diff --git a/src/PSRule.Types/TypeExtensions.cs b/src/PSRule.Types/TypeExtensions.cs new file mode 100644 index 0000000000..d479fc7e24 --- /dev/null +++ b/src/PSRule.Types/TypeExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Reflection; + +namespace PSRule +{ + internal static class TypeExtensions + { + public static bool TryGetPropertyInfo(this Type type, string propertyName, out PropertyInfo? value) + { + value = null; + if (type == null || propertyName == null) + return false; + + var propertyInfo = type.GetProperty(propertyName); + if (propertyInfo == null) + return false; + + value = propertyInfo; + return true; + } + } +} diff --git a/src/PSRule/Commands/InvokeRuleBlockCommand.cs b/src/PSRule/Commands/InvokeRuleBlockCommand.cs index 85239384cf..7d62d23bb9 100644 --- a/src/PSRule/Commands/InvokeRuleBlockCommand.cs +++ b/src/PSRule/Commands/InvokeRuleBlockCommand.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Management.Automation; +using PSRule.Configuration; using PSRule.Definitions; using PSRule.Pipeline; using PSRule.Resources; @@ -10,7 +11,7 @@ namespace PSRule.Commands { /// - /// An internal langauge command used to evaluate a rule script block. + /// An internal language command used to evaluate a rule script block. /// internal sealed class InvokeRuleBlockCommand : Cmdlet { @@ -37,14 +38,14 @@ protected override void ProcessRecord() if (Body == null) return; - // Evalute selector pre-condition + // Evaluate selector pre-condition if (!AcceptsWith()) { context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); return; } - // Evalute type pre-condition + // Evaluate type pre-condition if (!AcceptsType()) { context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); diff --git a/src/PSRule/Common/KeyMapDictionary.cs b/src/PSRule/Common/KeyMapDictionary.cs index 35be6cf7ab..8a0b090ea5 100644 --- a/src/PSRule/Common/KeyMapDictionary.cs +++ b/src/PSRule/Common/KeyMapDictionary.cs @@ -23,20 +23,19 @@ protected internal KeyMapDictionary() } /// - /// Create a map intially populated with values copied from an existing instance. + /// Create a map initially populated with values copied from an existing instance. /// /// An existing instance to copy key/ values from. /// Is raised if the map is null. protected internal KeyMapDictionary(KeyMapDictionary map) { - if (map == null) - throw new ArgumentNullException(nameof(map)); - - _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); + _Map = map == null ? + new Dictionary(StringComparer.OrdinalIgnoreCase) : + new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); } /// - /// Create a map intially populated with values copied from a dictionary. + /// Create a map initially populated with values copied from a dictionary. /// /// An existing dictionary to copy key/ values from. protected internal KeyMapDictionary(IDictionary dictionary) @@ -47,7 +46,7 @@ protected internal KeyMapDictionary(IDictionary dictionary) } /// - /// Create a map intially populated with values copied from a hashtable. + /// Create a map initially populated with values copied from a hashtable. /// /// An existing hashtable to copy key/ values from. protected internal KeyMapDictionary(Hashtable hashtable) diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index fb2ed7a064..9aa9a3d2aa 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -2,9 +2,19 @@ // Licensed under the MIT License. using System.ComponentModel; +using System.Diagnostics; namespace PSRule.Configuration { + internal static class BindingOptionExtensions + { + [DebuggerStepThrough] + public static StringComparer GetComparer(this BindingOption option) + { + return option.IgnoreCase.GetValueOrDefault(BindingOption.Default.IgnoreCase.Value) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + } + } + /// /// Options that affect property binding of TargetName and TargetType. /// @@ -92,20 +102,20 @@ public override int GetHashCode() } /// - /// Merge two option instances by repacing any unset properties from with values. + /// Merge two option instances by replacing any unset properties from with values. /// Values from that are set are not overridden. /// internal static BindingOption Combine(BindingOption o1, BindingOption o2) { var result = new BindingOption(o1) { - Field = o1.Field ?? o2.Field, - IgnoreCase = o1.IgnoreCase ?? o2.IgnoreCase, - NameSeparator = o1.NameSeparator ?? o2.NameSeparator, - PreferTargetInfo = o1.PreferTargetInfo ?? o2.PreferTargetInfo, - TargetName = o1.TargetName ?? o2.TargetName, - TargetType = o1.TargetType ?? o2.TargetType, - UseQualifiedName = o1.UseQualifiedName ?? o2.UseQualifiedName + Field = FieldMap.Combine(o1?.Field, o2?.Field), + IgnoreCase = o1?.IgnoreCase ?? o2?.IgnoreCase, + NameSeparator = o1?.NameSeparator ?? o2?.NameSeparator, + PreferTargetInfo = o1?.PreferTargetInfo ?? o2?.PreferTargetInfo, + TargetName = o1?.TargetName ?? o2?.TargetName, + TargetType = o1?.TargetType ?? o2?.TargetType, + UseQualifiedName = o1?.UseQualifiedName ?? o2?.UseQualifiedName }; return result; } diff --git a/src/PSRule/Configuration/ConventionOption.cs b/src/PSRule/Configuration/ConventionOption.cs index 1fcdafd205..9c519e33bb 100644 --- a/src/PSRule/Configuration/ConventionOption.cs +++ b/src/PSRule/Configuration/ConventionOption.cs @@ -63,7 +63,7 @@ internal static ConventionOption Combine(ConventionOption o1, ConventionOption o { var result = new ConventionOption(o1) { - Include = o1.Include ?? o2.Include + Include = o1?.Include ?? o2?.Include }; return result; } diff --git a/src/PSRule/Configuration/FieldMap.cs b/src/PSRule/Configuration/FieldMap.cs index a35af3a905..3d4adbd5fb 100644 --- a/src/PSRule/Configuration/FieldMap.cs +++ b/src/PSRule/Configuration/FieldMap.cs @@ -119,5 +119,15 @@ internal IDictionary GetFieldMap { get => _Map; } + + internal static FieldMap Combine(FieldMap m1, FieldMap m2) + { + if (m1 == null) return m2; + if (m2 == null) return m1; + + var result = new FieldMap(m1); + result._Map.AddUnique(m2._Map); + return result; + } } } diff --git a/src/PSRule/Configuration/IncludeOption.cs b/src/PSRule/Configuration/IncludeOption.cs index 82cdc5f871..3146f36b57 100644 --- a/src/PSRule/Configuration/IncludeOption.cs +++ b/src/PSRule/Configuration/IncludeOption.cs @@ -71,8 +71,8 @@ internal static IncludeOption Combine(IncludeOption o1, IncludeOption o2) { var result = new IncludeOption(o1) { - Path = o1.Path ?? o2.Path, - Module = o1.Module ?? o2.Module + Path = o1?.Path ?? o2?.Path, + Module = o1?.Module ?? o2?.Module }; return result; } diff --git a/src/PSRule/Configuration/InputOption.cs b/src/PSRule/Configuration/InputOption.cs index 66bfeebb6e..26d3c9f4cd 100644 --- a/src/PSRule/Configuration/InputOption.cs +++ b/src/PSRule/Configuration/InputOption.cs @@ -111,14 +111,14 @@ internal static InputOption Combine(InputOption o1, InputOption o2) { var result = new InputOption(o1) { - Format = o1.Format ?? o2.Format, - IgnoreGitPath = o1.IgnoreGitPath ?? o2.IgnoreGitPath, - IgnoreObjectSource = o1.IgnoreObjectSource ?? o2.IgnoreObjectSource, - IgnoreRepositoryCommon = o1.IgnoreRepositoryCommon ?? o2.IgnoreRepositoryCommon, - IgnoreUnchangedPath = o1.IgnoreUnchangedPath ?? o2.IgnoreUnchangedPath, - ObjectPath = o1.ObjectPath ?? o2.ObjectPath, - PathIgnore = o1.PathIgnore ?? o2.PathIgnore, - TargetType = o1.TargetType ?? o2.TargetType + Format = o1?.Format ?? o2?.Format, + IgnoreGitPath = o1?.IgnoreGitPath ?? o2?.IgnoreGitPath, + IgnoreObjectSource = o1?.IgnoreObjectSource ?? o2?.IgnoreObjectSource, + IgnoreRepositoryCommon = o1?.IgnoreRepositoryCommon ?? o2?.IgnoreRepositoryCommon, + IgnoreUnchangedPath = o1?.IgnoreUnchangedPath ?? o2?.IgnoreUnchangedPath, + ObjectPath = o1?.ObjectPath ?? o2?.ObjectPath, + PathIgnore = o1?.PathIgnore ?? o2?.PathIgnore, + TargetType = o1?.TargetType ?? o2?.TargetType }; return result; } diff --git a/src/PSRule/Configuration/LoggingOption.cs b/src/PSRule/Configuration/LoggingOption.cs index 0e58bff3dc..1ecc04e084 100644 --- a/src/PSRule/Configuration/LoggingOption.cs +++ b/src/PSRule/Configuration/LoggingOption.cs @@ -81,10 +81,10 @@ internal static LoggingOption Combine(LoggingOption o1, LoggingOption o2) { var result = new LoggingOption(o1) { - LimitDebug = o1.LimitDebug ?? o2.LimitDebug, - LimitVerbose = o1.LimitVerbose ?? o2.LimitVerbose, - RuleFail = o1.RuleFail ?? o2.RuleFail, - RulePass = o1.RulePass ?? o2.RulePass + LimitDebug = o1?.LimitDebug ?? o2?.LimitDebug, + LimitVerbose = o1?.LimitVerbose ?? o2?.LimitVerbose, + RuleFail = o1?.RuleFail ?? o2?.RuleFail, + RulePass = o1?.RulePass ?? o2?.RulePass }; return result; } diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 39d9e2e0d6..71a801f11d 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -125,18 +125,18 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) { var result = new OutputOption(o1) { - As = o1.As ?? o2.As, - Banner = o1.Banner ?? o2.Banner, - Culture = o1.Culture ?? o2.Culture, - Encoding = o1.Encoding ?? o2.Encoding, - Footer = o1.Footer ?? o2.Footer, - Format = o1.Format ?? o2.Format, - JobSummaryPath = o1.JobSummaryPath ?? o2.JobSummaryPath, - JsonIndent = o1.JsonIndent ?? o2.JsonIndent, - Outcome = o1.Outcome ?? o2.Outcome, - Path = o1.Path ?? o2.Path, - SarifProblemsOnly = o1.SarifProblemsOnly ?? o2.SarifProblemsOnly, - Style = o1.Style ?? o2.Style, + As = o1?.As ?? o2?.As, + Banner = o1?.Banner ?? o2?.Banner, + Culture = o1?.Culture ?? o2?.Culture, + Encoding = o1?.Encoding ?? o2?.Encoding, + Footer = o1?.Footer ?? o2?.Footer, + Format = o1?.Format ?? o2?.Format, + JobSummaryPath = o1?.JobSummaryPath ?? o2?.JobSummaryPath, + JsonIndent = o1?.JsonIndent ?? o2?.JsonIndent, + Outcome = o1?.Outcome ?? o2?.Outcome, + Path = o1?.Path ?? o2?.Path, + SarifProblemsOnly = o1?.SarifProblemsOnly ?? o2?.SarifProblemsOnly, + Style = o1?.Style ?? o2?.Style, }; return result; } diff --git a/src/PSRule/Configuration/RepositoryOption.cs b/src/PSRule/Configuration/RepositoryOption.cs index 92c2bcf1de..73ed331e7e 100644 --- a/src/PSRule/Configuration/RepositoryOption.cs +++ b/src/PSRule/Configuration/RepositoryOption.cs @@ -72,8 +72,8 @@ internal static RepositoryOption Combine(RepositoryOption o1, RepositoryOption o { var result = new RepositoryOption(o1) { - BaseRef = o1.BaseRef ?? o2.BaseRef, - Url = o1.Url ?? o2.Url, + BaseRef = o1?.BaseRef ?? o2?.BaseRef, + Url = o1?.Url ?? o2?.Url, }; return result; } diff --git a/src/PSRule/Configuration/RequiresOption.cs b/src/PSRule/Configuration/RequiresOption.cs index b26c695a24..c88bdb3ba4 100644 --- a/src/PSRule/Configuration/RequiresOption.cs +++ b/src/PSRule/Configuration/RequiresOption.cs @@ -44,6 +44,17 @@ public ModuleConstraint[] ToArray() return result.ToArray(); } + /// + /// Merge two option instances by replacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static RequiresOption Combine(RequiresOption o1, RequiresOption o2) + { + var result = new RequiresOption(o1); + result.AddUnique(o2); + return result; + } + /// /// Load Requires option from environment variables. /// diff --git a/src/PSRule/Configuration/RuleOption.cs b/src/PSRule/Configuration/RuleOption.cs index f465f41a54..1e91459b62 100644 --- a/src/PSRule/Configuration/RuleOption.cs +++ b/src/PSRule/Configuration/RuleOption.cs @@ -91,12 +91,12 @@ internal static RuleOption Combine(RuleOption o1, RuleOption o2) { var result = new RuleOption(o1) { - Baseline = o1.Baseline ?? o2.Baseline, - Exclude = o1.Exclude ?? o2.Exclude, - IncludeLocal = o1.IncludeLocal ?? o2.IncludeLocal, - Include = o1.Include ?? o2.Include, - Tag = o1.Tag ?? o2.Tag, - Labels = o1.Labels ?? o2.Labels, + Baseline = o1?.Baseline ?? o2?.Baseline, + Exclude = o1?.Exclude ?? o2?.Exclude, + IncludeLocal = o1?.IncludeLocal ?? o2?.IncludeLocal, + Include = o1?.Include ?? o2?.Include, + Tag = o1?.Tag ?? o2?.Tag, + Labels = o1?.Labels ?? o2?.Labels, }; return result; } diff --git a/src/PSRule/Configuration/SuppressionOption.cs b/src/PSRule/Configuration/SuppressionOption.cs index c94b6f1485..68dc9a7701 100644 --- a/src/PSRule/Configuration/SuppressionOption.cs +++ b/src/PSRule/Configuration/SuppressionOption.cs @@ -102,6 +102,17 @@ public IEnumerator> GetEnumerator() return _Rules.GetEnumerator(); } + /// + /// Merge two option instances by replacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static SuppressionOption Combine(SuppressionOption o1, SuppressionOption o2) + { + var result = new SuppressionOption(o1); + result.AddUnique(o2); + return result; + } + /// /// Remove a specific by rule name. /// diff --git a/src/PSRule/Definitions/Baselines/Baseline.cs b/src/PSRule/Definitions/Baselines/Baseline.cs index b28146e57e..34ab38a91a 100644 --- a/src/PSRule/Definitions/Baselines/Baseline.cs +++ b/src/PSRule/Definitions/Baselines/Baseline.cs @@ -113,9 +113,9 @@ private bool MatchWildcard(string name) internal sealed class BaselineRef : ResourceRef { - public readonly OptionContext.ScopeType Type; + public readonly ScopeType Type; - public BaselineRef(string id, OptionContext.ScopeType scopeType) + public BaselineRef(string id, ScopeType scopeType) : base(id, ResourceKind.Baseline) { Type = scopeType; diff --git a/src/PSRule/Definitions/Conventions/ConventionComparer.cs b/src/PSRule/Definitions/Conventions/ConventionComparer.cs index aae17b4c57..e382b197e1 100644 --- a/src/PSRule/Definitions/Conventions/ConventionComparer.cs +++ b/src/PSRule/Definitions/Conventions/ConventionComparer.cs @@ -19,7 +19,7 @@ internal ConventionComparer(RunspaceContext context) public int Compare(IConvention x, IConvention y) { - return _Context.Pipeline.Baseline.GetConventionOrder(x) - _Context.Pipeline.Baseline.GetConventionOrder(y); + return _Context.Pipeline.GetConventionOrder(x) - _Context.Pipeline.GetConventionOrder(y); } } } diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index 4ee09a779c..b24484a925 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Diagnostics; +using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; using PSRule.Resources; @@ -179,7 +180,7 @@ private static LanguageExpressionOuterFn PreconditionSelector(string[] with, Lan { return (context, o) => { - // Evalute selector pre-condition + // Evaluate selector pre-condition if (!AcceptsWith(with)) { context.Debug(PSRuleResources.DebugTargetTypeMismatch); @@ -193,7 +194,7 @@ private static LanguageExpressionOuterFn PreconditionType(string[] type, Languag { return (context, o) => { - // Evalute type pre-condition + // Evaluate type pre-condition if (!AcceptsType(type)) { context.Debug(PSRuleResources.DebugTargetTypeMismatch); @@ -211,7 +212,7 @@ private static LanguageExpressionOuterFn PreconditionSubselector(LanguageExpress { context.PushScope(RunspaceScope.Precondition); - // Evalute sub-selector pre-condition + // Evaluate sub-selector pre-condition if (!AcceptsSubselector(context, subselector, o)) { context.Debug(PSRuleResources.DebugTargetSubselectorMismatch); diff --git a/src/PSRule/Pipeline/IBindingOption.cs b/src/PSRule/Pipeline/IBindingOption.cs deleted file mode 100644 index 7bfc0bd85b..0000000000 --- a/src/PSRule/Pipeline/IBindingOption.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics; -using PSRule.Configuration; - -namespace PSRule.Pipeline -{ - internal interface IBindingOption - { - FieldMap[] Field { get; } - - bool IgnoreCase { get; } - - string NameSeparator { get; } - - bool PreferTargetInfo { get; } - - string[] TargetName { get; } - - string[] TargetType { get; } - - bool UseQualifiedName { get; } - } - - internal static class BindingOptionExtensions - { - [DebuggerStepThrough] - public static StringComparer GetComparer(this IBindingOption option) - { - return option.IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - } - } -} diff --git a/src/PSRule/Pipeline/OptionContext.cs b/src/PSRule/Pipeline/OptionContext.cs index 7dd1548985..f773726aa0 100644 --- a/src/PSRule/Pipeline/OptionContext.cs +++ b/src/PSRule/Pipeline/OptionContext.cs @@ -1,467 +1,74 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using PSRule.Configuration; using PSRule.Definitions; -using PSRule.Definitions.Baselines; -using PSRule.Definitions.Conventions; -using PSRule.Definitions.ModuleConfigs; -using PSRule.Definitions.Rules; -using PSRule.Runtime; +using PSRule.Options; namespace PSRule.Pipeline { - /// - /// Returns options based on the executing scope of the rule or resource. - /// internal sealed class OptionContext { - private readonly Dictionary _ModuleBaselineScope; - private readonly Dictionary _ModuleConfigScope; - private readonly List _ConventionOrder; - private readonly string[] _DefaultCulture; + private ConventionOption _Convention; + private List _ConventionOrder; - /// - /// Used when options are passed in by command line parameters. - /// - private BaselineScope _Parameter; - - /// - /// Used when a baseline is explictly set by name. - /// - private BaselineScope _Explicit; - - private BaselineScope _WorkspaceBaseline; - private ConfigScope _WorkspaceConfig; - - private BaselineScope _ModuleBaseline; - private ConfigScope _ModuleConfig; - - internal OptionContext() - { - _ModuleBaselineScope = new Dictionary(); - _ModuleConfigScope = new Dictionary(); - _ConventionOrder = new List(); - _DefaultCulture = GetDefaultCulture(); - } - - #region Helper classes - - internal enum ScopeType - { - /// - /// Used when options are passed in by command line parameters. - /// - Parameter = 0, - - /// - /// Used when a baseline is explictly set by name. - /// - Explicit = 1, - - /// - /// Used when options are set within the PSRule options from the workspace or an options object. - /// - Workspace = 2, - - /// - /// Used for options that are inherited from module configuration. - /// - Module = 3 - } - - internal abstract class OptionScope + public OptionContext() { - public readonly ScopeType Type; - public readonly string ModuleName; - protected OptionScope(ScopeType type, string moduleName) - { - Type = type; - ModuleName = moduleName; - } - } - - internal sealed class BaselineScope : OptionScope - { - public string Id; - public bool Obsolete; - - // Rule - public bool? IncludeLocal; - public string[] Include; - public string[] Exclude; - public Hashtable Tag; - public ResourceLabels Labels; - - // Configuration - public Dictionary Configuration; - - public ConventionOption Convention; - - // Binding - public FieldMap Field; - public bool? IgnoreCase; - public string NameSeparator; - public bool? PreferTargetInfo; - public string[] TargetName; - public string[] TargetType; - public bool? UseQualifiedName; - - public BaselineScope(ScopeType type, string baselineId, string moduleName, IBaselineV1Spec option, bool obsolete) - : base(type, moduleName) - { - Id = baselineId; - Obsolete = obsolete; - Field = option.Binding?.Field; - IgnoreCase = option.Binding?.IgnoreCase; - NameSeparator = option?.Binding?.NameSeparator; - PreferTargetInfo = option.Binding?.PreferTargetInfo; - TargetName = option.Binding?.TargetName; - TargetType = option.Binding?.TargetType; - UseQualifiedName = option.Binding?.UseQualifiedName; - IncludeLocal = option.Rule?.IncludeLocal; - Include = option.Rule?.Include; - Exclude = option.Rule?.Exclude; - Tag = option.Rule?.Tag; - Labels = option.Rule?.Labels; - Configuration = option.Configuration != null - ? new Dictionary(option.Configuration, StringComparer.OrdinalIgnoreCase) - : new Dictionary(StringComparer.OrdinalIgnoreCase); - Convention = new ConventionOption(option.Convention); - } - - public BaselineScope(ScopeType type, string[] include, Hashtable tag, string[] convention) - : base(type, null) - { - Include = include; - Tag = tag; - Configuration = new Dictionary(StringComparer.OrdinalIgnoreCase); - Convention = convention == null || convention.Length == 0 ? new ConventionOption() : new ConventionOption - { - Include = convention - }; - } } - internal sealed class ConfigScope : OptionScope - { - // Configuration - public Dictionary Configuration; - - public ConventionOption Convention; - - // Binding - public FieldMap Field; - public bool? IgnoreCase; - public string NameSeparator; - public bool? PreferTargetInfo; - public string[] TargetName; - public string[] TargetType; - public bool? UseQualifiedName; + public Options.BaselineOption Baseline { get; set; } - // Output - public string[] Culture; - - public ConfigScope(ScopeType type, string moduleName, PSRuleOption option) - : base(type, moduleName) - { - Field = option.Binding?.Field; - IgnoreCase = option.Binding?.IgnoreCase; - NameSeparator = option?.Binding?.NameSeparator; - PreferTargetInfo = option.Binding?.PreferTargetInfo; - TargetName = option.Binding?.TargetName; - TargetType = option.Binding?.TargetType; - UseQualifiedName = option.Binding?.UseQualifiedName; - Culture = option.Output?.Culture; - Configuration = option.Configuration != null - ? new Dictionary(option.Configuration, StringComparer.OrdinalIgnoreCase) - : new Dictionary(StringComparer.OrdinalIgnoreCase); - Convention = new ConventionOption(option.Convention); - } + public BindingOption Binding { get; set; } - public ConfigScope(ScopeType type, string moduleName, ModuleConfigV1Spec spec) - : base(type, moduleName) - { - Field = spec.Binding?.Field; - IgnoreCase = spec.Binding?.IgnoreCase; - NameSeparator = spec?.Binding?.NameSeparator; - PreferTargetInfo = spec.Binding?.PreferTargetInfo; - TargetName = spec.Binding?.TargetName; - TargetType = spec.Binding?.TargetType; - UseQualifiedName = spec.Binding?.UseQualifiedName; - Culture = spec.Output?.Culture; - Configuration = spec.Configuration != null - ? new Dictionary(spec.Configuration, StringComparer.OrdinalIgnoreCase) - : new Dictionary(StringComparer.OrdinalIgnoreCase); - Convention = new ConventionOption(spec.Convention); - } - } + public ConfigurationOption Configuration { get; set; } - private sealed class BindingOption : IBindingOption, IEquatable + public ConventionOption Convention { - public BindingOption(FieldMap[] field, bool ignoreCase, bool ignoreTargetInfo, string nameSeparator, string[] targetName, string[] targetType, bool useQualifiedName) - { - Field = field; - IgnoreCase = ignoreCase; - PreferTargetInfo = ignoreTargetInfo; - NameSeparator = nameSeparator; - TargetName = targetName; - TargetType = targetType; - UseQualifiedName = useQualifiedName; - } - - public FieldMap[] Field { get; } - - public bool IgnoreCase { get; } - - public string NameSeparator { get; } - - public bool PreferTargetInfo { get; } - - public string[] TargetName { get; } - - public string[] TargetType { get; } - - public bool UseQualifiedName { get; } - - public override bool Equals(object obj) - { - return obj is BindingOption option && Equals(option); - } - - public bool Equals(BindingOption other) + get { - return other != null && - Field == other.Field && - IgnoreCase == other.IgnoreCase && - NameSeparator == other.NameSeparator && - PreferTargetInfo == other.PreferTargetInfo && - TargetName == other.TargetName && - TargetType == other.TargetType && - UseQualifiedName == other.UseQualifiedName; + return _Convention; } - - public override int GetHashCode() + set { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Field != null ? Field.GetHashCode() : 0); - hash = hash * 23 + (IgnoreCase ? IgnoreCase.GetHashCode() : 0); - hash = hash * 23 + (NameSeparator != null ? NameSeparator.GetHashCode() : 0); - hash = hash * 23 + (PreferTargetInfo ? PreferTargetInfo.GetHashCode() : 0); - hash = hash * 23 + (TargetName != null ? TargetName.GetHashCode() : 0); - hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); - hash = hash * 23 + (UseQualifiedName ? UseQualifiedName.GetHashCode() : 0); - return hash; - } + _Convention = value; + _ConventionOrder = null; } } - #endregion Helper classes + public ExecutionOption Execution { get; set; } - public bool ContainsBaseline(string baselineId) - { - return _ModuleBaselineScope.ContainsKey(baselineId); - } - - /// - /// Update a language scope from scoped options. - /// - /// The specific language scope. - /// Returned when the language scope is null. - public void UpdateLanguageScope(ILanguageScope languageScope) - { - if (languageScope == null) - throw new ArgumentNullException(nameof(languageScope)); + public IncludeOption Include { get; set; } - ResetScope(languageScope.Name); - languageScope.Configure(GetConfiguration()); - languageScope.WithFilter(GetRuleFilter()); - languageScope.WithFilter(GetConventionFilter()); - languageScope.WithBinding(GetTargetBinding()); - languageScope.WithCulture(GetCulture()); - } + public InputOption Input { get; set; } - /// - /// Check for any obsolete resources and log warnings. - /// - internal void CheckObsolete(ILogger logger) - { - if (logger == null) - return; + public LoggingOption Logging { get; set; } - foreach (var baseline in _ModuleBaselineScope.Values) - { - if (baseline.Obsolete) - logger.WarnResourceObsolete(ResourceKind.Baseline, baseline.Id); - } - if (_Explicit != null && _Explicit.Obsolete) - logger.WarnResourceObsolete(ResourceKind.Baseline, _Explicit.Id); - } + public OutputOption Output { get; set; } - internal void Add(BaselineScope scope) - { - if (scope == null) - return; + public RepositoryOption Repository { get; set; } - var conventions = scope.Convention?.Include; - if (scope.Type == ScopeType.Module && !string.IsNullOrEmpty(scope.ModuleName) && !_ModuleBaselineScope.ContainsKey(scope.ModuleName)) - { - _ModuleBaselineScope.Add(scope.ModuleName, scope); - conventions = GetConventions(scope.ModuleName, scope.Convention?.Include); - } - else if (scope.Type == ScopeType.Explicit) - { - _Explicit = scope; - if (scope.ModuleName != null) - conventions = GetConventions(scope.ModuleName, scope.Convention?.Include); - } - else if (scope.Type == ScopeType.Workspace) - _WorkspaceBaseline = scope; - else if (scope.Type == ScopeType.Parameter) - _Parameter = scope; + public RequiresOption Requires { get; set; } - if (conventions != null) - _ConventionOrder.AddRange(conventions); - } + public RuleOption Rule { get; set; } - internal void Add(ConfigScope scope) - { - if (scope == null) - return; + public SuppressionOption Suppression { get; set; } - var conventions = scope.Convention?.Include; - if (scope.Type == ScopeType.Module && !string.IsNullOrEmpty(scope.ModuleName)) - { - _ModuleConfigScope.Add(scope.ModuleName, scope); - conventions = GetConventions(scope.ModuleName, scope.Convention?.Include); - } - else if (scope.Type == ScopeType.Workspace) - _WorkspaceConfig = scope; + public IResourceFilter ConventionFilter { get; set; } - if (conventions != null) - _ConventionOrder.AddRange(conventions); - } + public IResourceFilter RuleFilter { get; set; } internal int GetConventionOrder(IConvention convention) { + if (Convention?.Include == null || Convention.Include.Length == 0) + return -1; + + _ConventionOrder ??= new List(Convention.Include); var index = _ConventionOrder.IndexOf(convention.Id.Value); if (index == -1) index = _ConventionOrder.IndexOf(convention.Name); return index > -1 ? index : int.MaxValue; } - - #region Private methods - - private static string[] GetConventions(string scope, string[] include) - { - if (include == null || include.Length == 0) - return null; - - for (var i = 0; i < include.Length; i++) - include[i] = ResourceHelper.GetIdString(scope, include[i]); - - return include; - } - - private void ResetScope(string moduleName) - { - _ModuleConfig = !string.IsNullOrEmpty(moduleName) && _ModuleConfigScope.TryGetValue(moduleName, out var configScope) ? configScope : null; - _ModuleBaseline = !string.IsNullOrEmpty(moduleName) && _ModuleBaselineScope.TryGetValue(moduleName, out var baselineScope) ? baselineScope : null; - } - - private IResourceFilter GetRuleFilter() - { - var include = _Parameter?.Include ?? _Explicit?.Include ?? _WorkspaceBaseline?.Include ?? _ModuleBaseline?.Include; - var exclude = _Explicit?.Exclude ?? _WorkspaceBaseline?.Exclude ?? _ModuleBaseline?.Exclude; - var tag = _Parameter?.Tag ?? _Explicit?.Tag ?? _WorkspaceBaseline?.Tag ?? _ModuleBaseline?.Tag; - var labels = _Parameter?.Labels ?? _Explicit?.Labels ?? _WorkspaceBaseline?.Labels ?? _ModuleBaseline?.Labels; - var includeLocal = _Explicit == null && - _Parameter?.Include == null && - _Parameter?.Tag == null && - _Parameter?.Labels == null && - (_WorkspaceBaseline == null || !_WorkspaceBaseline.IncludeLocal.HasValue) ? true : _WorkspaceBaseline?.IncludeLocal; - return new RuleFilter(include, tag, exclude, includeLocal, labels); - } - - private IResourceFilter GetConventionFilter() - { - var include = new List(); - for (var i = 0; _Parameter?.Convention?.Include != null && i < _Parameter.Convention.Include.Length; i++) - include.Add(_Parameter.Convention.Include[i]); - - for (var i = 0; _WorkspaceConfig?.Convention?.Include != null && i < _WorkspaceConfig.Convention.Include.Length; i++) - include.Add(_WorkspaceConfig.Convention.Include[i]); - - for (var i = 0; _ModuleConfig?.Convention?.Include != null && i < _ModuleConfig.Convention.Include.Length; i++) - include.Add(ResourceHelper.GetIdString(_ModuleConfig.ModuleName, _ModuleConfig.Convention.Include[i])); - - return new ConventionFilter(include.ToArray()); - } - - private IBindingOption GetTargetBinding() - { - var field = new FieldMap[] { _Explicit?.Field, _WorkspaceBaseline?.Field, _ModuleBaseline?.Field, _ModuleConfig?.Field }; - var ignoreCase = _Explicit?.IgnoreCase ?? _WorkspaceBaseline?.IgnoreCase ?? _ModuleBaseline?.IgnoreCase ?? _ModuleConfig?.IgnoreCase ?? Configuration.BindingOption.Default.IgnoreCase.Value; - var nameSeparator = _Explicit?.NameSeparator ?? _WorkspaceBaseline?.NameSeparator ?? _ModuleBaseline?.NameSeparator ?? _ModuleConfig?.NameSeparator ?? Configuration.BindingOption.Default.NameSeparator; - var preferTargetInfo = _Explicit?.PreferTargetInfo ?? _WorkspaceBaseline?.PreferTargetInfo ?? _ModuleBaseline?.PreferTargetInfo ?? _ModuleConfig?.PreferTargetInfo ?? Configuration.BindingOption.Default.PreferTargetInfo.Value; - var targetName = _Explicit?.TargetName ?? _WorkspaceBaseline?.TargetName ?? _ModuleBaseline?.TargetName ?? _ModuleConfig?.TargetName; - var targetType = _Explicit?.TargetType ?? _WorkspaceBaseline?.TargetType ?? _ModuleBaseline?.TargetType ?? _ModuleConfig?.TargetType; - var useQualifiedName = _Explicit?.UseQualifiedName ?? _WorkspaceBaseline?.UseQualifiedName ?? _ModuleBaseline?.UseQualifiedName ?? _ModuleConfig?.UseQualifiedName ?? Configuration.BindingOption.Default.UseQualifiedName.Value; - return new BindingOption(field, ignoreCase, preferTargetInfo, nameSeparator, targetName, targetType, useQualifiedName); - } - - private Dictionary GetConfiguration() - { - var result = new Dictionary(); - if (_Explicit != null && _Explicit.Configuration.Count > 0) - result.AddUnique(_Explicit.Configuration); - - if (_WorkspaceBaseline != null && _WorkspaceBaseline.Configuration.Count > 0) - result.AddUnique(_WorkspaceBaseline.Configuration); - - if (_ModuleBaseline != null && _ModuleBaseline.Configuration.Count > 0) - result.AddUnique(_ModuleBaseline.Configuration); - - if (_ModuleConfig != null && _ModuleConfig.Configuration.Count > 0) - result.AddUnique(_ModuleConfig.Configuration); - - return result; - } - - /// - /// Get an ordered culture preference list. - /// - /// An ordered array of cultures to be tried for help. - private string[] GetCulture() - { - return _WorkspaceConfig?.Culture ?? _ModuleConfig?.Culture ?? _DefaultCulture; - } - - private static string[] GetDefaultCulture() - { - var result = new List(); - var set = new HashSet(); - - // Fallback to current culture - var current = Environment.GetCurrentCulture(); - if (!set.Contains(current.Name) && !string.IsNullOrEmpty(current.Name)) - { - result.Add(current.Name); - set.Add(current.Name); - } - for (var p = current.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent) - { - if (!result.Contains(p.Name)) - result.Add(p.Name); - } - return result.ToArray(); - } - - #endregion Private methods } } diff --git a/src/PSRule/Pipeline/OptionContextBuilder.cs b/src/PSRule/Pipeline/OptionContextBuilder.cs index 8a55650440..34e3fcb7a5 100644 --- a/src/PSRule/Pipeline/OptionContextBuilder.cs +++ b/src/PSRule/Pipeline/OptionContextBuilder.cs @@ -3,6 +3,13 @@ using System.Collections; using PSRule.Configuration; +using PSRule.Definitions; +using PSRule.Definitions.Baselines; +using PSRule.Definitions.Conventions; +using PSRule.Definitions.ModuleConfigs; +using PSRule.Definitions.Rules; +using PSRule.Options; +using PSRule.Runtime; namespace PSRule.Pipeline { @@ -11,7 +18,21 @@ namespace PSRule.Pipeline /// internal sealed class OptionContextBuilder { - private readonly OptionContext _OptionContext; + private readonly Dictionary _ModuleBaselineScope; + private readonly List _Scopes; + private readonly OptionScopeComparer _Comparer; + private readonly string[] _DefaultCulture; + private readonly List _ConventionOrder; + + internal OptionContextBuilder(string[] include = null, Hashtable tag = null, string[] convention = null) + { + _ModuleBaselineScope = new Dictionary(); + _Scopes = new List(); + _Comparer = new OptionScopeComparer(); + _DefaultCulture = GetDefaultCulture(); + _ConventionOrder = new List(); + Parameter(include, tag, convention); + } /// /// Create a builder with parameter and workspace options set. @@ -21,43 +42,182 @@ internal sealed class OptionContextBuilder /// A tag filter to determine which rules are included by parameters. /// A list of conventions to include by parameters. internal OptionContextBuilder(PSRuleOption option, string[] include = null, Hashtable tag = null, string[] convention = null) + : this(include, tag, convention) { - _OptionContext = new OptionContext(); - Parameter(include, tag, convention); Workspace(option); } /// /// Build an . /// - /// - internal OptionContext Build() + internal OptionContext Build(string languageScope) + { + languageScope = LanguageScope.Normalize(languageScope); + var context = new OptionContext(); + + _Scopes.Sort(_Comparer); + + for (var i = 0; i < _Scopes.Count; i++) + { + if (_Scopes[i] != null && ShouldCombine(languageScope, _Scopes[i])) + Combine(context, _Scopes[i]); + } + //Combine(PSRuleOption.FromDefault()); + context.Output.Culture ??= _DefaultCulture; + context.Rule.IncludeLocal = GetIncludeLocal(_Scopes) ?? context.Rule.IncludeLocal ?? true; + + context.RuleFilter = GetRuleFilter(context.Rule); + context.ConventionFilter = GetConventionFilter(languageScope, _Scopes); + context.Convention = new ConventionOption + { + Include = GetConventions(_Scopes) + }; + return context; + } + + public bool ContainsBaseline(string baselineId) + { + return _ModuleBaselineScope.ContainsKey(baselineId); + } + + /// + /// Check for any obsolete resources and log warnings. + /// + internal void CheckObsolete(ILogger logger) + { + if (logger == null) + return; + + foreach (var kv in _ModuleBaselineScope) + { + if (kv.Value) + logger.WarnResourceObsolete(ResourceKind.Baseline, kv.Key); + } + } + + private void Parameter(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + { + _Scopes.Add(OptionScope.FromParameters(ruleInclude, ruleTag, conventionInclude)); + } + + internal void Workspace(PSRuleOption option) + { + _Scopes.Add(OptionScope.FromWorkspace(option)); + } + + internal void Baseline(ScopeType type, string baselineId, string module, BaselineSpec spec, bool obsolete) + { + baselineId = ResourceHelper.GetIdString(module, baselineId); + _ModuleBaselineScope.Add(baselineId, obsolete); + _Scopes.Add(OptionScope.FromBaseline(type, baselineId, module, spec, obsolete)); + } + + internal void ModuleConfig(string module, ModuleConfigV1Spec spec) + { + _Scopes.Add(OptionScope.FromModuleConfig(module, spec)); + } + + private static bool ShouldCombine(string languageScope, OptionScope optionScope) + { + return optionScope.LanguageScope == LanguageScope.STANDALONE_SCOPENAME || optionScope.LanguageScope == languageScope || optionScope.Type == ScopeType.Explicit; + } + + /// + /// Combine the specified with an existing . + /// + private static void Combine(OptionContext context, OptionScope optionScope) { - return _OptionContext; + context.Baseline = Options.BaselineOption.Combine(context.Baseline, optionScope.Baseline); + context.Binding = BindingOption.Combine(context.Binding, optionScope.Binding); + context.Configuration = ConfigurationOption.Combine(context.Configuration, optionScope.Configuration); + //context.Convention = ConventionOption.Combine(context.Convention, optionScope.Convention); + context.Execution = ExecutionOption.Combine(context.Execution, optionScope.Execution); + context.Include = IncludeOption.Combine(context.Include, optionScope.Include); + context.Input = InputOption.Combine(context.Input, optionScope.Input); + context.Logging = LoggingOption.Combine(context.Logging, optionScope.Logging); + context.Output = OutputOption.Combine(context.Output, optionScope.Output); + context.Repository = RepositoryOption.Combine(context.Repository, optionScope.Repository); + context.Requires = RequiresOption.Combine(context.Requires, optionScope.Requires); + context.Rule = RuleOption.Combine(context.Rule, optionScope.Rule); + context.Suppression = SuppressionOption.Combine(context.Suppression, optionScope.Suppression); + } + + private static IResourceFilter GetRuleFilter(RuleOption option) + { + //var include = _Parameter?.Include ?? _Explicit?.Include ?? _WorkspaceBaseline?.Include ?? _ModuleBaseline?.Include; + //var exclude = _Explicit?.Exclude ?? _WorkspaceBaseline?.Exclude ?? _ModuleBaseline?.Exclude; + //var tag = _Parameter?.Tag ?? _Explicit?.Tag ?? _WorkspaceBaseline?.Tag ?? _ModuleBaseline?.Tag; + //var labels = _Parameter?.Labels ?? _Explicit?.Labels ?? _WorkspaceBaseline?.Labels ?? _ModuleBaseline?.Labels; + //var includeLocal = _Explicit == null && + // _Parameter?.Include == null && + // _Parameter?.Tag == null && + // _Parameter?.Labels == null && + // (_WorkspaceBaseline == null || !_WorkspaceBaseline.IncludeLocal.HasValue) ? true : _WorkspaceBaseline?.IncludeLocal; + return new RuleFilter(option.Include, option.Tag, option.Exclude, option.IncludeLocal, option.Labels); + } + + private static IResourceFilter GetConventionFilter(string languageScope, List scopes) + { + //var include = new List(); + //for (var i = 0; _Parameter?.Convention?.Include != null && i < _Parameter.Convention.Include.Length; i++) + // include.Add(_Parameter.Convention.Include[i]); + + //for (var i = 0; _WorkspaceConfig?.Convention?.Include != null && i < _WorkspaceConfig.Convention.Include.Length; i++) + // include.Add(_WorkspaceConfig.Convention.Include[i]); + + //for (var i = 0; _ModuleConfig?.Convention?.Include != null && i < _ModuleConfig.Convention.Include.Length; i++) + // include.Add(ResourceHelper.GetIdString(_ModuleConfig.LanguageScope, _ModuleConfig.Convention.Include[i])); + + return new ConventionFilter(GetConventions(scopes)); + } + + private static string[] GetConventions(List scopes) + { + var include = new List(); + for (var i = 0; i < scopes.Count; i++) + { + var add = scopes[i].Convention?.Include; + if (add == null || add.Length == 0) + continue; + + for (var j = 0; j < add.Length; j++) + { + if (scopes[i].Type == ScopeType.Module) + add[j] = ResourceHelper.GetIdString(scopes[i].LanguageScope, add[j]); + } + include.AddUnique(add); + } + return include.ToArray(); } - private void Parameter(string[] include, Hashtable tag, string[] convention) + private bool? GetIncludeLocal(List scopes) { - _OptionContext.Add(new OptionContext.BaselineScope( - type: OptionContext.ScopeType.Parameter, - include: include, - tag: tag, - convention: convention)); + for (var i = 0; i < scopes.Count; i++) + { + if (scopes[i].Type == ScopeType.Workspace && scopes[i].Rule != null && scopes[i].Rule.IncludeLocal.HasValue) + return scopes[i].Rule.IncludeLocal.Value; + } + return null; } - private void Workspace(PSRuleOption option) + private static string[] GetDefaultCulture() { - _OptionContext.Add(new OptionContext.BaselineScope( - type: OptionContext.ScopeType.Workspace, - baselineId: null, - moduleName: null, - option: option, - obsolete: false)); + var result = new List(); + var set = new HashSet(); - _OptionContext.Add(new OptionContext.ConfigScope( - type: OptionContext.ScopeType.Workspace, - moduleName: null, - option: option)); + // Fallback to current culture + var current = Environment.GetCurrentCulture(); + if (!set.Contains(current.Name) && !string.IsNullOrEmpty(current.Name)) + { + result.Add(current.Name); + set.Add(current.Name); + } + for (var p = current.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent) + { + if (!result.Contains(p.Name)) + result.Add(p.Name); + } + return result.ToArray(); } } } diff --git a/src/PSRule/Pipeline/OptionScope.cs b/src/PSRule/Pipeline/OptionScope.cs new file mode 100644 index 0000000000..a1db55e311 --- /dev/null +++ b/src/PSRule/Pipeline/OptionScope.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using PSRule.Configuration; +using PSRule.Definitions; +using PSRule.Definitions.Baselines; +using PSRule.Definitions.ModuleConfigs; +using PSRule.Options; + +namespace PSRule.Pipeline +{ + internal enum ScopeType + { + /// + /// Used when options are passed in by command line parameters. + /// + Parameter = 0, + + /// + /// Used when a baseline is explicitly set by name. + /// + Explicit = 1, + + /// + /// Used when options are set within the PSRule options from the workspace or an options object. + /// + Workspace = 2, + + /// + /// + /// + Baseline = 3, + + /// + /// Used for options that are inherited from module configuration. + /// + Module = 4 + } + + internal class OptionScope + { + public readonly ScopeType Type; + public readonly string LanguageScope; + + private OptionScope(ScopeType type, string languageScope) + { + Type = type; + LanguageScope = Runtime.LanguageScope.Normalize(languageScope); + } + + public Options.BaselineOption Baseline { get; set; } + + public BindingOption Binding { get; set; } + + public ConfigurationOption Configuration { get; set; } + + public ConventionOption Convention { get; set; } + + public ExecutionOption Execution { get; set; } + + public IncludeOption Include { get; set; } + + public InputOption Input { get; set; } + + public LoggingOption Logging { get; set; } + + public OutputOption Output { get; set; } + + public RepositoryOption Repository { get; set; } + + public RequiresOption Requires { get; set; } + + public RuleOption Rule { get; set; } + + public SuppressionOption Suppression { get; set; } + + public static OptionScope FromParameters(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + { + bool? includeLocal = ruleInclude == null && ruleTag == null ? null : false; + + return new OptionScope(ScopeType.Parameter, null) + { + Rule = new RuleOption + { + Include = ruleInclude, + Tag = ruleTag, + IncludeLocal = includeLocal, + }, + Convention = new ConventionOption + { + Include = conventionInclude + } + }; + } + + public static OptionScope FromWorkspace(PSRuleOption option) + { + return new OptionScope(ScopeType.Workspace, null) + { + Baseline = option.Baseline, + Binding = option.Binding, + Configuration = option.Configuration, + Convention = option.Convention, + Execution = option.Execution, + Include = option.Include, + Input = option.Input, + Logging = option.Logging, + Output = option.Output, + Repository = option.Repository, + Requires = option.Requires, + Rule = option.Rule, + Suppression = option.Suppression + }; + } + + public static OptionScope FromModuleConfig(string module, ModuleConfigV1Spec spec) + { + return new OptionScope(ScopeType.Module, module) + { + Binding = spec.Binding, + Configuration = spec.Configuration, + Convention = ApplyScope(module, spec.Convention), + Output = new OutputOption + { + Culture = spec.Output?.Culture + }, + Rule = new RuleOption + { + Baseline = spec.Rule?.Baseline + } + }; + } + + internal static OptionScope FromBaseline(ScopeType type, string baselineId, string module, BaselineSpec spec, bool obsolete) + { + return new OptionScope(type, module) + { + Binding = spec.Binding, + Configuration = spec.Configuration, + Rule = new RuleOption + { + Include = spec.Rule?.Include, + Exclude = spec.Rule?.Exclude, + Tag = spec.Rule?.Tag, + Labels = spec.Rule?.Labels, + IncludeLocal = type == ScopeType.Explicit ? false : null + } + }; + } + + private static string[] GetConventions(string scope, string[] include) + { + if (include == null || include.Length == 0) + return null; + + for (var i = 0; i < include.Length; i++) + include[i] = ResourceHelper.GetIdString(scope, include[i]); + + return include; + } + + private static ConventionOption ApplyScope(string scope, ConventionOption option) + { + if (option == null || option.Include == null || option.Include.Length == 0) + return option; + + option.Include = GetConventions(scope, option.Include); + return option; + } + } +} diff --git a/src/PSRule/Pipeline/OptionScopeComparer.cs b/src/PSRule/Pipeline/OptionScopeComparer.cs new file mode 100644 index 0000000000..70b4080d6c --- /dev/null +++ b/src/PSRule/Pipeline/OptionScopeComparer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Pipeline +{ + /// + /// A comparer to sort based on precedence. + /// + internal sealed class OptionScopeComparer : IComparer + { + public int Compare(OptionScope x, OptionScope y) + { + if (x == y) return 0; + if (x == null) return -1; + if (y == null) return 1; + if (x.Type == y.Type) return 0; + return x.Type < y.Type ? -1 : 1; + } + } +} diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 8fdd2552ad..da3f0ce650 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -439,7 +439,7 @@ protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTa { var unresolved = new List(); if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef) - unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), OptionContext.ScopeType.Explicit)); + unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), ScopeType.Explicit)); return PipelineContext.New( option: Option, @@ -448,7 +448,7 @@ protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTa bindTargetName: bindTargetName, bindTargetType: bindTargetType, bindField: bindField, - baseline: GetOptionContext(), + optionBuilder: GetOptionBuilder(), unresolved: unresolved ); } @@ -589,10 +589,9 @@ protected PathFilter GetInputFilter() return _InputFilter; } - private OptionContext GetOptionContext() + private OptionContextBuilder GetOptionBuilder() { - var builder = new OptionContextBuilder(Option, _Include, _Tag, _Convention); - return builder.Build(); + return new OptionContextBuilder(Option, _Include, _Tag, _Convention); } protected void ConfigureBinding(PSRuleOption option) diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 73badf6bdc..f4611e8c29 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -31,6 +31,8 @@ internal sealed class PipelineContext : IDisposable, IBindingContext [ThreadStatic] internal static PipelineContext CurrentThread; + private readonly OptionContextBuilder _OptionBuilder; + // Configuration parameters private readonly IList _Unresolved; private readonly LanguageMode _LanguageMode; @@ -50,7 +52,6 @@ internal sealed class PipelineContext : IDisposable, IBindingContext internal readonly Dictionary ContentCache; internal readonly Dictionary Selector; internal readonly List SuppressionGroup; - internal readonly OptionContext Baseline; internal readonly IHostContext HostContext; internal readonly PipelineReader Reader; internal readonly BindTargetMethod BindTargetName; @@ -60,10 +61,13 @@ internal sealed class PipelineContext : IDisposable, IBindingContext internal readonly Stopwatch RunTime; + private OptionContext _DefaultOptionContext; + public System.Security.Cryptography.HashAlgorithm ObjectHashAlgorithm { get; } - private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContext baseline, IList unresolved) + private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) { + _OptionBuilder = optionBuilder; Option = option; HostContext = hostContext; Reader = reader; @@ -77,18 +81,18 @@ private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineR ContentCache = new Dictionary(); Selector = new Dictionary(); SuppressionGroup = new List(); - Baseline = baseline; _Unresolved = unresolved ?? new List(); _TrackedIssues = new List(); ObjectHashAlgorithm = GetHashAlgorithm(option.Execution.HashAlgorithm.GetValueOrDefault(ExecutionOption.Default.HashAlgorithm.Value)); RunId = Environment.GetRunId() ?? ObjectHashAlgorithm.GetDigest(Guid.NewGuid().ToByteArray()); RunTime = Stopwatch.StartNew(); + _DefaultOptionContext = _OptionBuilder?.Build(null); } - public static PipelineContext New(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContext baseline, IList unresolved) + public static PipelineContext New(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) { - var context = new PipelineContext(option, hostContext, reader, bindTargetName, bindTargetType, bindField, baseline, unresolved); + var context = new PipelineContext(option, hostContext, reader, bindTargetName, bindTargetType, bindField, optionBuilder, unresolved); CurrentThread = context; return context; } @@ -165,7 +169,7 @@ internal void Import(RunspaceContext context, IResource resource) if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef)) { RemoveBaselineRef(resource.Id); - Baseline.Add(new OptionContext.BaselineScope(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete)); + _OptionBuilder.Baseline(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete); } else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector) Selector[selector.Id.Value] = new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); @@ -174,10 +178,10 @@ internal void Import(RunspaceContext context, IResource resource) if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline)) { var baselineId = ResourceHelper.GetIdString(moduleConfig.Source.Module, moduleConfig.Spec.Rule.Baseline); - if (!Baseline.ContainsBaseline(baselineId)) - _Unresolved.Add(new BaselineRef(baselineId, OptionContext.ScopeType.Module)); + if (!_OptionBuilder.ContainsBaseline(baselineId)) + _Unresolved.Add(new BaselineRef(baselineId, ScopeType.Baseline)); } - Baseline.Add(new OptionContext.ConfigScope(OptionContext.ScopeType.Module, resource.Source.Module, moduleConfig?.Spec)); + _OptionBuilder.ModuleConfig(resource.Source.Module, moduleConfig?.Spec); } else if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 suppressionGroup) { @@ -253,7 +257,19 @@ internal void Begin(RunspaceContext runspaceContext) { ReportUnresolved(runspaceContext); ReportIssue(runspaceContext); - Baseline.CheckObsolete(runspaceContext); + _DefaultOptionContext = _OptionBuilder.Build(null); + _OptionBuilder.CheckObsolete(runspaceContext); + } + + internal void UpdateLanguageScope(ILanguageScope languageScope) + { + var context = _OptionBuilder.Build(languageScope.Name); + languageScope.Configure(context); + } + + internal int GetConventionOrder(IConvention x) + { + return _DefaultOptionContext.GetConventionOrder(x); } private void ReportUnresolved(RunspaceContext runspaceContext) diff --git a/src/PSRule/Pipeline/TargetBinder.cs b/src/PSRule/Pipeline/TargetBinder.cs index d47232cd30..18f5049a7e 100644 --- a/src/PSRule/Pipeline/TargetBinder.cs +++ b/src/PSRule/Pipeline/TargetBinder.cs @@ -204,16 +204,28 @@ public TargetBindingResult(string targetName, string targetNamePath, string targ internal sealed class TargetBindingContext : ITargetBindingContext { - private readonly IBindingOption _BindingOption; + private readonly bool _PreferTargetInfo; + private readonly bool _IgnoreCase; + private readonly bool _UseQualifiedName; + private readonly FieldMap _Field; + private readonly string[] _TargetName; + private readonly string[] _TargetType; + private readonly string _NameSeparator; private readonly BindTargetMethod _BindTargetName; private readonly BindTargetMethod _BindTargetType; private readonly BindTargetMethod _BindField; private readonly HashSet _TypeFilter; - public TargetBindingContext(string languageScope, IBindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet typeFilter) + public TargetBindingContext(string languageScope, BindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet typeFilter) { LanguageScope = languageScope; - _BindingOption = bindingOption; + _PreferTargetInfo = bindingOption?.PreferTargetInfo ?? BindingOption.Default.PreferTargetInfo.Value; + _IgnoreCase = bindingOption?.IgnoreCase ?? BindingOption.Default.IgnoreCase.Value; + _UseQualifiedName = bindingOption?.UseQualifiedName ?? BindingOption.Default.UseQualifiedName.Value; + _Field = bindingOption?.Field; + _TargetName = bindingOption?.TargetName; + _TargetType = bindingOption?.TargetType; + _NameSeparator = bindingOption?.NameSeparator ?? BindingOption.Default.NameSeparator; _BindTargetName = bindTargetName; _BindTargetType = bindTargetType; _BindField = bindField; @@ -225,24 +237,24 @@ public TargetBindingContext(string languageScope, IBindingOption bindingOption, public ITargetBindingResult Bind(TargetObject o) { var targetNamePath = "."; - var targetName = _BindingOption.PreferTargetInfo && o.TargetName != null ? o.TargetName : _BindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o.Value, out targetNamePath); + var targetName = _PreferTargetInfo && o.TargetName != null ? o.TargetName : _BindTargetName(_TargetName, !_IgnoreCase, _PreferTargetInfo, o.Value, out targetNamePath); var targetTypePath = "."; - var targetType = _BindingOption.PreferTargetInfo && o.TargetType != null ? o.TargetType : _BindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o.Value, out targetTypePath); + var targetType = _PreferTargetInfo && o.TargetType != null ? o.TargetType : _BindTargetType(_TargetType, !_IgnoreCase, _PreferTargetInfo, o.Value, out targetTypePath); var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType)); // Bind custom fields - var field = BindField(_BindField, _BindingOption.Field, !_BindingOption.IgnoreCase, o.Value); + var field = BindField(_BindField, new[] { _Field }, !_IgnoreCase, o.Value); return Bind(targetName, targetNamePath, targetType, targetTypePath, field); } public ITargetBindingResult Bind(object o) { - var targetName = _BindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetNamePath); - var targetType = _BindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetTypePath); + var targetName = _BindTargetName(_TargetName, !_IgnoreCase, _PreferTargetInfo, o, out var targetNamePath); + var targetType = _BindTargetType(_TargetType, !_IgnoreCase, _PreferTargetInfo, o, out var targetTypePath); var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType)); // Bind custom fields - var field = BindField(_BindField, _BindingOption.Field, !_BindingOption.IgnoreCase, o); + var field = BindField(_BindField, new[] { _Field }, !_IgnoreCase, o); return Bind(targetName, targetNamePath, targetType, targetTypePath, field); } @@ -251,8 +263,8 @@ private ITargetBindingResult Bind(string targetName, string targetNamePath, stri var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType)); // Use qualified name - if (_BindingOption.UseQualifiedName) - targetName = string.Concat(targetType, _BindingOption.NameSeparator, targetName); + if (_UseQualifiedName) + targetName = string.Concat(targetType, _NameSeparator, targetName); return new TargetBindingResult ( diff --git a/src/PSRule/Runtime/LanguageScope.cs b/src/PSRule/Runtime/LanguageScope.cs index 1259d18202..7f499b9126 100644 --- a/src/PSRule/Runtime/LanguageScope.cs +++ b/src/PSRule/Runtime/LanguageScope.cs @@ -2,13 +2,14 @@ // Licensed under the MIT License. using System.Diagnostics; +using PSRule.Configuration; using PSRule.Definitions; using PSRule.Pipeline; namespace PSRule.Runtime { /// - /// A named scope for langauge elements. + /// A named scope for language elements. /// internal interface ILanguageScope : IDisposable { @@ -17,17 +18,14 @@ internal interface ILanguageScope : IDisposable /// string Name { get; } - IBindingOption Binding { get; } + BindingOption Binding { get; } /// /// Get an ordered culture preference list which will be tries for finding help. /// string[] Culture { get; } - /// - /// Adds one or more configuration values to the scope. - /// - void Configure(Dictionary configuration); + void Configure(OptionContext context); /// /// Try to get a specific configuration value by name. @@ -36,10 +34,6 @@ internal interface ILanguageScope : IDisposable void WithFilter(IResourceFilter resourceFilter); - void WithBinding(IBindingOption bindingOption); - - void WithCulture(string[] strings); - /// /// Get a filter for a specific resource kind. /// @@ -65,10 +59,10 @@ internal interface ILanguageScope : IDisposable [DebuggerDisplay("{Name}")] internal sealed class LanguageScope : ILanguageScope { - private const string STANDALONE_SCOPENAME = "."; + internal const string STANDALONE_SCOPENAME = "."; private readonly RunspaceContext _Context; - private readonly Dictionary _Configuration; + private IDictionary _Configuration; private readonly Dictionary _Service; private readonly Dictionary _Filter; @@ -78,7 +72,7 @@ public LanguageScope(RunspaceContext context, string name) { _Context = context; Name = Normalize(name); - _Configuration = new Dictionary(); + //_Configuration = new Dictionary(); _Filter = new Dictionary(); _Service = new Dictionary(); } @@ -87,7 +81,7 @@ public LanguageScope(RunspaceContext context, string name) public string Name { [DebuggerStepThrough] get; } /// - public IBindingOption Binding { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } + public BindingOption Binding { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } /// public string[] Culture { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } @@ -98,6 +92,19 @@ public void Configure(Dictionary configuration) _Configuration.AddUnique(configuration); } + /// + public void Configure(OptionContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + _Configuration = context.Configuration; + WithFilter(context.RuleFilter); + WithFilter(context.ConventionFilter); + Binding = context.Binding; + Culture = context.Output.Culture; + } + /// public bool TryConfigurationValue(string key, out object value) { @@ -111,18 +118,6 @@ public void WithFilter(IResourceFilter resourceFilter) _Filter[resourceFilter.Kind] = resourceFilter; } - /// - public void WithBinding(IBindingOption bindingOption) - { - Binding = bindingOption; - } - - /// - public void WithCulture(string[] culture) - { - Culture = culture; - } - /// public IResourceFilter GetFilter(ResourceKind kind) { diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 0ff07bb95c..3c9d81c23f 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -766,7 +766,7 @@ public void Init(Source[] source) Pipeline.Import(this, resource); foreach (var languageScope in _LanguageScopes.Get()) - Pipeline.Baseline.UpdateLanguageScope(languageScope); + Pipeline.UpdateLanguageScope(languageScope); foreach (var resource in resources) { @@ -786,7 +786,7 @@ public void Init(Source[] source) Pipeline.Import(this, resource); foreach (var languageScope in _LanguageScopes.Get()) - Pipeline.Baseline.UpdateLanguageScope(languageScope); + Pipeline.UpdateLanguageScope(languageScope); } private void InitLanguageScopes(Source[] source) diff --git a/tests/PSRule.Tests/BaselineTests.cs b/tests/PSRule.Tests/BaselineTests.cs index 15455545c8..24625c03e4 100644 --- a/tests/PSRule.Tests/BaselineTests.cs +++ b/tests/PSRule.Tests/BaselineTests.cs @@ -186,7 +186,7 @@ public void BaselineAsJson() private static Baseline[] GetBaselines(Source[] source) { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), null); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), null); context.Init(source); context.Begin(); var baseline = HostHelper.GetBaseline(source, context).ToArray(); diff --git a/tests/PSRule.Tests/FunctionBuilderTests.cs b/tests/PSRule.Tests/FunctionBuilderTests.cs index 1ba1432cd0..b5849be1d0 100644 --- a/tests/PSRule.Tests/FunctionBuilderTests.cs +++ b/tests/PSRule.Tests/FunctionBuilderTests.cs @@ -46,7 +46,7 @@ private static Source[] GetSource(string path) private static SelectorVisitor GetSelectorVisitor(string name, Source[] source, out RunspaceContext context) { - context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), null); + context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(), null), null); context.Init(source); context.Begin(); var selector = HostHelper.GetSelectorForTests(source, context).ToArray().FirstOrDefault(s => s.Name == name); diff --git a/tests/PSRule.Tests/FunctionTests.cs b/tests/PSRule.Tests/FunctionTests.cs index d53a8967fd..25a629453e 100644 --- a/tests/PSRule.Tests/FunctionTests.cs +++ b/tests/PSRule.Tests/FunctionTests.cs @@ -619,7 +619,7 @@ private static ExpressionContext GetContext() { var targetObject = new PSObject(); targetObject.Properties.Add(new PSNoteProperty("name", "TestObject1")); - var context = new Runtime.RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(GetOption(), null, null, null).Build(), null), null); + var context = new Runtime.RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(GetOption(), null, null, null), null), null); var s = GetSource(); var result = new ExpressionContext(context, s[0].File[0], Definitions.ResourceKind.Rule, targetObject); context.Init(s); diff --git a/tests/PSRule.Tests/MockLanguageScope.cs b/tests/PSRule.Tests/MockLanguageScope.cs new file mode 100644 index 0000000000..21edd88426 --- /dev/null +++ b/tests/PSRule.Tests/MockLanguageScope.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using PSRule.Configuration; +using PSRule.Definitions; +using PSRule.Definitions.Rules; +using PSRule.Pipeline; +using PSRule.Runtime; + +namespace PSRule +{ + internal sealed class MockLanguageScope : ILanguageScope + { + internal RuleFilter RuleFilter; + + public MockLanguageScope(string name) + { + Name = name; + } + + public string Name { get; } + + public BindingOption Binding => throw new System.NotImplementedException(); + + public string[] Culture => throw new System.NotImplementedException(); + + public void AddService(string name, object service) + { + + } + + public void Configure(OptionContext context) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + + } + + public IResourceFilter GetFilter(ResourceKind kind) + { + throw new System.NotImplementedException(); + } + + public object GetService(string name) + { + throw new System.NotImplementedException(); + } + + public bool TryConfigurationValue(string key, out object value) + { + throw new System.NotImplementedException(); + } + + public bool TryGetName(object o, out string name, out string path) + { + throw new System.NotImplementedException(); + } + + public bool TryGetScope(object o, out string[] scope) + { + throw new System.NotImplementedException(); + } + + public bool TryGetType(object o, out string type, out string path) + { + throw new System.NotImplementedException(); + } + + public void WithFilter(IResourceFilter resourceFilter) + { + if (resourceFilter is RuleFilter ruleFilter) + RuleFilter = ruleFilter; + } + } +} diff --git a/tests/PSRule.Tests/ModuleConfigTests.cs b/tests/PSRule.Tests/ModuleConfigTests.cs index 6ec0ccb9dd..36f19eb73b 100644 --- a/tests/PSRule.Tests/ModuleConfigTests.cs +++ b/tests/PSRule.Tests/ModuleConfigTests.cs @@ -18,7 +18,7 @@ public sealed class ModuleConfigTests [InlineData("ModuleConfig.Rule.jsonc")] public void ReadModuleConfig(string path) { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), null); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), null); var configuration = HostHelper.GetModuleConfigForTests(GetSource(path), context).ToArray(); Assert.NotNull(configuration); Assert.Equal("Configuration1", configuration[0].Name); diff --git a/tests/PSRule.Tests/OptionContextTests.cs b/tests/PSRule.Tests/OptionContextTests.cs index 10205c33e5..07a4b4e91d 100644 --- a/tests/PSRule.Tests/OptionContextTests.cs +++ b/tests/PSRule.Tests/OptionContextTests.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; +using System; +using System.IO; using PSRule.Configuration; using PSRule.Definitions; +using PSRule.Definitions.Baselines; using PSRule.Definitions.Rules; using PSRule.Pipeline; +using PSRule.Runtime; namespace PSRule { @@ -19,13 +22,10 @@ public void Build() { // Create option context var builder = new OptionContextBuilder(GetOption()); - var optionContext = builder.Build(); - - Assert.NotNull(optionContext); // Check empty scope - var testScope = new Runtime.LanguageScope(null, "Empty"); - optionContext.UpdateLanguageScope(testScope); + var testScope = new LanguageScope(null, "Empty"); + testScope.Configure(builder.Build(testScope.Name)); Assert.Equal(new string[] { "en-ZZ" }, testScope.Culture); } @@ -34,10 +34,9 @@ public void Order() { // Create option context var builder = new OptionContextBuilder(GetOption()); - var optionContext = builder.Build(); - var localScope = new Runtime.LanguageScope(null, null); - optionContext.UpdateLanguageScope(localScope); + var localScope = new LanguageScope(null, null); + localScope.Configure(builder.Build(null)); var ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; Assert.NotNull(ruleFilter); @@ -45,116 +44,121 @@ public void Order() // With explict baseline builder = new OptionContextBuilder(GetOption()); - optionContext = builder.Build(); - optionContext.Add(new OptionContext.BaselineScope(OptionContext.ScopeType.Explicit, new string[] { "abc" }, null, null)); - optionContext.UpdateLanguageScope(localScope); + builder.Baseline(ScopeType.Explicit, "BaselineExplicit", null, GetBaseline(ruleInclude: new[] { "abc" }), false); + localScope.Configure(builder.Build(localScope.Name)); ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; Assert.NotNull(ruleFilter); Assert.False(ruleFilter.IncludeLocal); // With include from parameters builder = new OptionContextBuilder(GetOption(), include: new string[] { "abc" }); - optionContext = builder.Build(); - optionContext.UpdateLanguageScope(localScope); + localScope.Configure(builder.Build(localScope.Name)); ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; Assert.NotNull(ruleFilter); Assert.False(ruleFilter.IncludeLocal); - builder = new OptionContextBuilder(GetOption()); - optionContext = builder.Build(); - optionContext.Add(new OptionContext.BaselineScope(OptionContext.ScopeType.Workspace, new string[] { "abc" }, null, null)); - optionContext.UpdateLanguageScope(localScope); + builder = new OptionContextBuilder(GetOption(ruleInclude: new[] { "abc" })); + localScope.Configure(builder.Build(localScope.Name)); ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; Assert.NotNull(ruleFilter); Assert.True(ruleFilter.IncludeLocal); } - #region Helper methods - - internal sealed class MockScope : Runtime.ILanguageScope + /// + /// Test that options from separate files can be combined. + /// + [Fact] + public void Merge_multiple_options_from_file() { - internal RuleFilter RuleFilter; + var builder = new OptionContextBuilder(); - public MockScope(string name) - { - Name = name; - } + builder.Workspace(GetOptionFromFile("PSRule.Tests2.yml")); + builder.Workspace(GetOptionFromFile()); + builder.Workspace(GetOption()); - public string Name { get; } + var context = builder.Build(null); - public IBindingOption Binding => throw new System.NotImplementedException(); + // With workspace options ordered by first + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); + Assert.Equal(new[] { "virtualMachine", "virtualNetwork" }, context.Input.TargetType); + Assert.Equal(new[] { "en-CC", "en-DD" }, context.Output.Culture); + Assert.True(context.Configuration.TryGetStringArray("option5", out var option5)); + Assert.Equal(new[] { "option5a", "option5b" }, option5); + Assert.True(context.Configuration.TryGetString("option6", out var option6)); + Assert.Equal("value6", option6); - public string[] Culture => throw new System.NotImplementedException(); + // With module default baseline + builder.Baseline(ScopeType.Module, "BaselineDefault", "Module1", GetBaseline(targetType: new[] { "defaultType" }, ruleInclude: new[] { "defaultRule" }), false); + context = builder.Build(null); - public void AddService(string name, object service) - { + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1", "rule2" }, context.Rule.Include); - } + context = builder.Build("Module1"); - public void Configure(Dictionary configuration) - { + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1", "rule2" }, context.Rule.Include); - } - - public void Dispose() - { + // With explict baseline + builder.Baseline(ScopeType.Explicit, "BaselineExplicit", "Module1", GetBaseline(), false); + context = builder.Build(null); - } + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "typeName" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1" }, context.Rule.Include); - public IResourceFilter GetFilter(ResourceKind kind) - { - throw new System.NotImplementedException(); - } + context = builder.Build("Module1"); - public object GetService(string name) - { - throw new System.NotImplementedException(); - } - - public bool TryConfigurationValue(string key, out object value) - { - throw new System.NotImplementedException(); - } - - public bool TryGetName(object o, out string name, out string path) - { - throw new System.NotImplementedException(); - } + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "typeName" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1" }, context.Rule.Include); + } - public bool TryGetScope(object o, out string[] scope) - { - throw new System.NotImplementedException(); - } + #region Helper methods - public bool TryGetType(object o, out string type, out string path) - { - throw new System.NotImplementedException(); - } + private static PSRuleOption GetOption(string[] culture = null, string[] ruleInclude = null) + { + var option = new PSRuleOption(); - public void WithBinding(IBindingOption bindingOption) - { + // Specify a culture otherwise it varies within CI. + option.Output.Culture = culture ?? new string[] { "en-ZZ" }; - } + option.Rule.Include = ruleInclude; - public void WithCulture(string[] strings) - { + // Add a configuration option. + option.Configuration.Add("option6", "value6"); + option.Configuration.Add("option5", "value5"); + return option; + } - } + private static PSRuleOption GetOptionFromFile(string file = "PSRule.Tests.yml") + { + return PSRuleOption.FromFileOrEmpty(GetSourcePath(file)); + } - public void WithFilter(IResourceFilter resourceFilter) + private static BaselineSpec GetBaseline(string[] targetType = null, string[] ruleInclude = null) + { + targetType ??= new[] { "typeName" }; + ruleInclude ??= new[] { "rule1" }; + return new BaselineSpec { - if (resourceFilter is RuleFilter ruleFilter) - RuleFilter = ruleFilter; - } + Binding = new BindingOption + { + TargetType = targetType + }, + Rule = new RuleOption + { + Include = ruleInclude + } + }; } - private static PSRuleOption GetOption(string[] culture = null) + private static string GetSourcePath(string fileName) { - var option = new PSRuleOption(); - - // Specify a culture otherwise it varies within CI. - option.Output.Culture = culture ?? new string[] { "en-ZZ" }; - return option; + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } #endregion Helper methods diff --git a/tests/PSRule.Tests/OutputWriterTests.cs b/tests/PSRule.Tests/OutputWriterTests.cs index 8f34e960f1..4b904ca844 100644 --- a/tests/PSRule.Tests/OutputWriterTests.cs +++ b/tests/PSRule.Tests/OutputWriterTests.cs @@ -311,7 +311,7 @@ public void JobSummary() var option = GetOption(); var output = new TestWriter(option); var result = new InvokeResult(); - var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null); + var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null); result.Add(GetPass()); result.Add(GetFail()); result.Add(GetFail("rid-003", SeverityLevel.Warning, ruleId: "TestModule\\Rule-003")); diff --git a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 index 70c78cd174..5bc66a4352 100644 --- a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 @@ -833,7 +833,7 @@ Describe 'Baseline' -Tag 'Baseline' { $Null = @($testObject | Invoke-PSRule -Path $ruleFilePath,$baselineFilePath -Baseline 'TestBaseline5' -WarningVariable outWarn -WarningAction SilentlyContinue); $warnings = @($outWarn); $warnings.Length | Should -Be 1; - $warnings[0] | Should -BeExactly "The Baseline 'TestBaseline5' is obsolete. Consider switching to an alternative Baseline."; + $warnings[0] | Should -BeExactly "The Baseline '.\TestBaseline5' is obsolete. Consider switching to an alternative Baseline."; } It 'With scoped configuration' { diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index eb97eb1f5f..52fea8556f 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -127,6 +127,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/tests/PSRule.Tests/PSRuleOptionTests.cs b/tests/PSRule.Tests/PSRuleOptionTests.cs index 9040ac042d..d1eaa662d7 100644 --- a/tests/PSRule.Tests/PSRuleOptionTests.cs +++ b/tests/PSRule.Tests/PSRuleOptionTests.cs @@ -9,6 +9,9 @@ namespace PSRule { + /// + /// Tests for . + /// public sealed class PSRuleOptionTests { [Fact] @@ -89,7 +92,7 @@ public void GetBaselineGroupFromYaml() private static Runtime.Configuration GetConfigurationHelper(PSRuleOption option) { var builder = new OptionContextBuilder(option); - var context = new Runtime.RunspaceContext(PipelineContext.New(option, null, null, null, null, null, builder.Build(), null), null); + var context = new Runtime.RunspaceContext(PipelineContext.New(option, null, null, null, null, null, builder, null), null); context.Init(null); context.Begin(); return new Runtime.Configuration(context); diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index 84fc350536..05de4826c9 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -179,7 +179,7 @@ public void GetRuleWithBaseline() public void PipelineWithInvariantCulture() { Environment.UseCurrentCulture(CultureInfo.InvariantCulture); - var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null); + var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null); var writer = new TestWriter(GetOption()); var pipeline = new GetRulePipeline(context, GetSource(), new PipelineReader(null, null, null), writer, false); try @@ -201,7 +201,7 @@ public void PipelineWithInvariantCultureDisabled() Environment.UseCurrentCulture(CultureInfo.InvariantCulture); var option = new PSRuleOption(); option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; - var context = PipelineContext.New(option, null, null, null, null, null, new OptionContext(), null); + var context = PipelineContext.New(option, null, null, null, null, null, new OptionContextBuilder(), null); var writer = new TestWriter(option); var pipeline = new GetRulePipeline(context, GetSource(), new PipelineReader(null, null, null), writer, false); try diff --git a/tests/PSRule.Tests/ResourceValidatorTests.cs b/tests/PSRule.Tests/ResourceValidatorTests.cs index f753226cdb..0228f8f1aa 100644 --- a/tests/PSRule.Tests/ResourceValidatorTests.cs +++ b/tests/PSRule.Tests/ResourceValidatorTests.cs @@ -17,7 +17,7 @@ public sealed class ResourceValidatorTests public void ResourceName() { var writer = new TestWriter(GetOption()); - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), writer); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), writer); // Get good rules var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false); diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index 594c51ac32..6fc01ed52f 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -24,7 +24,7 @@ public sealed class RulesTests [Fact] public void ReadYamlRule() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource()); context.Begin(); @@ -60,7 +60,7 @@ public void ReadYamlRule() [Fact] public void ReadYamlSubSelectorRule() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource("FromFileSubSelector.Rule.yaml")); context.Begin(); @@ -132,7 +132,7 @@ public void ReadYamlSubSelectorRule() [Fact] public void EvaluateYamlRule() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource()); context.Begin(); ImportSelectors(context); @@ -195,7 +195,7 @@ public void EvaluateYamlRule() [Fact] public void RuleWithObjectPath() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource()); context.Begin(); ImportSelectors(context); @@ -227,7 +227,7 @@ public void RuleWithObjectPath() [Fact] public void ReadJsonRule() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource()); context.Begin(); @@ -263,7 +263,7 @@ public void ReadJsonRule() [Fact] public void ReadJsonSubSelectorRule() { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption())); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(), null), new TestWriter(GetOption())); context.Init(GetSource("FromFileSubSelector.Rule.jsonc")); context.Begin(); diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index 9d3edbc397..7ca7b9f8f7 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -34,7 +34,7 @@ public sealed class SelectorTests public void ReadSelector(string type, string path) { var testObject = GetObject((name: "value", value: 3)); - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), null); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), null); context.Init(GetSource(path)); context.Begin(); var selector = HostHelper.GetSelectorForTests(GetSource(path), context).ToArray(); @@ -1874,7 +1874,7 @@ private static PSObject GetObject(params (string name, object value)[] propertie private static SelectorVisitor GetSelectorVisitor(string name, Source[] source, out RunspaceContext context) { var builder = new OptionContextBuilder(GetOption()); - context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, builder.Build(), null), null); + context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, builder, null), null); context.Init(source); context.Begin(); var selector = HostHelper.GetSelectorForTests(source, context).ToArray().FirstOrDefault(s => s.Name == name); diff --git a/tests/PSRule.Tests/SuppressionFilterTests.cs b/tests/PSRule.Tests/SuppressionFilterTests.cs index 07b5871e70..ec77d9694a 100644 --- a/tests/PSRule.Tests/SuppressionFilterTests.cs +++ b/tests/PSRule.Tests/SuppressionFilterTests.cs @@ -18,7 +18,7 @@ public sealed class SuppressionFilterTests public void Match() { var option = GetOption(); - var context = new RunspaceContext(PipelineContext.New(option, null, null, null, null, null, new OptionContext(), null), new TestWriter(option)); + var context = new RunspaceContext(PipelineContext.New(option, null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(option)); context.Init(GetSource()); context.Begin(); var rules = HostHelper.GetRule(GetSource(), context, includeDependencies: false); diff --git a/tests/PSRule.Tests/SuppressionGroupTests.cs b/tests/PSRule.Tests/SuppressionGroupTests.cs index d95fe9b4b3..66f5325340 100644 --- a/tests/PSRule.Tests/SuppressionGroupTests.cs +++ b/tests/PSRule.Tests/SuppressionGroupTests.cs @@ -87,9 +87,9 @@ private static PSRuleOption GetOption() return option; } - private static OptionContext GetOptionContext() + private static OptionContextBuilder GetOptionContext() { - return new OptionContextBuilder(GetOption()).Build(); + return new OptionContextBuilder(GetOption()); } private static Source[] GetSource(string path) diff --git a/tests/PSRule.Tests/TargetBinderTests.cs b/tests/PSRule.Tests/TargetBinderTests.cs index 394b5da6a6..51d788dc2e 100644 --- a/tests/PSRule.Tests/TargetBinderTests.cs +++ b/tests/PSRule.Tests/TargetBinderTests.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json.Linq; using PSRule.Configuration; using PSRule.Definitions.Baselines; +using PSRule.Definitions.ModuleConfigs; using PSRule.Pipeline; namespace PSRule @@ -78,58 +79,52 @@ private static TargetObject GetTargetObject(string targetType = null) private static ITargetBinder GetBinder() { var builder = new TargetBinderBuilder(PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, null); - var option = new OptionContext(); - - option.Add(new OptionContext.BaselineScope( - type: OptionContext.ScopeType.Module, - baselineId: null, - moduleName: "Module1", - option: GetOption( - targetName: new string[] { "name" }, - targetType: new string[] { "type" } - ), - obsolete: false - )); - - option.Add(new OptionContext.BaselineScope( - type: OptionContext.ScopeType.Module, - baselineId: null, - moduleName: "Module2", - option: GetOption( - targetName: new string[] { "AlternativeName" }, - targetType: new string[] { "type" } - ), - obsolete: false - )); - - option.Add(new OptionContext.BaselineScope( - type: OptionContext.ScopeType.Module, - baselineId: null, - moduleName: "Module3", - option: GetOption( - targetName: new string[] { "name" }, - targetType: new string[] { "type" }, - preferTargetInfo: true - ), - obsolete: false - )); + var option = new OptionContextBuilder(); + + option.ModuleConfig("Module1", new ModuleConfigV1Spec + { + Binding = new BindingOption + { + TargetName = new[] { "name" }, + TargetType = new[] { "type" } + } + }); + + option.ModuleConfig("Module2", new ModuleConfigV1Spec + { + Binding = new BindingOption + { + TargetName = new[] { "AlternativeName" }, + TargetType = new[] { "type" } + } + }); + + option.ModuleConfig("Module3", new ModuleConfigV1Spec + { + Binding = new BindingOption + { + TargetName = new[] { "name" }, + TargetType = new[] { "type" }, + PreferTargetInfo = true + } + }); var scopes = new Runtime.LanguageScopeSet(null); scopes.Import("Module1", out var module1); - option.UpdateLanguageScope(module1); + module1.Configure(option.Build(module1.Name)); builder.With(module1); scopes.Import("Module2", out var module2); - option.UpdateLanguageScope(module2); + module2.Configure(option.Build(module2.Name)); builder.With(module2); scopes.Import("Module3", out var module3); - option.UpdateLanguageScope(module3); + module3.Configure(option.Build(module3.Name)); builder.With(module3); scopes.Import(".", out var local); - option.UpdateLanguageScope(local); + local.Configure(option.Build(local.Name)); builder.With(local); return builder.Build(); }